Expression Syntax Reference¶
Mooncake uses expr-lang for expressions in conditionals (when), custom change detection (changed_when), and custom failure detection (failed_when).
Quick Start
Expressions are string values evaluated at runtime:
Operators¶
Comparison Operators¶
| Operator | Description | Example |
|---|---|---|
== |
Equal | os == "linux" |
!= |
Not equal | arch != "arm64" |
> |
Greater than | cpu_cores > 4 |
>= |
Greater or equal | memory_total_mb >= 8192 |
< |
Less than | disk_free_percent < 20 |
<= |
Less or equal | cpu_load <= 2.0 |
Examples:
- name: Install on Linux
shell: apt install -y neovim
when: os == "linux"
- name: Check high CPU systems
print: "High-performance system detected"
when: cpu_cores >= 16
Logical Operators¶
| Operator | Description | Example |
|---|---|---|
&& |
Logical AND | os == "linux" && arch == "amd64" |
\|\| |
Logical OR | os == "darwin" \|\| os == "linux" |
! |
Logical NOT | !is_docker_installed |
Examples:
- name: Install on Ubuntu/Debian
shell: apt install -y docker.io
become: true
when: os == "linux" && (distribution == "ubuntu" || distribution == "debian")
- name: Warn if not root
print: "Warning: May need sudo"
when: !(user == "root")
String Operators¶
| Operator | Description | Example |
|---|---|---|
in |
Contains substring | "error" in result.stderr |
not in |
Does not contain | "success" not in output |
matches |
Regex match | version matches "^v?[0-9]+" |
contains |
Same as in |
stderr contains "warning" |
startsWith |
Prefix check | path startsWith "/usr" |
endsWith |
Suffix check | file endsWith ".yml" |
+ |
Concatenation | "/usr/bin/" + app_name |
Examples:
- name: Check for errors
shell: ./run-tests.sh
register: test_result
failed_when: "'FAILED' in test_result.stdout"
- name: Detect version format
print: "Valid version detected"
when: app_version matches "^[0-9]+\\.[0-9]+\\.[0-9]+$"
- name: Process YAML files only
shell: validate-config {{item}}
with_filetree: ./configs
when: item.name endsWith ".yml" || item.name endsWith ".yaml"
Arithmetic Operators¶
| Operator | Description | Example |
|---|---|---|
+ |
Addition | cpu_cores + 2 |
- |
Subtraction | memory_total_mb - 1024 |
* |
Multiplication | timeout * 2 |
/ |
Division | disk_total_gb / 1024 |
% |
Modulo | port % 1000 |
** |
Exponentiation | 2 ** 10 |
Examples:
- name: Check if half memory is free
assert:
command: test {{memory_free_mb}} -gt $(({{memory_total_mb}} / 2))
- name: Use exponential backoff
shell: retry-command.sh
retry_delay: "{{2 ** retry_count}}s"
Variable Access¶
Direct Variables¶
Access variables defined in vars blocks:
- vars:
app_name: myapp
version: "1.0.0"
- name: Show version
print: "{{app_name}} v{{version}}"
when: version != ""
System Facts¶
Access auto-detected system information:
- name: Platform-specific action
shell: echo "Running on {{os}}/{{arch}}"
when: os == "darwin" && arch == "arm64"
- name: Check resources
print: "CPU: {{cpu_cores}} cores, RAM: {{memory_total_mb}}MB"
when: cpu_cores >= 4 && memory_total_mb >= 8192
Common facts:
os,distribution,arch,hostname,usercpu_cores,cpu_model,memory_total_mb,memory_free_mbpackage_manager,python_version,go_version,docker_version
See Facts Reference for complete list.
Result Variables¶
Access results from previous steps with register:
- name: Check service status
shell: systemctl is-active nginx
register: nginx_status
ignore_errors: true
- name: Start if not running
service:
name: nginx
state: started
when: nginx_status.rc != 0
become: true
- name: Show service output
print: "Service was: {{nginx_status.stdout}}"
Result fields:
result.rc- Exit code (0 = success)result.stdout- Standard outputresult.stderr- Standard errorresult.changed- Whether step changed stateresult.failed- Whether step failed
Nested Access¶
Access nested structures with dot notation:
- vars:
config:
server:
host: localhost
port: 8080
features:
ssl: true
auth: true
- name: Configure server
template:
src: server.conf.j2
dest: /etc/server.conf
when: config.features.ssl == true && config.server.port > 1024
Functions¶
String Functions¶
| Function | Description | Example |
|---|---|---|
len(s) |
String length | len(password) >= 8 |
trim(s) |
Remove whitespace | trim(result.stdout) == "ok" |
upper(s) |
Uppercase | upper(env) == "PRODUCTION" |
lower(s) |
Lowercase | lower(os) == "linux" |
split(s, sep) |
Split string | split(result.stdout, ",") |
Examples:
- name: Validate password
assert:
command: test {{len(password)}} -ge 8
when: password is defined
- name: Normalize environment
vars:
normalized_env: "{{lower(environment)}}"
when: environment is defined
- name: Process CSV output
shell: echo {{item}}
with_items: "{{split(csv_data, ',')}}"
Collection Functions¶
| Function | Description | Example |
|---|---|---|
len(arr) |
Array length | len(packages) > 0 |
all(arr, pred) |
All match | all(results, .changed) |
any(arr, pred) |
Any match | any(checks, .failed) |
filter(arr, pred) |
Filter items | filter(files, .size > 1000) |
map(arr, expr) |
Transform items | map(users, .name) |
Examples:
- name: Check all services started
print: "All services running"
when: all(service_results, .rc == 0)
- name: Alert if any checks failed
print: "Some checks failed!"
when: any(health_checks, .failed == true)
- name: Process large files only
shell: compress {{item.path}}
with_items: "{{filter(files, .size > 10000000)}}"
Type Checking¶
| Function | Description | Example |
|---|---|---|
type(v) |
Get type name | type(value) == "string" |
int(v) |
Convert to int | int(port) > 1024 |
float(v) |
Convert to float | float(version) >= 1.5 |
string(v) |
Convert to string | string(port) + ":8080" |
Examples:
- name: Validate port number
assert:
command: test {{int(port)}} -gt 1024 -a {{int(port)}} -lt 65535
when: port is defined
- name: Compare versions
shell: upgrade-app.sh
when: float(current_version) < float(target_version)
Conditionals (when)¶
Basic Usage¶
- name: Linux only
shell: apt update
when: os == "linux"
become: true
- name: macOS only
shell: brew update
when: os == "darwin"
Complex Conditions¶
- name: Install on Ubuntu 20.04+
shell: apt install -y neovim
when: |
os == "linux" &&
distribution == "ubuntu" &&
float(distribution_version) >= 20.04
become: true
- name: High-performance systems only
shell: enable-turbo-mode.sh
when: |
cpu_cores >= 16 &&
memory_total_mb >= 32768 &&
(arch == "amd64" || arch == "arm64")
Result-Based Conditionals¶
- name: Check if file exists
shell: test -f /etc/config.yml
register: config_check
ignore_errors: true
- name: Create config if missing
file:
path: /etc/config.yml
state: file
content: "default: config"
when: config_check.rc != 0
- name: Validate config content
shell: validate-config.sh
when: config_check.rc == 0 && "error" not in config_check.stderr
Custom Change Detection (changed_when)¶
By default, shell commands always report changed: true. Use changed_when to detect changes based on output:
- name: Install package
shell: apt install -y neovim
become: true
register: install_result
changed_when: "'is already the newest version' not in install_result.stdout"
- name: Reload service
shell: systemctl reload nginx
become: true
register: reload_result
changed_when: "'Reloaded' in reload_result.stdout"
- name: Update configuration
shell: config-tool set key value
register: config_result
changed_when: config_result.stdout contains "Updated"
Multiple Conditions¶
- name: Deploy application
shell: ./deploy.sh
register: deploy_result
changed_when: |
deploy_result.rc == 0 &&
("deployed" in deploy_result.stdout || "updated" in deploy_result.stdout)
Never Changed¶
- name: Check status only
shell: systemctl status nginx
register: status_check
changed_when: false # Never report as changed
Custom Failure Detection (failed_when)¶
Override default failure detection (non-zero exit code):
- name: Check service health
shell: curl -f http://localhost:8080/health
register: health_check
failed_when: |
health_check.rc != 0 ||
"unhealthy" in health_check.stdout
- name: Run tests
shell: npm test
register: test_result
failed_when: |
test_result.rc != 0 ||
"FAILED" in test_result.stdout ||
int(test_result.stdout.split("passed")[0]) < 100
Multiple Failure Conditions¶
- name: Deploy with validation
shell: ./deploy.sh --validate
register: deploy_result
failed_when: |
deploy_result.rc != 0 ||
"error" in lower(deploy_result.stderr) ||
"failed" in lower(deploy_result.stdout) ||
len(deploy_result.stderr) > 0
Never Fail¶
- name: Optional cleanup
shell: rm -rf /tmp/cache
failed_when: false # Never fail (similar to ignore_errors)
Common Patterns¶
Platform Detection¶
# Linux distributions
when: os == "linux" && distribution == "ubuntu"
when: os == "linux" && package_manager == "apt"
when: os == "linux" && (distribution == "debian" || distribution == "ubuntu")
# macOS versions
when: os == "darwin" && float(os_version) >= 13.0
# Architecture
when: arch == "amd64" || arch == "arm64"
when: arch in ["amd64", "arm64", "x86_64"]
Resource Checks¶
# CPU cores
when: cpu_cores >= 4
when: cpu_cores > 1 && cpu_cores <= 8
# Memory
when: memory_total_mb >= 8192
when: memory_free_mb > (memory_total_mb / 2)
# Disk space
when: disk_free_gb >= 10
Version Comparisons¶
# Semantic versions
when: float(python_version) >= 3.8
when: go_version matches "^1\\.(2[0-9]|[3-9][0-9])"
# Package versions
when: int(split(version, ".")[0]) >= 2
String Matching¶
# Exact match
when: environment == "production"
# Contains
when: "'staging' in environment || 'prod' in environment"
# Regex
when: hostname matches "^web-[0-9]+-prod$"
# Prefix/suffix
when: path startsWith "/usr/local/"
when: filename endsWith ".yml" || filename endsWith ".yaml"
Result Inspection¶
# Exit code
when: result.rc == 0
failed_when: result.rc != 0 && result.rc != 2
# Output contains
when: "'success' in result.stdout"
failed_when: "'error' in result.stderr || 'failed' in result.stdout"
# Changed state
when: result.changed == true
changed_when: result.stdout contains "Updated"
# Empty output
when: len(result.stdout) == 0
failed_when: len(result.stderr) > 0
Collection Operations¶
# Check if list has items
when: len(packages) > 0
when: len(filter(services, .status == "active")) == len(services)
# All/any
when: all(health_checks, .rc == 0)
when: any(warnings, .severity == "high")
Escape Sequences¶
Quotes in Strings¶
# Single quotes (no escaping needed)
when: 'result.stdout == "value"'
# Double quotes (escape with backslash)
when: "result.stdout == \"value\""
# Mixed quotes
when: "result.stdout contains 'error message'"
Special Characters¶
# Newlines
when: "result.stdout contains '\\n'"
# Tabs
when: "result.stdout contains '\\t'"
# Backslashes
when: "path == 'C:\\\\Program Files\\\\App'"
Debugging Expressions¶
Print Variable Values¶
- name: Debug variables
print: |
os: {{os}}
arch: {{arch}}
cpu_cores: {{cpu_cores}}
Expression result: {{os == "linux" && cpu_cores > 4}}
Test Expressions in Shell¶
- name: Test condition
shell: |
echo "os={{os}}"
echo "condition result=$(({{os}} == "linux"))"
register: debug_output
- name: Show result
print: "{{debug_output.stdout}}"
Dry-Run Mode¶
# See which steps would execute
mooncake run config.yml --dry-run
# See conditions evaluated
mooncake run config.yml --dry-run --verbose
Performance Tips¶
-
Simple expressions are faster:
-
Cache expensive operations:
-
Use short-circuit evaluation:
See Also¶
- Control Flow - Conditionals, loops, tags
- Variables - Working with variables
- Facts Reference - All available system facts
- Actions - Complete actions reference