-
Notifications
You must be signed in to change notification settings - Fork 47
Scenarios Tutorial
Scenarios are a powerful feature that allows you to chain multiple Broker actions together in YAML files. Instead of running separate broker checkout, broker checkin, and shell commands, you can define a complete workflow in a single scenario file.
This tutorial covers everything you need to know to write effective scenarios.
- Quick Start
- Scenario Structure
- Available Actions
- Step Configuration Options
- Variables and Templating
- Host Selection and Inventory Filters
- Loops
- Error Handling
- Capturing Output
- Complete Examples
- CLI Reference
Here's a minimal scenario that checks out a container, runs a command, and checks it back in:
# my_first_scenario.yaml
steps:
- name: Provision a container
action: checkout
arguments:
container_host: ubi8
- name: Run a command
action: ssh
arguments:
command: "cat /etc/os-release"
with:
hosts: scenario_inventory
- name: Release the container
action: checkin
with:
hosts: scenario_inventorySave this file to ~/.broker/scenarios/my_first_scenario.yaml, then run:
broker scenarios execute my_first_scenarioA scenario file has three main sections:
# config: Optional global settings
config:
inventory_path: /path/to/custom_inventory.yaml # Custom inventory file
log_path: my_scenario.log # Custom log file
settings: # Provider-specific settings
Container:
runtime: podman
# variables: Optional key-value pairs for use in steps
variables:
MY_IMAGE: ubi8
INSTALL_PACKAGES: true
TIMEOUT: 60
# steps: Required list of actions to execute
steps:
- name: Step 1
action: checkout
arguments:
container_host: "{{ MY_IMAGE }}"The config section allows you to customize scenario behavior:
| Key | Description |
|---|---|
inventory_path |
Path to a custom inventory file for this scenario |
log_path |
Custom log file path (see path resolution rules below) |
settings |
Nested map of provider-specific settings that override broker_settings.yaml
|
Log Path Resolution Rules:
-
Not specified: Uses the default
broker.logfile in{BROKER_DIRECTORY}/logs/ -
Filename only (e.g.,
my_scenario.log): Creates file in{BROKER_DIRECTORY}/logs/ -
Absolute path with filename (e.g.,
/var/log/broker/custom.log): Uses as-is -
Absolute directory (e.g.,
/var/log/broker/): Creates{scenario_name}.login that directory
Example: Override AnsibleTower timeout
config:
settings:
AnsibleTower:
workflow_timeout: 600Variables defined here are available throughout your scenario via Jinja2 templating:
variables:
RHEL_VERSION: "9.4"
HOST_COUNT: 3
DEPLOY_CONFIG:
memory: 4096
cpus: 2
steps:
- name: Deploy hosts
action: checkout
arguments:
workflow: deploy-rhel
rhel_version: "{{ RHEL_VERSION }}"
count: "{{ HOST_COUNT }}"Variables can be overridden via CLI:
broker scenarios execute my_scenario --RHEL_VERSION 8.10 --HOST_COUNT 5Scenarios support 11 different actions:
Checks out hosts from a provider (VMs, containers, etc.).
- name: Provision RHEL VM
action: checkout
arguments:
workflow: deploy-rhel # AnsibleTower workflow
rhel_version: "9.4"
count: 2 # Number of hosts
note: "Testing new feature"
- name: Provision container
action: checkout
arguments:
container_host: ubi8 # Container image
ports: "22:2222 80:8080" # Port mappings
environment: "DEBUG=1" # Environment variablesOutput: List of host objects, automatically added to scenario_inventory.
Releases hosts back to the provider.
- name: Release all scenario hosts
action: checkin
with:
hosts: scenario_inventory
- name: Release specific hosts
action: checkin
with:
hosts: "@scenario_inv[0:2]" # First two hosts onlyOutput: true on success.
Runs shell commands on target hosts.
- name: Check disk space
action: ssh
arguments:
command: "df -h"
timeout: 30 # Optional timeout in seconds
with:
hosts: scenario_inventoryOutput:
- Single host: A Result object with
stdout,stderr, andstatusattributes - Multiple hosts: Dictionary mapping hostname to Result object
Uploads files to remote hosts.
- name: Upload config file
action: scp
arguments:
source: /local/path/config.yaml
destination: /etc/myapp/config.yaml
with:
hosts: scenario_inventoryOutput: Result object with success message.
Transfers files via SFTP (supports both upload and download).
- name: Upload script
action: sftp
arguments:
source: ./scripts/setup.sh
destination: /root/setup.sh
direction: upload # Default
- name: Download logs
action: sftp
arguments:
source: /var/log/app.log
destination: ./logs/app.log
direction: download
with:
hosts: scenario_inventoryOutput: Result object with success message.
Executes arbitrary provider actions (like power operations, extend lease, etc.).
- name: Extend VM lease
action: execute
arguments:
workflow: extend-lease
source_vm: "{{ host.name }}"
extend_days: 7Output: Provider-specific result.
Queries a provider for available resources (workflows, images, inventories, etc.).
# Flag-style query: list all workflows
- name: List available workflows
action: provider_info
arguments:
provider: AnsibleTower
query: workflows
tower_inventory: my-inventory
# Value-style query: get specific workflow details
- name: Get workflow details
action: provider_info
arguments:
provider: AnsibleTower
query:
workflow: deploy-rhelAvailable queries by provider:
| Provider | Flag Queries | Value Queries |
|---|---|---|
| AnsibleTower |
workflows, inventories, job_templates, templates, flavors
|
workflow, inventory, job_template
|
| Container |
container_hosts, container_apps
|
container_host, container_app
|
| Beaker | jobs |
job |
| Foreman | hostgroups |
hostgroup |
| OpenStack |
images, flavors, networks, templates
|
- |
Output: Dictionary or list of provider resource data.
Works with Broker's inventory system.
# Sync inventory from a provider
- name: Sync Tower inventory
action: inventory
arguments:
sync: AnsibleTower
# Filter inventory
- name: Get RHEL hosts
action: inventory
arguments:
filter: "'rhel' in @inv.name"Output: Inventory data (list of host dictionaries).
Writes content to stdout, stderr, or a file.
# Write to stdout
- name: Display message
action: output
arguments:
content: "Deployment complete!"
destination: stdout # Default
# Write to file (format auto-detected by extension)
- name: Save results to JSON
action: output
arguments:
content: "{{ workflow_results }}"
destination: /tmp/results.json
mode: overwrite # or "append"
# Write to YAML file
- name: Save hosts list
action: output
arguments:
content: "{{ scenario_inventory }}"
destination: ./hosts.yamlOutput: The content that was written.
Terminates scenario execution with a return code.
- name: Exit on failure condition
action: exit
arguments:
return_code: 1
message: "Required condition not met"
when: not deployment_successful
- name: Exit successfully
action: exit
arguments:
return_code: 0
message: "All tests passed"Chains other scenario files for modular workflows.
- name: Run cleanup scenarios
action: run_scenarios
arguments:
paths:
- /path/to/cleanup_vms.yaml
- /path/to/cleanup_containers.yamlOutput: List of {path, success} dictionaries.
Each step supports several configuration options:
Human-readable identifier for the step. Used for logging and step memory references.
- name: Deploy production serversOne of the 11 available actions.
Key-value pairs passed to the action. Supports templating.
Specifies which hosts an action should target.
with:
hosts: scenario_inventory # All hosts from this scenario
hosts: inventory # All hosts from main Broker inventory
hosts: "@scenario_inv[0]" # First scenario host
hosts: "'rhel' in @inv.name" # Filtered hostsExecute step only if condition is true.
- name: Install packages
action: ssh
arguments:
command: "dnf install -y httpd"
with:
hosts: scenario_inventory
when: INSTALL_PACKAGES == true
- name: Cleanup only if failed
action: checkin
with:
hosts: scenario_inventory
when: previous_step.status == 'failed'For multi-host actions, control whether they run in parallel or sequentially.
- name: Run migrations sequentially
action: ssh
arguments:
command: "./migrate.sh"
with:
hosts: scenario_inventory
parallel: false # Run one at a time (default: true)By default, scenarios stop on step failure. Set to false to continue.
- name: Optional cleanup
action: ssh
arguments:
command: "rm -rf /tmp/cache"
with:
hosts: scenario_inventory
exit_on_error: false # Continue even if this failsScenarios use Jinja2 templating for dynamic values.
# Simple variable
"{{ variable_name }}"
# Attribute access
"{{ host.hostname }}"
# Method calls
"{{ result.stdout.strip() }}"
# String interpolation
"Host {{ hostname }} returned: {{ result.stdout }}"
# Expressions
"{{ count * 2 }}"| Variable | Description |
|---|---|
step |
Current step's memory (name, output, status) |
previous_step |
Previous step's memory |
steps |
Dictionary of all steps by name |
scenario_inventory |
List of hosts checked out by this scenario |
| User variables | Any variable from variables section or CLI |
| Captured variables | Any variable captured via capture
|
Access previous step results:
- name: Run command
action: ssh
arguments:
command: "ls /root"
with:
hosts: scenario_inventory
capture:
as: ls_result
- name: Check if file exists
action: ssh
arguments:
command: "cat /root/config.yaml"
with:
hosts: scenario_inventory
when: "'config.yaml' in ls_result.stdout"Step memory has these attributes:
-
name- Step name -
output- Action result -
status- "pending", "running", "completed", "skipped", or "failed"
| Expression | Description |
|---|---|
scenario_inventory |
All hosts checked out by this scenario |
inventory |
All hosts from main Broker inventory |
@scenario_inv |
Scenario inventory (for filtering) |
@inv |
Main inventory (for filtering) |
Filter hosts using Python-like expressions:
# Index access
"@scenario_inv[0]" # First host
"@scenario_inv[-1]" # Last host
# Slicing
"@scenario_inv[0:3]" # First three hosts
"@scenario_inv[1:]" # All except first
"@inv[:]" # All hosts (as list)
# Attribute filtering
"'rhel' in @inv.name" # Hosts with 'rhel' in name
"'satellite' in @inv.hostname" # Hosts with 'satellite' in hostname
"@inv._broker_provider == 'AnsibleTower'" # By providerExecute a step multiple times over an iterable.
- name: Process each host
action: ssh
arguments:
command: "hostname"
loop:
iterable: "@scenario_inv[:]" # Loop over all scenario hosts
iter_var: current_host
with:
hosts: "{{ current_host }}" # Use loop variablevariables:
PACKAGES:
- httpd
- postgresql
- redis
steps:
- name: Install packages
action: ssh
arguments:
command: "dnf install -y {{ package }}"
loop:
iterable: PACKAGES
iter_var: package
with:
hosts: scenario_inventoryUse tuple unpacking to iterate over dictionary items:
- name: Get command output from each host
action: ssh
arguments:
command: "uptime"
with:
hosts: scenario_inventory
capture:
as: uptime_results # Dict: {hostname: Result}
- name: Process each result
action: output
arguments:
content: "{{ hostname }}: {{ result.stdout }}"
destination: stdout
loop:
iterable: uptime_results.items()
iter_var: hostname, result # Tuple unpackingThe when condition is evaluated for each iteration:
- name: Only process successful results
action: output
arguments:
content: "{{ hostname }} is healthy"
loop:
iterable: check_results.items()
iter_var: hostname, result
when: result.status == 0Continue loop even if some iterations fail:
- name: Run risky command on each host
action: ssh
arguments:
command: "risky-operation"
loop:
iterable: "@scenario_inv[:]"
iter_var: host
on_error: continue # Don't stop on failure
with:
hosts: "{{ host }}"Proceed to next step even if current step fails:
- name: Optional step
action: ssh
arguments:
command: "optional-command"
with:
hosts: scenario_inventory
on_error: continueExecute cleanup or recovery actions when a step fails:
- name: Critical operation
action: ssh
arguments:
command: "critical-operation"
with:
hosts: scenario_inventory
on_error:
- name: Log failure
action: output
arguments:
content: "Critical operation failed, cleaning up..."
destination: stderr
- name: Cleanup resources
action: checkin
with:
hosts: scenario_inventory
- name: Exit with error
action: exit
arguments:
return_code: 1
message: "Critical operation failed"For non-critical steps without recovery:
- name: Try to gather metrics
action: ssh
arguments:
command: "collect-metrics"
with:
hosts: scenario_inventory
exit_on_error: false # Continue regardless of outcomeStore step results in variables for later use.
- name: Get hostname
action: ssh
arguments:
command: "hostname -f"
with:
hosts: scenario_inventory
capture:
as: hostname_result
- name: Display hostname
action: output
arguments:
content: "FQDN: {{ hostname_result.stdout }}"Extract or transform the output before storing:
- name: Get OS version
action: ssh
arguments:
command: "cat /etc/redhat-release"
with:
hosts: scenario_inventory
capture:
as: os_version
transform: "{{ step.output.stdout.strip() }}"When capturing loop results, each iteration's result is stored in a dictionary:
- name: Check each service
action: ssh
arguments:
command: "systemctl is-active {{ service }}"
loop:
iterable: SERVICES
iter_var: service
with:
hosts: scenario_inventory
capture:
as: service_status # Dict: {"httpd": Result, "nginx": Result, ...}Use custom keys for better organization:
- name: Get workflow details
action: provider_info
arguments:
provider: AnsibleTower
query:
workflow: "{{ workflow_name }}"
loop:
iterable: workflow_list
iter_var: workflow_name
capture:
as: workflow_details
key: result.name # Use workflow name as dict key# ci_pipeline_test.yaml
# Tests deployment and runs verification on containers
variables:
TEST_IMAGE: ubi9
HOST_COUNT: 2
TEST_PACKAGES:
- python3
- git
- make
config:
inventory_path: ~/.broker/ci_test_inventory.yaml
steps:
- name: Provision test containers
action: checkout
arguments:
container_host: "{{ TEST_IMAGE }}"
count: "{{ HOST_COUNT }}"
- name: Install test dependencies
action: ssh
arguments:
command: "dnf install -y {{ TEST_PACKAGES | join(' ') }}"
with:
hosts: scenario_inventory
parallel: true
- name: Clone test repository
action: ssh
arguments:
command: "git clone https://github.com/example/tests.git /root/tests"
with:
hosts: scenario_inventory
- name: Run tests
action: ssh
arguments:
command: "cd /root/tests && make test"
timeout: 300
with:
hosts: scenario_inventory
capture:
as: test_results
- name: Save test results
action: output
arguments:
content: "{{ test_results }}"
destination: ./test_results.yaml
- name: Cleanup containers
action: checkin
with:
hosts: scenario_inventory# sync_and_report.yaml
# Syncs inventory from multiple providers and generates a report
variables:
PROVIDERS:
- AnsibleTower
- Container
- Beaker
steps:
- name: Sync all provider inventories
action: inventory
arguments:
sync: "{{ provider }}"
loop:
iterable: PROVIDERS
iter_var: provider
on_error: continue
capture:
as: sync_results
- name: Load full inventory
action: inventory
arguments: {}
capture:
as: full_inventory
- name: Generate inventory report
action: output
arguments:
content: |
# Broker Inventory Report
Generated: {{ now }}
Total hosts: {{ full_inventory | length }}
Hosts by provider:
{% for host in full_inventory %}
- {{ host.hostname }} ({{ host._broker_provider }})
{% endfor %}
destination: ./inventory_report.md
- name: Display summary
action: output
arguments:
content: "Synced {{ PROVIDERS | length }} providers. Total hosts: {{ full_inventory | length }}"# deploy_with_rollback.yaml
# Deploys application with automatic rollback on failure
variables:
WORKFLOW: deploy-application
APP_VERSION: "2.1.0"
ROLLBACK_VERSION: "2.0.0"
steps:
- name: Deploy new version
action: checkout
arguments:
workflow: "{{ WORKFLOW }}"
app_version: "{{ APP_VERSION }}"
on_error:
- name: Log deployment failure
action: output
arguments:
content: "Deployment of {{ APP_VERSION }} failed, initiating rollback..."
destination: stderr
- name: Deploy rollback version
action: checkout
arguments:
workflow: "{{ WORKFLOW }}"
app_version: "{{ ROLLBACK_VERSION }}"
on_error:
- name: Critical failure
action: exit
arguments:
return_code: 2
message: "Both deployment and rollback failed!"
- name: Rollback successful
action: output
arguments:
content: "Rollback to {{ ROLLBACK_VERSION }} successful"
- name: Exit with warning
action: exit
arguments:
return_code: 1
message: "Deployed rollback version due to failure"
- name: Verify deployment
action: ssh
arguments:
command: "curl -s localhost:8080/health"
with:
hosts: scenario_inventory
capture:
as: health_check
- name: Deployment complete
action: output
arguments:
content: "Successfully deployed {{ APP_VERSION }}"broker scenarios listShows all scenarios in ~/.broker/scenarios/.
# By name (from scenarios directory)
broker scenarios execute my_scenario
# By path
broker scenarios execute /path/to/scenario.yaml
# With variable overrides
broker scenarios execute my_scenario --MY_VAR value --COUNT 5
# With config overrides
broker scenarios execute my_scenario --config.settings.Container.runtime docker
# Run in background
broker scenarios execute my_scenario --backgroundbroker scenarios info my_scenario
# Without syntax highlighting
broker scenarios info my_scenario --no-syntaxbroker scenarios validate my_scenarioChecks syntax and schema validation without executing.
-
Always clean up: Include a
checkinstep at the end of your scenarios, preferably with error handling to ensure cleanup even on failure. -
Use meaningful names: Step names appear in logs and can be referenced via
steps['Step Name']. -
Capture intermediate results: Use
captureliberally to store results for debugging and conditional logic. -
Test with
--background: Long-running scenarios can be run in the background. -
Validate before running: Use
broker scenarios validateto catch syntax errors early. -
Modularize complex workflows: Split large scenarios into smaller ones and use
run_scenariosto chain them. -
Use variables for flexibility: Define configurable values in the
variablessection so they can be overridden via CLI. -
Handle errors gracefully: Use
on_errorblocks for critical steps that need cleanup on failure.
"Undefined variable in template"
- Check that the variable is defined in
variablessection or captured by a previous step - Variables are case-sensitive
- Use
broker scenarios infoto see available variables
"Scenario not found"
- Ensure file is in
~/.broker/scenarios/or provide full path - File must have
.yamlor.ymlextension
"SSH action requires target hosts"
- Add a
with.hostsspecification to the step - Ensure a previous
checkoutstep has added hosts toscenario_inventory
"Step failed but no on_error defined"
- Add
on_error: continueto ignore failures - Add
exit_on_error: falseto continue without error handling - Add an
on_errorblock with recovery steps
Enable verbose logging:
broker --log-level debug scenarios execute my_scenarioCheck the scenario structure:
broker scenarios info my_scenarioValidate without executing:
broker scenarios validate my_scenario