Files
osmedeus/internal/runner/host_runner.go
T
j3ssie f5840272c5 feat: add run cancellation, event enhancements, and performance optimizations
Major features:
- Add run registry for tracking active runs with PID management
- Add API-based run cancellation with process termination
- Add event trigger input vars syntax for multi-variable extraction
- Add filter_functions with utility function support in triggers
- Add event envelope injection for full event context in workflows
- Add write coordinator for batched database operations

API improvements:
- Add logout endpoint and diffs endpoints for assets/vulnerabilities
- Add step-results listing endpoint
- Update schedule model with target, workspace, params fields
- Change run_id to run_uuid across API responses

Performance:
- Add compiled JS program caching for 60-80% faster loop conditions
- Add parallel shard rendering for 20-40% faster workflow startup
- Add memory-mapped I/O for large file line counting
- Add efficient output buffer combining in runners
- Add mtime-based cache invalidation for workflow loader

Other changes:
- Rename trigger field from trigger to triggers in workflow YAML
- Disable pongo2 HTML autoescape for shell command templates
- Update JWT expiration default to 1440 minutes (1 day)
- Change CORS default to reflect-origin for credentials support
- Add source_type field to events (run, eval, api)
- Skip copying core Unix tools to external-binaries
2026-01-24 01:11:33 +08:00

146 lines
3.2 KiB
Go

package runner
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"strings"
"syscall"
"github.com/j3ssie/osmedeus/v5/internal/core"
)
// HostRunner executes commands on the local machine
type HostRunner struct {
binariesPath string
onPIDStart PIDCallback
onPIDEnd PIDCallback
}
// NewHostRunner creates a new host runner
func NewHostRunner(binariesPath string) *HostRunner {
return &HostRunner{binariesPath: binariesPath}
}
// Execute runs a command on the local machine
func (r *HostRunner) Execute(ctx context.Context, command string) (*CommandResult, error) {
cmd := exec.Command("sh", "-c", command)
// Create new process group so we can kill all children on interrupt
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
// Prepend binaries path to PATH if configured
if r.binariesPath != "" {
env := os.Environ()
pathUpdated := false
for i, e := range env {
if strings.HasPrefix(e, "PATH=") {
env[i] = "PATH=" + r.binariesPath + ":" + strings.TrimPrefix(e, "PATH=")
pathUpdated = true
break
}
}
if !pathUpdated {
env = append(env, "PATH="+r.binariesPath)
}
cmd.Env = env
}
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
// Start the command
if err := cmd.Start(); err != nil {
return &CommandResult{
Output: "",
ExitCode: -1,
Error: err,
}, nil
}
// Track PID for cancellation support
pid := cmd.Process.Pid
if r.onPIDStart != nil {
r.onPIDStart(pid)
}
// Wait for command completion or context cancellation
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
select {
case <-ctx.Done():
// Context cancelled - kill entire process group
if cmd.Process != nil {
// Kill process group (negative PID)
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}
<-done // Wait for process to exit
// Notify PID ended
if r.onPIDEnd != nil {
r.onPIDEnd(pid)
}
return &CommandResult{
Output: combineOutput(&stdout, &stderr),
ExitCode: -1,
Error: ctx.Err(),
}, nil
case err := <-done:
// Normal completion - notify PID ended
if r.onPIDEnd != nil {
r.onPIDEnd(pid)
}
exitCode := 0
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
exitCode = exitErr.ExitCode()
}
}
return &CommandResult{
Output: combineOutput(&stdout, &stderr),
ExitCode: exitCode,
Error: err,
}, nil
}
}
// SetPIDCallbacks sets callbacks for process lifecycle events
func (r *HostRunner) SetPIDCallbacks(onStart, onEnd PIDCallback) {
r.onPIDStart = onStart
r.onPIDEnd = onEnd
}
// Setup is a no-op for host runner
func (r *HostRunner) Setup(ctx context.Context) error {
return nil
}
// Cleanup is a no-op for host runner
func (r *HostRunner) Cleanup(ctx context.Context) error {
return nil
}
// CopyFromRemote is not supported for host runner (local execution doesn't need file copy)
func (r *HostRunner) CopyFromRemote(ctx context.Context, remotePath, localPath string) error {
return fmt.Errorf("CopyFromRemote not supported for host runner")
}
// Type returns the runner type
func (r *HostRunner) Type() core.RunnerType {
return core.RunnerTypeHost
}
// IsRemote returns false since host runner runs locally
func (r *HostRunner) IsRemote() bool {
return false
}