Skip to content

Loops Examples


Basic Loop Iteration

# Example: Basic Loop Iteration
# OrchStep supports looping over lists, counts, and ranges.
# Each iteration has access to loop metadata like index and position.
#
# Key concepts:
# - "loop:" accepts a list variable, a count, or a range
# - {{ loop.item }} -- current item value
# - {{ loop.index }} -- zero-based index (0, 1, 2, ...)
# - {{ loop.index1 }} -- one-based index (1, 2, 3, ...)
# - {{ loop.first }} -- true on the first iteration
# - {{ loop.last }} -- true on the last iteration
# - {{ loop.length }} -- total number of items
# - "as:" renames loop.item to a custom name
# - Loop outputs are collected into an array
#
# Try: orchstep run
name: basic-loop-demo
desc: "Iterate over lists, counts, and ranges"
defaults:
servers:
- "web-01.example.com"
- "web-02.example.com"
- "web-03.example.com"
tasks:
main:
desc: "Demonstrate loop patterns"
steps:
# Pattern 1: Loop over a list variable
- name: check_servers
desc: "Health-check each server"
loop: "{{ vars.servers }}"
func: shell
do: |
echo "Checking server {{ loop.index1 }}/{{ loop.length }}: {{ loop.item }}"
echo " First: {{ loop.first }}, Last: {{ loop.last }}"
outputs:
server: "{{ loop.item }}"
position: "{{ loop.index1 }}"
# Pattern 2: Loop a fixed number of times
- name: retry_attempts
desc: "Simulate 5 retry attempts"
loop: 5
func: shell
do: echo "Attempt {{ loop.item }} of {{ loop.length }}"
outputs:
attempt: "{{ loop.item }}"
# Pattern 3: Loop over a numeric range
- name: port_scan
desc: "Check ports 8080-8084"
loop:
range: [8080, 8084]
as: port
func: shell
do: echo "Scanning port {{ loop.port }}"
outputs:
port: "{{ loop.port }}"
# Pattern 4: Loop with a custom variable name
- name: deploy_servers
desc: "Deploy to each server using a named loop variable"
loop:
items: "{{ vars.servers }}"
as: hostname
func: shell
do: |
echo "Deploying to {{ loop.hostname }} ({{ loop.index1 }}/{{ loop.length }})"
outputs:
hostname: "{{ loop.hostname }}"

Loop Until

# Example: Loop Until
# The "until:" keyword breaks the loop when a condition becomes true.
# This is useful for polling, retrying, or searching through items.
#
# Key concepts:
# - "until:" evaluates after each iteration
# - When the until condition is true, the loop stops
# - Supports Go template expressions: '{{ eq .loop.iteration 5 }}'
# - Supports JavaScript expressions: 'loop.iteration === 5'
# - Can reference step outputs for status-based breaking
# - "delay:" adds a pause between iterations (useful for polling)
#
# Try: orchstep run find_target
# Try: orchstep run retry_until_ready
# Try: orchstep run search_items
name: loop-until-demo
desc: "Break loops with until conditions"
defaults:
target_number: 5
search_items:
- name: "item-a"
valid: false
- name: "item-b"
valid: false
- name: "item-c"
valid: true
- name: "item-d"
valid: false
tasks:
# Count-based loop that stops at a target
find_target:
desc: "Loop until we reach the target number"
steps:
- name: count_to_target
loop:
count: 20
until: '{{ eq .loop.iteration .vars.target_number }}'
func: shell
do: |
echo "Checking: {{ loop.iteration }}"
outputs:
checked: "{{ loop.iteration }}"
# Retry pattern with delay
retry_until_ready:
desc: "Poll a service until it becomes ready"
steps:
- name: wait_for_ready
loop:
count: 10
until: '{{ eq .steps.wait_for_ready.status "ready" }}'
delay: "100ms"
func: shell
do: |
echo "Attempt {{ loop.iteration }}..."
if [ {{ loop.iteration }} -ge 3 ]; then
echo "STATUS: ready"
else
echo "STATUS: not_ready"
fi
outputs:
attempt: "{{ loop.iteration }}"
status: '{{ if ge loop.iteration 3 }}ready{{ else }}not_ready{{ end }}'
# Search through items until a match is found
search_items:
desc: "Search a list until a valid item is found"
steps:
- name: find_valid
loop:
items: "{{ vars.search_items }}"
as: item
until: '{{ eq .loop.item.valid true }}'
func: shell
do: |
echo "Checking: {{ loop.item.name }} (valid={{ loop.item.valid }})"
outputs:
item_name: "{{ loop.item.name }}"
is_valid: "{{ loop.item.valid }}"
# JavaScript-style until condition
js_until_example:
desc: "Loop until a JavaScript condition is met"
steps:
- name: count_up
loop:
count: 10
until: 'loop.iteration > 5'
func: shell
do: echo "Iteration {{ loop.iteration }}"
outputs:
iteration: "{{ loop.iteration }}"

Loop with Conditions

# Example: Loop with Conditions
# Combine loops with "if:" to filter which iterations execute.
# OrchStep evaluates the condition differently depending on context:
#
# - if WITHOUT loop.* references -> evaluated ONCE before the loop
# (acts as a gate: run the entire loop or skip it)
# - if WITH loop.* references -> evaluated PER ITERATION
# (acts as a filter: skip individual items)
#
# Try: orchstep run
# Try: orchstep run --var enable_deployment=false
name: loop-with-conditions-demo
desc: "Filter loop iterations using if conditions"
defaults:
servers:
- name: "web-01"
type: "web"
enabled: true
port: 8080
- name: "web-02"
type: "web"
enabled: false
port: 8081
- name: "db-01"
type: "database"
enabled: true
port: 5432
- name: "cache-01"
type: "cache"
enabled: true
port: 6379
enable_deployment: true
tasks:
main:
desc: "Deploy infrastructure with conditional filtering"
steps:
# Gate pattern: if without loop.* -> evaluated once
# If enable_deployment is false, the entire loop is skipped
- name: deploy_all
loop: '{{ vars.servers }}'
if: '{{ vars.enable_deployment }}'
func: shell
do: echo "Deploying {{ loop.item.name }} ({{ loop.item.type }})"
# Filter pattern: if with loop.item -> evaluated per iteration
# Only processes servers where enabled=true
- name: deploy_enabled_only
loop: '{{ vars.servers }}'
if: '{{ loop.item.enabled }}'
func: shell
do: 'echo "Deploying enabled server: {{ loop.item.name }} on port {{ loop.item.port }}"'
# Complex filter: multiple conditions
# Only web servers that are enabled
- name: deploy_web_servers
loop: '{{ vars.servers }}'
if: '{{ and (eq loop.item.type "web") loop.item.enabled }}'
func: shell
do: 'echo "Web server: {{ loop.item.name }} on port {{ loop.item.port }}"'
# Filter by type
- name: setup_databases
loop: '{{ vars.servers }}'
if: '{{ eq loop.item.type "database" }}'
func: shell
do: 'echo "Setting up database: {{ loop.item.name }} on port {{ loop.item.port }}"'
# Show iteration metadata with filtering
- name: enabled_server_report
loop: '{{ vars.servers }}'
if: '{{ loop.item.enabled }}'
func: shell
do: |
echo "Server {{ loop.index1 }}/{{ loop.length }}: {{ loop.item.name }}"
echo " Type: {{ loop.item.type }}, Port: {{ loop.item.port }}"

Loop with Task Call

# Example: Loop with Task Call
# Loops can call tasks using "task:" and pass per-iteration parameters
# with "with:". This is ideal for deploying to multiple regions, running
# the same operation across environments, etc.
#
# Key concepts:
# - "loop:" + "task:" calls the task once per iteration
# - "with:" passes loop data as task parameters
# - Loop variables (loop.item, loop.index, etc.) are available in "with:"
# - "as:" renames loop.item for clearer references
#
# Try: orchstep run
name: loop-with-task-call-demo
desc: "Call tasks in a loop with per-iteration parameters"
defaults:
regions:
- name: "us-east-1"
replicas: 3
host: "east.example.com"
- name: "us-west-2"
replicas: 5
host: "west.example.com"
- name: "eu-west-1"
replicas: 7
host: "eu.example.com"
tasks:
main:
desc: "Deploy to all regions"
steps:
# Loop through regions, calling deploy_region for each
- name: deploy_all_regions
desc: "Deploy to every region using a reusable task"
loop:
items: "{{ vars.regions }}"
as: region
task: deploy_region
with:
region_name: "{{ loop.region.name }}"
replica_count: "{{ loop.region.replicas }}"
host_name: "{{ loop.region.host }}"
progress: "{{ loop.index1 }}/{{ loop.length }}"
# Reusable task called once per region
deploy_region:
desc: "Deploy to a single region"
steps:
- name: show_config
func: shell
do: |
echo "=== Deploying Region [{{ vars.progress }}] ==="
echo "Region: {{ vars.region_name }}"
echo "Host: {{ vars.host_name }}"
echo "Replicas: {{ vars.replica_count }}"
outputs:
region: "{{ vars.region_name }}"
replicas: "{{ vars.replica_count }}"
- name: execute_deploy
func: shell
do: |
echo "kubectl apply -f deploy.yml \
--context={{ vars.region_name }} \
--replicas={{ vars.replica_count }}"
echo "Status: deployed"
outputs:
status: "deployed"
host: "{{ vars.host_name }}"