When working with Ansible, it's common to organize variables across multiple YAML files using the vars_files
directive. A typical setup might look like this:
vars_files:
- vars/vars.default.yml
- vars/vars.yml
However, this approach fails when one of the files doesn't exist. Ansible throws an error:
ERROR: file could not read: /.../vars/vars.yml
Ansible's vars_files
directive expects all listed files to exist by default. This behavior makes sense for required configuration files but becomes problematic when dealing with optional overrides.
Here are several approaches to handle this situation:
1. Using include_vars with Conditionals
The most flexible solution is to use include_vars
with a file existence check:
- name: Include default variables
include_vars: vars/vars.default.yml
- name: Include custom variables if file exists
include_vars: vars/vars.yml
when: lookup('file', 'vars/vars.yml') != ''
2. Combining with defaults
For more complex scenarios, you can combine this with default values:
- name: Set default variables
set_fact:
custom_vars: {}
- name: Load custom variables if available
include_vars:
file: vars/vars.yml
name: custom_vars
when: lookup('file', 'vars/vars.yml') != ''
3. Using a Custom Plugin
For enterprise environments, consider creating a custom lookup plugin:
# In playbook:
vars:
optional_vars: "{{ lookup('optional_file', 'vars/vars.yml') }}"
# Plugin code (python):
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
if os.path.exists(terms[0]):
return [self._loader.load_from_file(terms[0])]
return [{}]
- Always document which files are optional
- Consider using a naming convention for optional files (e.g., *.local.yml)
- Test your playbooks with and without optional files
Here's how this might look in a production environment:
- hosts: webservers
tasks:
- name: Load base configuration
include_vars: configs/base.yml
- name: Load environment-specific config
include_vars: "configs/{{ env }}.yml"
when: lookup('file', "configs/{{ env }}.yml") != ''
- name: Load local overrides
include_vars: configs/local.yml
when: lookup('file', 'configs/local.yml') != ''
When working with Ansible playbooks, we often use vars_files
to organize variables across multiple YAML files. A common pattern is having default variables and environment-specific overrides:
vars_files:
- vars/vars.default.yml
- vars/vars.yml
However, this approach fails when the second file (vars.yml
) doesn't exist, throwing an error that interrupts playbook execution.
In real-world scenarios, we frequently need optional configuration files:
- Environment-specific overrides (dev/staging/prod)
- Local developer customizations that shouldn't be committed
- Temporary experimental configurations
1. The include_vars Approach
The most flexible solution uses include_vars
with error handling:
- name: Load default variables
include_vars: vars/vars.default.yml
- name: Attempt to load optional variables
include_vars: vars/vars.yml
ignore_errors: yes
2. Conditionally Include Files
For more control, check file existence first:
- name: Check if vars file exists
stat:
path: "vars/vars.yml"
register: vars_file
- name: Load optional variables if exists
include_vars: vars/vars.yml
when: vars_file.stat.exists
3. Using a Custom Plugin
For advanced use cases, create a custom lookup plugin:
vars_files:
- "{{ lookup('first_found', {
'files': ['vars.yml', 'empty.yml'],
'paths': ['vars'],
'skip': true
}) }}"
- Document which files are optional in your README
- Provide example/template files for optional configurations
- Consider using
.example
extensions for files that should be copied - In CI/CD pipelines, explicitly check for required files
Here's how a production playbook might handle this:
- name: Load base configuration
include_vars: base_config.yml
- name: Load environment configuration
include_vars: "{{ env_config_file }}"
ignore_errors: yes
vars:
env_config_file: "env/{{ target_env }}.yml"
- name: Load developer overrides
include_vars: local_dev.yml
when:
- ansible_user_id == 'developer'
- lookup('file', 'local_dev.yml') != ''