Add web auth, infinite-scroll, agent admission and review fixes across agent/server/web
This commit is contained in:
@@ -6,6 +6,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
@@ -14,8 +16,9 @@ import (
|
||||
var replacementRune = []byte("<22>")
|
||||
|
||||
const (
|
||||
MaxOutputBytes = 8192
|
||||
truncMarkerFmt = "...[truncated %d bytes]"
|
||||
MaxOutputBytes = 8192
|
||||
truncMarkerFmt = "...[truncated %d bytes]"
|
||||
limitFailPrefix = "__MONLET_RESOURCE_LIMIT_FAILED__:"
|
||||
)
|
||||
|
||||
type Status string
|
||||
@@ -28,16 +31,41 @@ const (
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
Status Status
|
||||
ExitCode int
|
||||
DurationMs int64
|
||||
Output string
|
||||
OutputTruncated bool
|
||||
Status Status
|
||||
ExitCode int
|
||||
DurationMs int64
|
||||
Output string
|
||||
OutputTruncated bool
|
||||
ResourceLimitFailure string
|
||||
}
|
||||
|
||||
// ResourceLimits contains best-effort shell ulimit values for a check command.
|
||||
type ResourceLimits struct {
|
||||
CPUTime time.Duration
|
||||
MemoryBytes int64
|
||||
OpenFiles int
|
||||
}
|
||||
|
||||
func (l ResourceLimits) IsZero() bool {
|
||||
return l.CPUTime == 0 && l.MemoryBytes == 0 && l.OpenFiles == 0
|
||||
}
|
||||
|
||||
// Run executes argv with timeout. Stdout+stderr are captured into a single
|
||||
// buffer; both are truncated to 8 KiB UTF-8 bytes if exceeded.
|
||||
func Run(ctx context.Context, argv []string, timeout time.Duration) Result {
|
||||
return runArgv(ctx, argv, timeout)
|
||||
}
|
||||
|
||||
// RunCommand executes a shell command with optional resource limits.
|
||||
func RunCommand(ctx context.Context, command string, timeout time.Duration, limits ResourceLimits) Result {
|
||||
argv := []string{"/bin/sh", "-c", command}
|
||||
if !limits.IsZero() {
|
||||
argv = limitedShellArgv(command, limits)
|
||||
}
|
||||
return runArgv(ctx, argv, timeout)
|
||||
}
|
||||
|
||||
func runArgv(ctx context.Context, argv []string, timeout time.Duration) Result {
|
||||
start := time.Now()
|
||||
cctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
@@ -67,6 +95,23 @@ func Run(ctx context.Context, argv []string, timeout time.Duration) Result {
|
||||
r.Output = fmt.Sprintf("critical: check timed out after %s", timeout)
|
||||
return r
|
||||
}
|
||||
if isExitCode(err, 125) {
|
||||
if resource, ok := detectLimitFailure(out); ok {
|
||||
r.Status = StatusUnknown
|
||||
r.ExitCode = -1
|
||||
r.Output = "unknown: failed to apply resource limit: " + resource
|
||||
r.OutputTruncated = false
|
||||
r.ResourceLimitFailure = resource
|
||||
return r
|
||||
}
|
||||
}
|
||||
if isSignal(err, syscall.SIGXCPU) {
|
||||
r.Status = StatusCritical
|
||||
r.ExitCode = 2
|
||||
r.Output = "critical: CPU time limit exceeded"
|
||||
r.OutputTruncated = false
|
||||
return r
|
||||
}
|
||||
if err != nil {
|
||||
var ee *exec.ExitError
|
||||
if errors.As(err, &ee) {
|
||||
@@ -74,6 +119,8 @@ func Run(ctx context.Context, argv []string, timeout time.Duration) Result {
|
||||
} else {
|
||||
r.Status = StatusUnknown
|
||||
r.ExitCode = -1
|
||||
// Launch failure (e.g. shell missing); surface exec error, never check output.
|
||||
r.Output, r.OutputTruncated = TruncateUTF8([]byte("unknown: failed to start check: "+err.Error()), MaxOutputBytes)
|
||||
return r
|
||||
}
|
||||
}
|
||||
@@ -81,6 +128,60 @@ func Run(ctx context.Context, argv []string, timeout time.Duration) Result {
|
||||
return r
|
||||
}
|
||||
|
||||
func isExitCode(err error, code int) bool {
|
||||
var ee *exec.ExitError
|
||||
return errors.As(err, &ee) && ee.ExitCode() == code
|
||||
}
|
||||
|
||||
func limitedShellArgv(command string, limits ResourceLimits) []string {
|
||||
cpu := ""
|
||||
if limits.CPUTime > 0 {
|
||||
cpu = strconv.FormatInt(int64((limits.CPUTime+time.Second-1)/time.Second), 10)
|
||||
}
|
||||
mem := ""
|
||||
if limits.MemoryBytes > 0 {
|
||||
mem = strconv.FormatInt((limits.MemoryBytes+1023)/1024, 10)
|
||||
}
|
||||
openFiles := ""
|
||||
if limits.OpenFiles > 0 {
|
||||
openFiles = strconv.Itoa(limits.OpenFiles)
|
||||
}
|
||||
script := `
|
||||
cmd=$1
|
||||
cpu=$2
|
||||
mem=$3
|
||||
open_files=$4
|
||||
if [ -n "$cpu" ]; then ulimit -t "$cpu" 2>/dev/null || { printf '%s\n' '` + limitFailPrefix + `cpu_time'; exit 125; }; fi
|
||||
if [ -n "$mem" ]; then ulimit -v "$mem" 2>/dev/null || { printf '%s\n' '` + limitFailPrefix + `memory'; exit 125; }; fi
|
||||
if [ -n "$open_files" ]; then ulimit -n "$open_files" 2>/dev/null || { printf '%s\n' '` + limitFailPrefix + `open_files'; exit 125; }; fi
|
||||
exec /bin/sh -c "$cmd"
|
||||
`
|
||||
return []string{"/bin/sh", "-c", script, "monlet-limit-wrapper", command, cpu, mem, openFiles}
|
||||
}
|
||||
|
||||
func detectLimitFailure(output string) (string, bool) {
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, limitFailPrefix) {
|
||||
resource := strings.TrimPrefix(line, limitFailPrefix)
|
||||
if resource != "cpu_time" && resource != "memory" && resource != "open_files" {
|
||||
resource = "unknown"
|
||||
}
|
||||
return resource, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func isSignal(err error, sig syscall.Signal) bool {
|
||||
var ee *exec.ExitError
|
||||
if !errors.As(err, &ee) {
|
||||
return false
|
||||
}
|
||||
ws, ok := ee.Sys().(syscall.WaitStatus)
|
||||
return ok && ws.Signaled() && ws.Signal() == sig
|
||||
}
|
||||
|
||||
func MapExit(code int) Status {
|
||||
switch code {
|
||||
case 0:
|
||||
|
||||
Reference in New Issue
Block a user