exec

package
v1.21.23 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 15, 2026 License: Apache-2.0 Imports: 25 Imported by: 10

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ContainsShellOperators

func ContainsShellOperators(cmd string) bool

ContainsShellOperators checks if a command contains shell-specific operators that require wrapping in a shell (bash -c or sh -c)

func ListeningPorts added in v1.21.23

func ListeningPorts(pgid int) ([]int, error)

ListeningPorts returns the distinct TCP ports the process group led by pgid is listening on, in ascending order. It shells out to lsof, which is present on macOS and most Linux dev/CI images. WithProcessGroup() puts each supervised process in its own group whose id equals the leader pid, so passing the leader pid as pgid captures listeners opened by the process and any child it forked (e.g. `npm run dev` → node).

Detection is advisory: when lsof is not on PATH the result is (nil, nil) so callers degrade to "no ports detected" (and Windows, which has no lsof, is a no-op). lsof exits non-zero when nothing matches ("not listening yet") — that is the normal empty case, so whatever it printed is parsed instead of erroring. Only a failure to execute lsof at all is returned as an error.

func NewDebugLogger

func NewDebugLogger(log logger.Logger, level logger.LogLevel) logger.Logger

Types

type ExecLogger

type ExecLogger struct {
	Stderr io.Writer
	Stdout io.Writer
	// contains filtered or unexported fields
}

ExecLogger buffers a subprocess's stdout/stderr. The subprocess writes the buffers (via the writers returned by GetStdoutWriter/GetStderrWriter) on the os/exec copy goroutines while callers read them via GetStdout/GetStderr — so every buffer access is guarded by mu. bytes.Buffer is not safe for concurrent read/write on its own.

func NewExecLogger

func NewExecLogger() *ExecLogger

func (*ExecLogger) GetOutput

func (e *ExecLogger) GetOutput() string

func (*ExecLogger) GetStderr

func (e *ExecLogger) GetStderr() string

func (*ExecLogger) GetStderrWriter

func (l *ExecLogger) GetStderrWriter() (writer io.Writer)

func (*ExecLogger) GetStdout

func (e *ExecLogger) GetStdout() string

func (*ExecLogger) GetStdoutWriter

func (l *ExecLogger) GetStdoutWriter() (writer io.Writer)

func (*ExecLogger) Reset

func (l *ExecLogger) Reset()

func (*ExecLogger) Tee

func (l *ExecLogger) Tee(stdout, stderr io.Writer) *ExecLogger

WithTee returns a new ExecLogger that tees logs to stdout/stderr as well.

type ExecResult

type ExecResult struct {
	Stdout   string        `json:"stdout,omitempty"`
	Stderr   string        `json:"stderr,omitempty"`
	Status   string        `json:"status,omitempty"`
	ExitCode int           `json:"exit_code,omitempty"`
	Started  *time.Time    `json:"started,omitempty"`
	Duration time.Duration `json:"duration,omitempty"`
	PID      int           `json:"pid,omitempty"`
	Command  string        `json:"command,omitempty"`
	Args     []string      `json:"args,omitempty"`
	Error    error         `json:"error,omitempty"`
	// contains filtered or unexported fields
}

ExecResult contains the result of a command execution with structured output and metadata.

func (ExecResult) IsCompleted

func (r ExecResult) IsCompleted() bool

func (ExecResult) IsOk

func (r ExecResult) IsOk() bool

func (ExecResult) IsPending

func (r ExecResult) IsPending() bool

func (ExecResult) Output

func (r ExecResult) Output() string

func (ExecResult) Pretty

func (r ExecResult) Pretty() api.Text

func (ExecResult) PrettyFull

func (r ExecResult) PrettyFull() api.Textable

func (*ExecResult) Refresh

func (e *ExecResult) Refresh() *ExecResult

Refresh refreshes the ExecResult by re-fetching data from the underlying Process

type Process

type Process struct {
	Started *time.Time

	Timeout time.Duration
	Env     map[string]string
	Cwd     string
	Err     error
	Shell   string
	Cmd     string
	Args    []string
	// Consider a non-zero exit code as an error
	SucceedOnNonZero bool
	// contains filtered or unexported fields
}

func NewExec

func NewExec(cmd string, args ...string) *Process

NewExec creates a new Process with the specified command and arguments

func NewExecf

func NewExecf(cmd string, args ...any) *Process

NewExecf creates a new Process with formatted command string

func (*Process) AsWrapper

func (p *Process) AsWrapper() WrapperFunc

AsWrapper converts the Process into a reusable WrapperFunc that executes commands with the template's configuration. Each invocation creates a new Process instance by copying the template's settings.

Example:

docker := clicky.Exec("docker", "-v").AsWrapper()
result, err := docker("ps", "-a")
if err != nil {
    log.Fatal(err)
}
fmt.Println(result.Stdout)

func (*Process) Debug

func (p *Process) Debug() *Process

func (*Process) ExitCode

func (p *Process) ExitCode() int

func (*Process) ForceKill

func (p *Process) ForceKill() error

func (*Process) GetOutput

func (p *Process) GetOutput() string

func (*Process) GetStderr

func (p *Process) GetStderr() string

func (*Process) GetStdout

func (p *Process) GetStdout() string

func (*Process) GetTask

func (p *Process) GetTask() *task.Task

GetTask implements the Taskable interface

func (*Process) IsOK

func (p *Process) IsOK() bool

func (*Process) IsRunning

func (p *Process) IsRunning() bool

func (*Process) Kill

func (p *Process) Kill(timeout time.Duration) error

func (*Process) KillTree

func (p *Process) KillTree() error

KillTree terminates the subprocess and every descendant. Prefer this over Kill/ForceKill for processes that fork or exec grandchildren (test runners, headless browsers, etc). When WithProcessGroup was set at spawn, the kill is atomic (POSIX: SIGKILL to -pgid; Windows: TerminateJobObject); otherwise KillTree falls back to a gopsutil-style descendant walk, which is racy.

func (*Process) MustStop

func (p *Process) MustStop(timeout time.Duration) error

MustStop attempts to gracefully stop a process, after which it is forcefully killed

func (*Process) Name

func (p *Process) Name() string

func (*Process) Out

func (p *Process) Out() string

func (*Process) Pid

func (p *Process) Pid() int

Pid returns the OS pid of the running subprocess, or 0 if it hasn't started yet. Safe to call concurrently with Run().

func (*Process) Pretty

func (p *Process) Pretty() api.Text

func (*Process) Result

func (p *Process) Result() *ExecResult

func (*Process) Run

func (p *Process) Run() *Process

func (*Process) RunAsTask

func (p *Process) RunAsTask(name string, opts ...task.Option) task.TypedTask[ExecResult]

StartAsTask creates and starts a Task for this Process with typed result handling

func (*Process) Short

func (p *Process) Short() api.Text

func (*Process) Start

func (p *Process) Start() error

Start runs the process in the background

func (*Process) StartAsTask

func (p *Process) StartAsTask(name string, opts ...task.Option) task.TypedTask[*Process]

StartAsTask creates and starts a Task for this Process with typed result handling

func (*Process) Stop

func (p *Process) Stop() error

func (*Process) Stream

func (p *Process) Stream(stdout, stderr io.Writer) *Process

func (*Process) Supervise added in v1.21.23

func (p *Process) Supervise(opts SuperviseOptions) *SupervisedProcess

Supervise turns a configured Process into a SupervisedProcess using it as the template re-run on each (re)start. Call Start to begin supervising.

func (*Process) Terminate

func (p *Process) Terminate() error

func (*Process) Wait

func (p *Process) Wait() error

func (*Process) WaitForStdout

func (p *Process) WaitForStdout(message string, timeout time.Duration) error

WaitForStdout waits for a specific message to appear in the process stdout This is useful for waiting for server startup messages like "Server started on port"

func (*Process) WithCwd

func (p *Process) WithCwd(cwd string) *Process

func (*Process) WithEnv

func (p *Process) WithEnv(env map[string]string) *Process

func (*Process) WithProcessGroup

func (p *Process) WithProcessGroup() *Process

WithProcessGroup spawns the subprocess in its own process group so the entire descendant tree can be terminated atomically via KillTree. Must be called before Run/Start. On POSIX this sets Setpgid=true; on Windows it sets CREATE_NEW_PROCESS_GROUP.

func (*Process) WithShell

func (p *Process) WithShell(shell string) *Process

func (*Process) WithTask

func (p *Process) WithTask(t *task.Task) *Process

func (*Process) WithTimeout

func (p *Process) WithTimeout(timeout time.Duration) *Process

func (*Process) WithoutShell

func (p *Process) WithoutShell() *Process

type ProcessSample added in v1.21.23

type ProcessSample struct {
	PID        int32   `json:"pid"`
	PPID       int32   `json:"ppid"`
	Command    string  `json:"command"`
	CPUPercent float64 `json:"cpuPercent"`
	RSSBytes   uint64  `json:"rssBytes"`
	OpenFiles  int     `json:"openFiles"`
}

ProcessSample is the resource usage of one process in the supervised group, the per-process breakdown behind the aggregate ResourceSnapshot. OpenFiles is -1 where the platform cannot report it.

type ResourceLimits added in v1.21.23

type ResourceLimits struct {
	// MaxRSSBytes kills the process the first sample its tree RSS exceeds this.
	MaxRSSBytes uint64
	// MaxCPUPercent kills the process after CPUSampleCount consecutive samples
	// above this aggregate CPU percentage (transient spikes are tolerated).
	MaxCPUPercent float64
	// CPUSampleCount is the consecutive over-limit CPU samples tolerated before
	// a kill. Defaults to defaultCPUSampleCount.
	CPUSampleCount int
	// Interval is the sampling cadence. Defaults to defaultSampleInterval.
	Interval time.Duration
}

ResourceLimits optionally bounds a supervised process. A zero value in any limit field disables that limit. When a limit is exceeded the whole process tree is killed (KillTree) and SupervisedProcess.Killed reports true.

type ResourceSnapshot added in v1.21.23

type ResourceSnapshot struct {
	CPUPercent float64   `json:"cpuPercent"`
	RSSBytes   uint64    `json:"rssBytes"`
	OpenFiles  int       `json:"openFiles"`
	SampledAt  time.Time `json:"sampledAt"`
}

ResourceSnapshot is a point-in-time measurement of a supervised process and its descendant tree. OpenFiles is -1 on platforms that cannot report it.

type RestartPolicy added in v1.21.23

type RestartPolicy string

RestartPolicy controls whether a supervised process is restarted after it exits.

const (
	RestartNo        RestartPolicy = "no"         // never restart
	RestartOnFailure RestartPolicy = "on-failure" // restart only on a non-zero exit
	RestartAlways    RestartPolicy = "always"     // restart on any exit
)

type Status added in v1.21.23

type Status string

Status is the lifecycle state of a supervised process.

const (
	StatusStopped    Status = "stopped"
	StatusStarting   Status = "starting"
	StatusRunning    Status = "running"
	StatusRestarting Status = "restarting"
	StatusCrashed    Status = "crashed"
	StatusExited     Status = "exited"
)

type SuperviseOptions added in v1.21.23

type SuperviseOptions struct {
	// Limits bounds resource usage; zero values disable individual limits.
	Limits ResourceLimits
	// RestartPolicy controls restart-on-exit; defaults to RestartNo.
	RestartPolicy RestartPolicy
	// MaxRestarts caps automatic restarts (0 = unlimited).
	MaxRestarts int
	// StopGrace is the SIGTERM→SIGKILL window on Stop; defaults to 5s.
	StopGrace time.Duration
	// DetectPorts runs the lsof port-watch loop after each start.
	DetectPorts bool
	// OnStart, if set, is called before each (re)start (e.g. to write a log header).
	OnStart func()
	// OnExit, if set, is called once when the supervise loop ends permanently
	// (the process exited and will not be restarted, or it was stopped).
	OnExit func()
}

SuperviseOptions configures the supervision loop.

type SupervisedProcess added in v1.21.23

type SupervisedProcess struct {
	// contains filtered or unexported fields
}

SupervisedProcess supervises a single process: it (re)runs a template command per its RestartPolicy, detects listening ports, samples CPU/memory/open-files of the whole process group, and enforces resource limits. It is created from a configured Process via (*Process).Supervise and driven with Start/Stop/Restart.

func Supervise added in v1.21.23

func Supervise(opts SuperviseOptions, cmd string, args ...string) *SupervisedProcess

Supervise builds a supervised process from a command, mirroring NewExec.

func (*SupervisedProcess) ExitCode added in v1.21.23

func (s *SupervisedProcess) ExitCode() *int

ExitCode returns the last run's exit code, or nil if it hasn't exited yet.

func (*SupervisedProcess) IsRunning added in v1.21.23

func (s *SupervisedProcess) IsRunning() bool

IsRunning reports whether a run is currently executing.

func (*SupervisedProcess) KillTree added in v1.21.23

func (s *SupervisedProcess) KillTree() error

KillTree force-kills the current run's process tree (no-op between runs).

func (*SupervisedProcess) Killed added in v1.21.23

func (s *SupervisedProcess) Killed() bool

Killed reports whether the process was killed for breaching a resource limit.

func (*SupervisedProcess) Name added in v1.21.23

func (s *SupervisedProcess) Name() string

Name returns the template command's display name.

func (*SupervisedProcess) Peak added in v1.21.23

Peak returns the high-water mark across all samples.

func (*SupervisedProcess) Pid added in v1.21.23

func (s *SupervisedProcess) Pid() int

Pid returns the current run's pid, or 0 between runs.

func (*SupervisedProcess) Ports added in v1.21.23

func (s *SupervisedProcess) Ports() []int

Ports returns the listening ports detected for the current run (empty if none or port detection is disabled).

func (*SupervisedProcess) Resources added in v1.21.23

func (s *SupervisedProcess) Resources() ResourceSnapshot

Resources returns the most recent aggregate resource sample.

func (*SupervisedProcess) Restart added in v1.21.23

func (s *SupervisedProcess) Restart()

Restart restarts the process now, resetting the restart counter. If no loop is active it simply starts one.

func (*SupervisedProcess) Restarts added in v1.21.23

func (s *SupervisedProcess) Restarts() int

Restarts returns the number of automatic restarts in the current run streak.

func (*SupervisedProcess) Start added in v1.21.23

func (s *SupervisedProcess) Start()

Start begins (or resumes) supervising in the background. It is idempotent while a supervise loop is active; calling it again after the loop has ended (Stop, or a permanent exit) starts a fresh loop.

func (*SupervisedProcess) Started added in v1.21.23

func (s *SupervisedProcess) Started() *time.Time

Started returns when the current run started, or nil if not running.

func (*SupervisedProcess) Status added in v1.21.23

func (s *SupervisedProcess) Status() Status

Status returns the current lifecycle state.

func (*SupervisedProcess) Stop added in v1.21.23

func (s *SupervisedProcess) Stop()

Stop gracefully stops the process and prevents further restarts, blocking until the supervise loop has ended (or the stop grace elapsed and it was force-killed). Safe to call when already stopped.

func (*SupervisedProcess) Tree added in v1.21.23

func (s *SupervisedProcess) Tree() []ProcessSample

Tree returns the most recent per-process breakdown of the supervised group.

func (*SupervisedProcess) Wait added in v1.21.23

func (s *SupervisedProcess) Wait()

Wait blocks until the current supervise loop has ended.

type WrapperFunc

type WrapperFunc func(...any) (*ExecResult, error)

WrapperFunc is a function type returned by AsWrapper that executes commands with pre-configured settings from a template Process.

type WrapperOption

type WrapperOption interface {
	// contains filtered or unexported methods
}

WrapperOption is a functional option that modifies a Process for a single execution.

func WithContext

func WithContext(ctx context.Context) WrapperOption

WithContext returns a WrapperOption that sets a context for cancellation/deadline. Note: Currently not fully implemented for context-based cancellation.

func WithDebug

func WithDebug() WrapperOption

func WithDir

func WithDir(path string) WrapperOption

WithDir returns a WrapperOption that overrides the working directory.

func WithEnv

func WithEnv(key, value string) WrapperOption

WithEnv returns a WrapperOption that adds or overrides an environment variable.

func WithLogger

func WithLogger(log logger.Logger) WrapperOption

func WithTee

func WithTee(stdout, stderr io.Writer) WrapperOption

func WithTimeout

func WithTimeout(timeout time.Duration) WrapperOption

WithTimeout returns a WrapperOption that overrides the execution timeout.

func WithoutErrorOnNonZero

func WithoutErrorOnNonZero() WrapperOption

WithoutErrorOnNonZero returns a WrapperOption that makes non-zero exit codes succeed

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL