Skip to content

Control Flow

OrchStep provides conditionals, loops, switch/case, and task calling for workflow branching and iteration.

steps:
- name: cleanup
if: '{{ vars.clear_cache }}'
func: shell
do: rm -rf /tmp/cache/*

Step is skipped when the condition evaluates to false.

steps:
- name: deploy
if: '{{ eq vars.environment "production" }}'
then:
- name: deploy_prod
func: shell
do: echo "Production deployment with approval"
else:
then:
- name: deploy_staging
func: shell
do: echo "Staging deployment"
steps:
- name: set_log_level
if: '{{ eq vars.environment "production" }}'
then:
- func: shell
do: echo "LOG_LEVEL=error"
elif:
- if: '{{ eq vars.environment "staging" }}'
then:
- func: shell
do: echo "LOG_LEVEL=warn"
- if: '{{ eq vars.environment "development" }}'
then:
- func: shell
do: echo "LOG_LEVEL=debug"
else:
then:
- func: shell
do: echo "LOG_LEVEL=info"
steps:
- name: deploy
if: '{{ eq vars.environment "production" }}'
then: deploy_production
else: deploy_staging

Go Template expressions (wrapped in {{ }}):

OperatorExample
eq'{{ eq vars.env "prod" }}'
ne'{{ ne vars.status "failed" }}'
gt, ge'{{ gt vars.count 10 }}'
lt, le'{{ lt vars.timeout 30 }}'
and'{{ and (eq vars.env "prod") (gt vars.replicas 2) }}'
or'{{ or (eq vars.mode "test") (eq vars.mode "dev") }}'
not'{{ not (empty vars.name) }}'

JavaScript expressions (no {{ }} wrapper):

if: 'vars.count > 10 && vars.env === "production"'

For multi-way branching based on a single value:

steps:
- name: route_deploy
switch:
value: '{{ vars.environment }}'
cases:
- when: 'production'
then:
- func: shell
do: echo "Blue-green deploy"
- when: 'staging'
then:
- func: shell
do: echo "Rolling deploy"
- when: ['development', 'testing']
then:
- func: shell
do: echo "Direct deploy"
default:
- func: shell
do: echo "Unknown environment"

Use arrays to match multiple values in a single case:

switch:
value: '{{ vars.status_code }}'
cases:
- when: [200, 201, 204]
then:
- func: shell
do: echo "Success"
- when: [400, 401, 403, 404]
then:
- func: shell
do: echo "Client error"
- when: [500, 502, 503]
then:
- func: shell
do: echo "Server error, retry"
switch:
value: '{{ vars.deploy_strategy }}'
cases:
- when: 'blue_green'
task: deploy_blue_green
- when: 'canary'
task: deploy_canary
- when: 'rolling'
task: deploy_rolling
default:
- task: deploy_standard
steps:
- name: deploy_regions
loop: ["us-east-1", "eu-west-1", "ap-southeast-1"]
func: shell
do: echo "Deploying to {{ loop.item }}"
vars:
servers: ["web1", "web2", "web3"]
steps:
- name: restart
loop: "{{ vars.servers }}"
func: shell
do: ssh {{ loop.item }} "systemctl restart app"
VariableTypeDescription
loop.itemanyCurrent item value
loop.indexintZero-based index (0, 1, 2…)
loop.index1intOne-based index (1, 2, 3…)
loop.firstboolTrue for first iteration
loop.lastboolTrue for last iteration
loop.lengthintTotal number of items
loop.iterationintAlias for loop.index1
steps:
- name: process
loop:
items: "{{ vars.servers }}"
as: server # Custom name (access via loop.server)
on_error: continue # fail | continue | break
until: '{{ eq loop.server.status "active" }}' # Break condition
timeout: "30s" # Per-iteration timeout
delay: "2s" # Delay between iterations
collect_errors: true # Save error details
func: shell
do: deploy.sh {{ loop.server.host }}
steps:
- name: retry_check
loop: 5
func: shell
do: echo "Attempt {{ loop.iteration }} of {{ loop.length }}"
steps:
- name: scale
loop:
range: [1, 10, 2] # 1, 3, 5, 7, 9
func: shell
do: echo "Instance {{ loop.item }}"
vars:
servers:
- name: web1
enabled: true
- name: web2
enabled: false
- name: db1
enabled: true
steps:
- name: deploy_enabled
loop: "{{ vars.servers }}"
if: '{{ loop.item.enabled }}'
func: shell
do: echo "Deploying {{ loop.item.name }}"

Stop the loop when a condition becomes true:

steps:
- name: wait_for_ready
loop:
count: 30
until: '{{ contains steps.wait_for_ready.output "READY" }}'
delay: "2s"
func: shell
do: curl -s http://service/health
tasks:
deploy_all:
steps:
- name: deploy_regions
loop:
items: "{{ vars.regions }}"
as: region
on_error: continue
task: deploy_single
with:
region_name: "{{ loop.region.name }}"
replicas: "{{ loop.region.replicas }}"
deploy_single:
steps:
- func: shell
do: deploy {{ vars.region_name }} --replicas {{ vars.replicas }}
tasks:
main:
steps:
- name: run_deploy
task: deploy_service
deploy_service:
steps:
- func: shell
do: echo "Deploying..."

Use with: to pass parameters that override the called task’s defaults:

tasks:
main:
steps:
- name: deploy_staging
task: deploy
with:
environment: staging
replicas: 2
- name: deploy_production
task: deploy
with:
environment: production
replicas: 5
deploy:
vars:
environment: dev
replicas: 1
steps:
- func: shell
do: echo "Deploying to {{ vars.environment }} ({{ vars.replicas }} replicas)"

Inner steps of conditional/switch blocks are isolated. Expose outputs via the parent outputs: field:

steps:
- name: get_config
if: '{{ eq vars.env "production" }}'
then:
- name: load_prod
func: shell
do: echo "prod-config-123"
outputs:
config_id: "{{ result.output }}"
else:
then:
- name: load_dev
func: shell
do: echo "dev-config-456"
outputs:
config_id: "{{ result.output }}"
outputs:
final_config: '{{ steps.load_prod.config_id || steps.load_dev.config_id }}'
- name: use_config
func: shell
do: echo "Using {{ steps.get_config.final_config }}"