When working with Ansible roles, we often encounter situations where default variables need to adapt based on other variables' values. A common scenario involves dependent configurations like environment settings (env
) and their corresponding command options (composer_opts
).
# Default values that work together
env: prod
composer_opts: "--no-dev --optimize-autoloader --no-interaction"
The challenge arises when users change the env
to dev
but forget to adjust composer_opts
. The production-optimized defaults then break the development environment setup.
# Problem scenario
env: dev # Changed from default
composer_opts: "--no-dev" # Still using production default - breaks dev setup!
Ansible provides several ways to implement smart defaults that adjust based on other variables:
Solution 1: Using the default Filter with Conditionals
# defaults/main.yml
env: prod
composer_opts: "{{ '--no-dev --optimize-autoloader --no-interaction' if env == 'prod' else '' }}"
Solution 2: Jinja2 Conditional Expressions in vars/
# vars/main.yml
composer_opts: >-
{% if env == 'prod' %}
--no-dev --optimize-autoloader --no-interaction
{% else %}
# Empty string for dev
{% endif %}
Solution 3: Combining defaults with set_fact
# tasks/main.yml
- name: Set smart composer_opts default
ansible.builtin.set_fact:
composer_opts: "{{ composer_opts | default(env_opts_mapping[env]) }}"
vars:
env_opts_mapping:
prod: "--no-dev --optimize-autoloader --no-interaction"
dev: ""
test: "--no-dev"
All these solutions respect existing variable assignments while providing smart defaults:
# Example playbook that overrides defaults
vars:
env: dev
composer_opts: "--profile" # User's specific setting takes precedence
For complex scenarios, consider creating a custom filter plugin:
# filter_plugins/smart_defaults.py
def smart_composer_opts(env, current_value=None):
if current_value:
return current_value
return "--no-dev --optimize-autoloader --no-interaction" if env == "prod" else ""
class FilterModule(object):
def filters(self):
return {'smart_composer_opts': smart_composer_opts}
Usage in playbook:
composer_opts: "{{ env | smart_composer_opts(composer_opts) }}"
- Document your conditional defaults clearly in role documentation
- Consider adding input validation for the env variable
- Test all possible combinations of explicit and default values
In Ansible role development, we often encounter situations where default variables need to adapt based on other variable values. A common scenario involves interdependent configuration parameters where changing one should intelligently affect another - but only when the second variable isn't explicitly set.
Consider these related variables for a Composer deployment:
# defaults/main.yml
env: prod
composer_opts: "--no-dev --optimize-autoloader --no-interaction"
When env: prod
, the default composer_opts
works perfectly. But if we switch to env: dev
while keeping the default options, we get broken behavior since development dependencies should be installed.
Ansible provides several ways to implement conditional defaults:
Method 1: Using the default
filter with conditions
# defaults/main.yml
env: prod
composer_opts: "{{ '' if env == 'dev' else '--no-dev --optimize-autoloader --no-interaction' }}"
Method 2: Separate variable files with includes
# defaults/main.yml
env: prod
composer_opts: "{{ composer_opts_default }}"
# vars/dev.yml
composer_opts_default: ""
# vars/prod.yml
composer_opts_default: "--no-dev --optimize-autoloader --no-interaction"
To ensure explicit variable settings always take precedence:
# tasks/main.yml
- name: Set dynamic composer options
set_fact:
final_composer_opts: >-
{% if composer_opts is defined and composer_opts != omit %}
{{ composer_opts }}
{% else %}
{{ '' if env == 'dev' else '--no-dev --optimize-autoloader --no-interaction' }}
{% endif %}
- Document all dynamic default behaviors in role documentation
- Use clear variable naming (like
_default
suffix) - Test all conditional branches in molecule tests
- Consider using
omit
for truly optional parameters
For complex logic, create a custom filter plugin:
# filter_plugins/dynamic_defaults.py
def dynamic_composer_opts(env, current_value=None):
if current_value:
return current_value
return '' if env == 'dev' else '--no-dev --optimize-autoloader --no-interaction'
class FilterModule(object):
def filters(self):
return {'dynamic_composer_opts': dynamic_composer_opts}
Usage in playbook:
composer_opts: "{{ env | dynamic_composer_opts(composer_opts) }}"