Handling Missing Variable Files in Ansible: How to Conditionally Include vars_files


3 views

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') != ''