Documentation
¶
Index ¶
- func ContainsShellOperators(cmd string) bool
- func ListeningPorts(pgid int) ([]int, error)
- func NewDebugLogger(log logger.Logger, level logger.LogLevel) logger.Logger
- type ExecLogger
- func (e *ExecLogger) GetOutput() string
- func (e *ExecLogger) GetStderr() string
- func (l *ExecLogger) GetStderrWriter() (writer io.Writer)
- func (e *ExecLogger) GetStdout() string
- func (l *ExecLogger) GetStdoutWriter() (writer io.Writer)
- func (l *ExecLogger) Reset()
- func (l *ExecLogger) Tee(stdout, stderr io.Writer) *ExecLogger
- type ExecResult
- type Process
- func (p *Process) AsWrapper() WrapperFunc
- func (p *Process) Debug() *Process
- func (p *Process) ExitCode() int
- func (p *Process) ForceKill() error
- func (p *Process) GetOutput() string
- func (p *Process) GetStderr() string
- func (p *Process) GetStdout() string
- func (p *Process) GetTask() *task.Task
- func (p *Process) IsOK() bool
- func (p *Process) IsRunning() bool
- func (p *Process) Kill(timeout time.Duration) error
- func (p *Process) KillTree() error
- func (p *Process) MustStop(timeout time.Duration) error
- func (p *Process) Name() string
- func (p *Process) Out() string
- func (p *Process) Pid() int
- func (p *Process) Pretty() api.Text
- func (p *Process) Result() *ExecResult
- func (p *Process) Run() *Process
- func (p *Process) RunAsTask(name string, opts ...task.Option) task.TypedTask[ExecResult]
- func (p *Process) Short() api.Text
- func (p *Process) Start() error
- func (p *Process) StartAsTask(name string, opts ...task.Option) task.TypedTask[*Process]
- func (p *Process) Stop() error
- func (p *Process) Stream(stdout, stderr io.Writer) *Process
- func (p *Process) Supervise(opts SuperviseOptions) *SupervisedProcess
- func (p *Process) Terminate() error
- func (p *Process) Wait() error
- func (p *Process) WaitForStdout(message string, timeout time.Duration) error
- func (p *Process) WithCwd(cwd string) *Process
- func (p *Process) WithEnv(env map[string]string) *Process
- func (p *Process) WithProcessGroup() *Process
- func (p *Process) WithShell(shell string) *Process
- func (p *Process) WithTask(t *task.Task) *Process
- func (p *Process) WithTimeout(timeout time.Duration) *Process
- func (p *Process) WithoutShell() *Process
- type ProcessSample
- type ResourceLimits
- type ResourceSnapshot
- type RestartPolicy
- type Status
- type SuperviseOptions
- type SupervisedProcess
- func (s *SupervisedProcess) ExitCode() *int
- func (s *SupervisedProcess) IsRunning() bool
- func (s *SupervisedProcess) KillTree() error
- func (s *SupervisedProcess) Killed() bool
- func (s *SupervisedProcess) Name() string
- func (s *SupervisedProcess) Peak() ResourceSnapshot
- func (s *SupervisedProcess) Pid() int
- func (s *SupervisedProcess) Ports() []int
- func (s *SupervisedProcess) Resources() ResourceSnapshot
- func (s *SupervisedProcess) Restart()
- func (s *SupervisedProcess) Restarts() int
- func (s *SupervisedProcess) Start()
- func (s *SupervisedProcess) Started() *time.Time
- func (s *SupervisedProcess) Status() Status
- func (s *SupervisedProcess) Stop()
- func (s *SupervisedProcess) Tree() []ProcessSample
- func (s *SupervisedProcess) Wait()
- type WrapperFunc
- type WrapperOption
- func WithContext(ctx context.Context) WrapperOption
- func WithDebug() WrapperOption
- func WithDir(path string) WrapperOption
- func WithEnv(key, value string) WrapperOption
- func WithLogger(log logger.Logger) WrapperOption
- func WithTee(stdout, stderr io.Writer) WrapperOption
- func WithTimeout(timeout time.Duration) WrapperOption
- func WithoutErrorOnNonZero() WrapperOption
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ContainsShellOperators ¶
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
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.
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 (*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) KillTree ¶
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 ¶
MustStop attempts to gracefully stop a process, after which it is forcefully killed
func (*Process) Pid ¶
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) Result ¶
func (p *Process) Result() *ExecResult
func (*Process) RunAsTask ¶
StartAsTask creates and starts a Task for this Process with typed result handling
func (*Process) StartAsTask ¶
StartAsTask creates and starts a Task for this Process with typed result handling
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) WaitForStdout ¶
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) WithProcessGroup ¶
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) WithoutShell ¶
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.
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
func (s *SupervisedProcess) Peak() ResourceSnapshot
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