Files
osmedeus/internal/runner/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

124 lines
3.7 KiB
Go

package runner
import (
"bytes"
"context"
"fmt"
"strings"
"github.com/j3ssie/osmedeus/v5/internal/core"
)
// MaxOutputSize limits the combined output size to prevent memory issues
// with very large command outputs (10MB default)
const MaxOutputSize = 10 * 1024 * 1024
// combineOutput efficiently combines stdout and stderr using a single allocation.
// This reduces memory allocations from 3 to 1 compared to stdout.String() + stderr.String().
// Outputs exceeding MaxOutputSize are truncated with a warning message.
func combineOutput(stdout, stderr *bytes.Buffer) string {
totalLen := stdout.Len() + stderr.Len()
if totalLen == 0 {
return ""
}
if totalLen > MaxOutputSize {
// Truncate with message
var sb strings.Builder
sb.Grow(MaxOutputSize + 30)
limit := min(MaxOutputSize, stdout.Len())
sb.Write(stdout.Bytes()[:limit])
if remaining := MaxOutputSize - limit; remaining > 0 && stderr.Len() > 0 {
sb.Write(stderr.Bytes()[:min(remaining, stderr.Len())])
}
sb.WriteString("\n[output truncated]")
return sb.String()
}
var sb strings.Builder
sb.Grow(totalLen)
sb.Write(stdout.Bytes())
sb.Write(stderr.Bytes())
return sb.String()
}
// CommandResult holds the output of a command execution
type CommandResult struct {
Output string // Combined stdout and stderr
ExitCode int // Exit code of the command
Error error // Error if execution failed
}
// PIDCallback is called when a process starts or ends
type PIDCallback func(pid int)
// Runner interface for executing commands in different environments
type Runner interface {
// Execute runs a command and returns the result
Execute(ctx context.Context, command string) (*CommandResult, error)
// Setup prepares the runner (e.g., copy binary, start container, establish SSH)
Setup(ctx context.Context) error
// Cleanup tears down resources (e.g., stop container, close SSH connection)
Cleanup(ctx context.Context) error
// Type returns the runner type
Type() core.RunnerType
// IsRemote returns true if commands run on a remote machine
IsRemote() bool
// CopyFromRemote copies a file from the remote environment to the local host
// For Docker: uses docker cp, for SSH: uses rsync
CopyFromRemote(ctx context.Context, remotePath, localPath string) error
// SetPIDCallbacks sets callbacks for process lifecycle events.
// onStart is called when a process starts (with the PID)
// onEnd is called when the process ends (with the PID)
// This enables tracking of running processes for cancellation support.
SetPIDCallbacks(onStart, onEnd PIDCallback)
}
// NewRunner creates a runner based on workflow configuration
func NewRunner(workflow *core.Workflow, binaryPath string) (Runner, error) {
runnerType := workflow.Runner
if runnerType == "" {
runnerType = core.RunnerTypeHost
}
config := workflow.RunnerConfig
if config == nil {
config = &core.RunnerConfig{}
}
switch runnerType {
case core.RunnerTypeHost:
return NewHostRunner(binaryPath), nil
case core.RunnerTypeDocker:
return NewDockerRunner(config, binaryPath)
case core.RunnerTypeSSH:
return NewSSHRunner(config, binaryPath)
default:
return nil, fmt.Errorf("unknown runner type: %s", runnerType)
}
}
// NewRunnerFromType creates a runner from type and config directly
func NewRunnerFromType(runnerType core.RunnerType, config *core.RunnerConfig, binaryPath string) (Runner, error) {
if config == nil {
config = &core.RunnerConfig{}
}
switch runnerType {
case core.RunnerTypeHost, "":
return NewHostRunner(binaryPath), nil
case core.RunnerTypeDocker:
return NewDockerRunner(config, binaryPath)
case core.RunnerTypeSSH:
return NewSSHRunner(config, binaryPath)
default:
return nil, fmt.Errorf("unknown runner type: %s", runnerType)
}
}