Documentation
¶
Overview ¶
Package hooks lets users attach commands to the lifecycle events of a Locorum site (start, stop, delete, clone, version-change, multisite, export). Each hook is one of three task types:
- exec: runs in a per-site Docker container (default: php).
- exec-host: runs in a host shell (bash on Unix/WSL, cmd on Windows).
- wp-cli: convenience wrapper over `wp …` in the php container.
The runner is GUI-agnostic: it streams output through callbacks and writes a complete on-disk log per Run. The SiteManager is responsible for firing pre/post hooks at every lifecycle method; the UI is responsible for rendering output and presenting the editor.
Concurrency: hooks within a single event run sequentially in position order. Different events on different sites may run concurrently; the SiteManager owns a per-site mutex so two events on the same site do not interleave.
Index ¶
- Constants
- Variables
- func BuildEnv(site *types.Site, ctx EnvContext) []string
- func SweepLogs(baseDir string, maxAge time.Duration, maxPerSite int) error
- type Config
- type ContainerExecOptions
- type ContainerExecer
- type DockerContainerExecer
- type EnvContext
- type Event
- type Hook
- type HookLister
- type HostExecOptions
- type HostExecer
- type Result
- type RunOptions
- type Runner
- type SettingsReader
- type Summary
- type TaskType
- type Template
- type UtilsHostExecer
Constants ¶
const ( // DefaultMaxLogsPerSite — applied per (slug, event) family. DefaultMaxLogsPerSite = 50 // DefaultLogMaxAge — applied across the entire LogsBaseDir. DefaultLogMaxAge = 30 * 24 * time.Hour )
const ( SettingKeyFailGlobal = "hooks.fail_on_error.global" SettingKeyFailPrefix = "hooks.fail_on_error." )
SettingKeyFailGlobal is the storage key for the global "fail on error" toggle. Per-site keys are SettingKeyFailPrefix+<siteID>.
const DefaultTaskTimeout = 5 * time.Minute
DefaultTaskTimeout is the per-task wall-clock deadline. Hooks should not run for longer than this; the runner cancels the task's context when it elapses. v1 uses a global default; the schema reserves room for a per-hook column.
const DefaultsPath = "config/hooks/defaults.json"
DefaultsPath is the embedded path used by Locorum at startup.
const HostDBPort = "3306"
HostDBPort is the host port published by the site database container in host-context tasks. Locorum currently does not publish per-site DB ports to the host; users typically connect via Adminer at db.localhost. We expose the in-container port (3306) here as a sensible default. If/when the platform starts publishing per-site DB ports, swap this for a lookup against the docker port mapping.
Variables ¶
var ( // ErrHookInvalid is the umbrella sentinel for every Validate failure. ErrHookInvalid = errors.New("hook is invalid") // ErrEmptyCommand is returned by Validate when Command is blank. ErrEmptyCommand = errors.New("hook command is empty") // ErrSkipped is returned by the runner when LOCORUM_SKIP_HOOKS is set. // Run() returns nil but the Summary is empty. Useful for tests to detect // the skip behaviour explicitly. ErrSkipped = errors.New("hooks skipped: LOCORUM_SKIP_HOOKS=1") )
Sentinel errors. Use errors.Is to test.
Functions ¶
func BuildEnv ¶
func BuildEnv(site *types.Site, ctx EnvContext) []string
BuildEnv returns the complete LOCORUM_* environment variable list to inject into a hook task. The result is a fresh slice; the caller is free to append to it.
Variables are sorted by name so the output is reproducible across calls (helpful for diffs in tests and reasoning about ordering precedence).
The returned slice is "KEY=VALUE" formatted, ready to pass to either docker.ExecOptions.Env or utils.HostExecOptions.Env.
Types ¶
type Config ¶
type Config struct {
Lister HookLister
Container ContainerExecer
Host HostExecer
Settings SettingsReader
LogsBaseDir string // typically ~/.locorum/hooks/runs/
// SkipEnvVar is the environment variable name that, if set to "1",
// short-circuits Run() with ErrSkipped. Defaults to LOCORUM_SKIP_HOOKS.
SkipEnvVar string
// MaxLogsPerSite caps the number of run-log files retained per site
// after the startup sweep. Older files are removed.
MaxLogsPerSite int
// LogMaxAge caps the age of retained log files. Older files are removed.
LogMaxAge time.Duration
}
Config wires the runner's external dependencies. Every field is required in production; tests inject fakes.
type ContainerExecOptions ¶
ContainerExecOptions mirrors docker.ExecOptions but is decoupled so the hooks package does not need to import docker. The runner-side glue layer translates between the two.
type ContainerExecer ¶
type ContainerExecer interface {
ExecInContainerStream(ctx context.Context, containerName string, opts ContainerExecOptions, onLine func(string, bool)) (int, error)
}
ContainerExecer is the narrow port the runner uses to run an exec task inside a Docker container. The production implementation is docker.Docker.ExecInContainerStream; tests inject a fake.
type DockerContainerExecer ¶
DockerContainerExecer wraps *docker.Docker to satisfy ContainerExecer.
The narrow ContainerExecer interface lets the runner stay decoupled from the docker package; this adapter is the only place where the two types meet. Move it here (rather than into internal/docker) so the docker package never imports hooks.
func (DockerContainerExecer) ExecInContainerStream ¶
func (a DockerContainerExecer) ExecInContainerStream(ctx context.Context, container string, opts ContainerExecOptions, onLine func(string, bool)) (int, error)
ExecInContainerStream implements ContainerExecer.
type EnvContext ¶
type EnvContext int
EnvContext selects which network perspective the env vars should describe. Container-context vars resolve database addresses to the in-network alias (database:3306); host-context vars resolve them to 127.0.0.1 plus the published port.
const ( // ContextContainer is for tasks that run inside one of the site's // containers (exec, wp-cli). ContextContainer EnvContext = iota // ContextHost is for tasks that run on the host shell (exec-host). ContextHost )
func (EnvContext) String ¶
func (c EnvContext) String() string
String returns a stable label suitable for diagnostics.
type Event ¶
type Event string
Event identifies a lifecycle moment at which hooks fire. Events are paired (pre-X / post-X) but each is independent; pre-X failure does not prevent post-X from running on a different invocation.
const ( PreStart Event = "pre-start" PostStart Event = "post-start" PreStop Event = "pre-stop" PostStop Event = "post-stop" PreDelete Event = "pre-delete" PostDelete Event = "post-delete" PreClone Event = "pre-clone" PostClone Event = "post-clone" PreVersionsChange Event = "pre-versions-change" PostVersionsChange Event = "post-versions-change" PreMultisite Event = "pre-multisite" PostMultisite Event = "post-multisite" PreExport Event = "pre-export" PostExport Event = "post-export" PreImportDB Event = "pre-import-db" PostImportDB Event = "post-import-db" PreSnapshot Event = "pre-snapshot" PostSnapshot Event = "post-snapshot" )
Active events — fire today.
const ( PreImportFiles Event = "pre-import-files" PostImportFiles Event = "post-import-files" PreRestoreSnapshot Event = "pre-restore-snapshot" PostRestoreSnapshot Event = "post-restore-snapshot" PreImportSite Event = "pre-import-site" PostImportSite Event = "post-import-site" )
Reserved events — declared but not yet fired by any lifecycle method. Adding the firing site is a one-line runner.Run(...) addition once the underlying feature lands.
func ActiveEvents ¶
func ActiveEvents() []Event
ActiveEvents returns the events currently fired by the SiteManager.
func AllEvents ¶
func AllEvents() []Event
AllEvents returns every recognised event, including reserved ones. The slice is a copy so callers may sort/filter freely.
func SortedActiveEvents ¶
func SortedActiveEvents() []Event
SortedActiveEvents is ActiveEvents() sorted lexically — convenient when the UI wants a stable display order.
func (Event) AllowsContainerTasks ¶
AllowsContainerTasks reports whether the event fires while the site's containers are running (and so exec / wp-cli tasks are sensible).
The "pre-X" events for lifecycle methods that bring containers up are rejected: the containers don't exist yet. Similarly, post-stop and pre/post-delete fire when containers are down or being torn down.
type Hook ¶
type Hook struct {
ID int64 `json:"id"`
SiteID string `json:"siteId"`
Event Event `json:"event"`
Position int `json:"position"`
TaskType TaskType `json:"taskType"`
Command string `json:"command"`
Service string `json:"service"` // exec only: web|php|database|redis
RunAsUser string `json:"runAsUser"` // exec only: e.g. "root" or "1000:1000"
Enabled bool `json:"enabled"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
Hook is a user-defined command attached to a lifecycle Event.
Hooks are persisted per-site in the site_hooks table; ID is the SQLite row id. Position controls execution order within an event (lower runs first; storage assigns the next free position on insert).
type HookLister ¶
HookLister loads the persisted hooks for a (site, event). The runner does not write to storage; that is the GUI's job.
type HostExecOptions ¶
HostExecOptions mirrors utils.HostExecOptions for the same reason.
type HostExecer ¶
type HostExecer interface {
RunHostStream(ctx context.Context, opts HostExecOptions, onLine func(string, bool)) (int, error)
}
HostExecer is the narrow port the runner uses to run an exec-host task on the host shell. The production implementation is utils.RunHostStream wrapped to match this signature.
type Result ¶
type Result struct {
Hook Hook
StartedAt time.Time
FinishedAt time.Time
ExitCode int
Err error
// Stderr is true if stderr lines were observed.
StderrSeen bool
// LinesEmitted is the total number of stdout+stderr lines streamed.
LinesEmitted int
// LogPath is the absolute path of the per-event log file the runner
// writes during the Run.
LogPath string
}
Result describes the outcome of a single task execution.
type RunOptions ¶
type RunOptions struct {
OnTaskStart func(Hook)
OnOutput func(line string, stderr bool)
OnTaskDone func(Result)
OnAllDone func(Summary)
}
RunOptions bundles the streaming callbacks. Every field is optional; nil callbacks are no-ops. Callbacks fire on the runner's goroutine — if a callback blocks, the runner blocks. Implementations should hand work off to channels or goroutines if they need to do more than copy a string.
type Runner ¶
type Runner interface {
// Run executes every enabled hook for ev attached to site, in position
// order. Returns the first task error if fail-strict mode is on AND a
// task fails; otherwise nil. Runner returns nil if no hooks are
// configured or LOCORUM_SKIP_HOOKS is set.
Run(ctx context.Context, ev Event, site *types.Site, opts RunOptions) error
// RunOne executes a single in-memory hook (not necessarily persisted).
// Used by the "Run now" GUI button and the editor's "Test" button.
// Always returns the task's error verbatim; fail-strict semantics do
// not apply to a single task.
RunOne(ctx context.Context, h Hook, site *types.Site, opts RunOptions) (Result, error)
}
Runner executes hooks for a lifecycle Event. One Runner per process.
type SettingsReader ¶
SettingsReader reads named user settings. Used to look up the per-site / global "fail on hook error" toggle.
type Summary ¶
type Summary struct {
Event Event
SiteID string
Total int
Succeeded int
Failed int
Skipped int
Aborted bool // true if fail-strict caused early termination
Duration time.Duration
LogPath string
}
Summary aggregates per-Run statistics. Emitted via RunOptions.OnAllDone.
type TaskType ¶
type TaskType string
TaskType identifies how a Hook's command is dispatched.
const ( // TaskExec runs the command inside one of the site's running containers // (default: php). Use Hook.Service to target web/database/redis instead. TaskExec TaskType = "exec" // TaskExecHost runs the command on the host shell. Cwd defaults to the // site's FilesDir. TaskExecHost TaskType = "exec-host" // TaskWPCLI runs `wp <command>` inside the php container. Equivalent to // TaskExec with `wp ` prepended; offered as a separate type so the GUI // can validate and document the wp-cli use case. TaskWPCLI TaskType = "wp-cli" )
func AllTaskTypes ¶
func AllTaskTypes() []TaskType
AllTaskTypes returns the canonical, ordered list of task types.
type Template ¶
type Template struct {
Name string `json:"name"`
Description string `json:"description"`
Event Event `json:"event"`
TaskType TaskType `json:"taskType"`
Command string `json:"command"`
Service string `json:"service,omitempty"`
}
Template is a one-click hook preset surfaced in the UI's "Templates" menu. Templates are inserts (not replacements) — clicking one appends a new hook to the user's site at the appropriate event.
func LoadTemplates ¶
LoadTemplates reads and validates the embedded defaults.json from the supplied filesystem at path. Pass DefaultsPath for production code; tests use a stub path under testdata/.
Validation: each template's TaskType + Event combination must satisfy Hook.Validate. Invalid templates return an error so a packaging mistake is caught at startup, not surfaced as a confusing GUI error later.
type UtilsHostExecer ¶
type UtilsHostExecer struct{}
UtilsHostExecer wraps utils.RunHostStream so it satisfies HostExecer.
func (UtilsHostExecer) RunHostStream ¶
func (UtilsHostExecer) RunHostStream(ctx context.Context, opts HostExecOptions, onLine func(string, bool)) (int, error)
RunHostStream implements HostExecer.