Posix Provider

This document describes the implementation details of the Posix exec provider for executing commands without a shell.

Provider Selection

The Posix provider is the default exec provider. It is always available and returns priority 1 for all exec resources unless a different provider is explicitly requested via the provider property.

To use the shell provider instead, specify provider: shell in the resource properties.

Comparison with Shell Provider

FeaturePosixShell
Shell invocationNoYes (/bin/sh -c)
Pipes (|)Not supportedSupported
Redirections (>, <)Not supportedSupported
Shell builtins (cd, export)Not supportedSupported
Glob expansionNot supportedSupported
Command substitution ($(...))Not supportedSupported
Argument parsingshellquote.Split()Passed as single string
SecurityLower attack surfaceShell injection possible

When to use Posix (default):

  • Simple commands with arguments
  • When shell features are not needed
  • For better security (no shell injection risk)

When to use Shell:

  • Commands with pipes, redirections, or shell builtins
  • Complex command strings
  • When shell expansion is required

Operations

Execute

Process:

  1. Determine command source (Command property or Name if Command is empty)
  2. Parse command string into words using shellquote.Split()
  3. Extract command (first word) and arguments (remaining words)
  4. Execute via CommandRunner.ExecuteWithOptions()
  5. Optionally log output line-by-line if LogOutput is enabled

Command Parsing:

The command string is parsed using github.com/kballard/go-shellquote, which handles:

SyntaxExampleResult
Simple wordsecho hello world["echo", "hello", "world"]
Single quotesecho 'hello world'["echo", "hello world"]
Double quotesecho "hello world"["echo", "hello world"]
Escaped spacesecho hello\ world["echo", "hello world"]
Mixed quotingecho "it's a test"["echo", "it's a test"]

Execution Options:

OptionSourceDescription
CommandFirst word after parsingExecutable path or name
ArgsRemaining wordsCommand arguments
Cwdproperties.CwdWorking directory
Environmentproperties.EnvironmentAdditional env vars (KEY=VALUE format)
Pathproperties.PathSearch path for executables
Timeoutproperties.ParsedTimeoutMaximum execution time

Output Logging:

When LogOutput: true is set and a user logger is provided:

scanner := bufio.NewScanner(bytes.NewReader(stdout))
for scanner.Scan() {
    log.Info(scanner.Text())
}

Each line of stdout is logged as a separate Info message.

Error Handling:

ConditionBehavior
Empty command stringReturn error: “no command specified”
Invalid shell quotingReturn parsing error (e.g., “Unterminated single quote”)
Runner not configuredReturn error: “no command runner configured”
Command execution failsReturn error from runner
Non-zero exit codeReturn exit code (not an error by itself)

Status

Process:

  1. Create state with EnsurePresent (exec resources are always “present”)
  2. Check if Creates file exists via util.FileExists()
  3. Set CreatesSatisfied accordingly

State Fields:

FieldValue
Protocolio.choria.ccm.v1.resource.exec.state
ResourceTypeexec
NameResource name
Ensurepresent (always)
CreatesSatisfiedtrue if Creates file exists

Idempotency

The exec resource achieves idempotency through multiple mechanisms:

Creates File

If creates is specified and the file exists, the command does not run:

- exec:
    - /usr/bin/tar -xzf app.tar.gz:
        creates: /opt/app/bin/app
        cwd: /opt

RefreshOnly Mode

When refreshonly: true, the command only runs when triggered by a subscribed resource:

- exec:
    - systemctl reload httpd:
        refreshonly: true
        subscribe:
          - file#/etc/httpd/conf/httpd.conf

Exit Code Validation

The returns property specifies acceptable exit codes (default: [0]):

- exec:
    - /opt/app/healthcheck:
        returns: [0, 1, 2]  # 0=healthy, 1=degraded, 2=warning

Decision Flow

┌─────────────────────────────────────────┐
│ Should resource be applied?             │
└─────────────────┬───────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────┐
│ Subscribe triggered?                     │
│ (subscribed resource changed)            │
└─────────────┬───────────────┬───────────┘
              │ Yes           │ No
              ▼               ▼
┌─────────────────┐   ┌───────────────────┐
│ Execute command │   │ Creates satisfied? │
└─────────────────┘   └─────────┬─────────┘
                                │
                      ┌─────────┴─────────┐
                      │ Yes               │ No
                      ▼                   ▼
              ┌───────────────┐   ┌───────────────────┐
              │ Skip (stable) │   │ RefreshOnly mode? │
              └───────────────┘   └─────────┬─────────┘
                                            │
                                  ┌─────────┴─────────┐
                                  │ Yes               │ No
                                  ▼                   ▼
                          ┌───────────────┐   ┌───────────────┐
                          │ Skip (stable) │   │ Execute       │
                          └───────────────┘   └───────────────┘

Properties Validation

The model validates exec properties before execution:

PropertyValidation
nameMust be parseable by shellquote (balanced quotes)
timeoutMust be valid duration format (e.g., 30s, 5m)
subscribeEach entry must be type#name format
pathEach directory must be absolute (start with /)
environmentEach entry must be KEY=VALUE format with non-empty key and value

Platform Support

The Posix provider works on all platforms supported by Go’s os/exec package. It does not use any platform-specific system calls directly.

The command runner (model.CommandRunner) handles the actual process execution, which may have platform-specific implementations.

Security Considerations

No Shell Injection

Unlike the shell provider, the posix provider does not invoke a shell. Arguments are passed directly to the executable, preventing shell injection attacks:

# Safe with posix provider - $USER is passed literally, not expanded
- exec:
    - /bin/echo $USER:
        provider: posix  # Default

# Potentially dangerous with shell provider - $USER is expanded
- exec:
    - /bin/echo $USER:
        provider: shell

Path Validation

The path property only accepts absolute directory paths, preventing path traversal via relative paths.

Environment Validation

Environment variables must have non-empty keys and values, preventing injection of empty or malformed entries.