Exec Type
This document describes the design of the exec resource type for executing commands.
Overview
The exec resource executes commands with idempotency controls:
- Creates: Skip execution if a file exists
- OnlyIf / Unless: Guard commands that gate execution based on exit code
- Refresh Only: Only execute when triggered by a subscribed resource
- Exit Codes: Validate success via configurable return codes
Provider Interface
Exec providers must implement the ExecProvider interface:
Method Responsibilities
| Method | Responsibility |
|---|---|
Status | Check if creates file exists, return current state |
Execute | Run the command, return exit code |
EvaluateGuard | Run a guard command, return true if it exits 0, false if non-zero |
Status Response
The Status method returns an ExecState containing:
The Ensure field in CommonResourceState is set to:
presentif thecreatesfile existsabsentif thecreatesfile does not exist (or not specified)
Available Providers
| Provider | Execution Method | Documentation |
|---|---|---|
posix | Direct exec (no shell) | Posix |
shell | Via /bin/sh -c | Shell |
Properties
| Property | Type | Description |
|---|---|---|
command | string | Command to run (defaults to name if not set) |
cwd | string | Working directory for command execution |
environment | []string | Additional environment variables (KEY=value) |
path | string | Search path for executables (colon-separated) |
returns | []int | Acceptable exit codes (default: [0]) |
timeout | string | Maximum execution time (e.g., 30s, 5m) |
creates | string | File path; skip execution if exists |
onlyif | string | Guard command; exec runs only if it exits 0 |
unless | string | Guard command; exec runs only if it exits non-zero |
refresh_only | bool | Only execute via subscribe refresh |
subscribe | []string | Resources to watch for changes (type#name) |
logoutput | bool | Log command output |
Apply Logic
Idempotency
The exec resource provides idempotency through several mechanisms:
Creates Property
The creates property specifies a file that indicates successful prior execution:
Behavior:
- If
/opt/app/bin/appexists, skip execution - Useful for one-time setup commands
- Provider checks file existence via
Status()
Guard Commands (OnlyIf / Unless)
The onlyif and unless properties specify guard commands that control whether the exec runs:
Behavior:
onlyif: Exec runs only if the guard command exits 0unless: Exec runs only if the guard command exits non-zero- Guard commands are evaluated via
EvaluateGuard(), not insideStatus() - Guards share the exec’s
cwd,environment,path, andtimeout - Guards run even in noop mode to accurately report what would happen
createstakes precedence: if the creates file exists, guards are not checked- Subscribe-triggered refreshes override guards
Error handling:
- A non-zero exit code from a guard is not an error; it simply means the condition is not met
- An actual execution failure (command not found, permission denied) is propagated as an error
Refresh Only Property
The refresh_only property limits execution to subscribe refreshes:
Behavior:
- Command only runs when subscribed resource changes
- Without a subscribe trigger, command is skipped
- Useful for reload/restart commands
Decision Table
| Condition | Action |
|---|---|
| Subscribe triggered | Execute |
creates file exists | Skip |
onlyif guard exits non-zero | Skip |
unless guard exits 0 | Skip |
refresh_only: true + no trigger | Skip |
refresh_only: false + no guards | Execute |
Subscribe Behavior
Exec resources can subscribe to other resources and execute when they change:
Subscribe takes precedence over all other idempotency checks - if a subscribed resource changed, the command executes regardless of creates file existence or guard command results.
Exit Code Validation
By default, exit code 0 indicates success. The returns property customizes acceptable codes:
Behavior:
- Command succeeds if exit code is in
returnslist - Command fails if exit code is not in
returnslist - Used for desired state validation after execution
Noop Mode
In noop mode, the exec type:
- Queries current state normally (checks
createsfile) - Evaluates guard commands (
onlyif/unless) - these run even in noop mode - Evaluates subscribe triggers
- Logs what actions would be taken
- Sets appropriate
NoopMessage:- “Would have executed”
- “Would have executed via subscribe”
- Reports
Changed: trueif execution would occur - Does not call provider
Executemethod
Desired State Validation
After execution (in non-noop mode), the type verifies success:
Guard checks are gated on ExitCode == nil because after execution, the exit code determines success. The post-execution isDesiredState() call must not re-evaluate guards, which would produce incorrect results since guard state is only set on initialStatus.
If the exit code is not in the acceptable returns list, an ErrDesiredStateFailed error is returned.
Command vs Name
The command property is optional. If not specified, the name is used as the command:
Using a descriptive name with explicit command is recommended for clarity.
Environment and Path
Commands can be configured with custom environment:
Environment:
- Added to the command’s environment
- Format:
KEY=value - Does not replace existing environment
Path:
- Sets the
PATHfor executable lookup - Must be absolute directories
- Colon-separated list