Ansible: Can I use vars_files when some files do not exist

Solution 1:

It's quite simple really. You can squash your different vars_files items into a single tuple and Ansible will automatically go through each one until it finds a file that exists and load it. E.x.:

vars_files:
  - [ "vars/foo.yml", "vars/bar.yml", "vars/default.yml" ]

Solution 2:

According to Ansible developers, the proper way to solve this is to use something like:

vars_files_locs: ['../path/to/file1', '../path/to/file2', ...]

- include_vars: "{{ item }}"
  with_first_found: vars_files_locs

Furthermore, they say:

The above will properly load only the first file found, and is more flexible than trying to do this via the vars_files language keyword.


Solution 3:

I encountered this problem in a setup where I needed to create multiple deployment environments (live, demo, sandbox) to the same physical server (not allowed virtual machines here), and then a script to deploy arbitrary svn repos

This required a directory tree of (optional) variable.yml files, that would merge ontop of each other and not throw an exception if any where missing

Start by enabling variable merging in ansible - note that this does shallow hash merging (1 level deep) and not fully recursive deep merge

ansible.cfg

[defaults]
hash_behaviour=merge ;; merge rather than replace dictionaries http://docs.ansible.com/ansible/intro_configuration.html###hash-behaviour

Ansible directory layout

/group_vars
└── all.yml

/playbooks
├── boostrap.yml
├── demo.yml
├── live.yml
└── sandbox.yml

/roles/deploy/
├── files
├── tasks
│   ├── includes.yml
│   ├── main.yml
└── vars
    ├── main.yml
    ├── project_1.yml
    ├── project_2.yml
    ├── demo
    │   ├── project_1.yml
    │   ├── project_2.yml   
    │   └── main.yml
    ├── live
    │   ├── project_1.yml
    │   ├── project_2.yml   
    │   └── main.yml
    └── sandbox
        ├── project_1.yml
        ├── project_2.yml   
        └── main.yml

roles/deploy/tasks/includes.yml

This is the main logic for a directory tree of optional variable files.

;; imports in this order:
;; - /roles/deploy/vars/main.yml
;; - /roles/deploy/vars/{{ project_name }}.yml
;; - /roles/deploy/vars/{{ project_name }}/main.yml
;; - /roles/deploy/vars/{{ project_name }}/{{ project_env }}.yml
- include_vars:
    dir: 'vars'
    files_matching: "{{ item }}"
    depth: 1
  with_items:
    - "main.yml"
    - "{{ project_name }}.yml"

- include_vars:
    dir: 'vars/{{ env_name }}'
    files_matching: "{{ item }}"
    depth: 1
  with_items:
    - "main.yml"
    - "{{ project_name }}.yml"

group_vars/all.yml

Configure default variables for the project and various users and environments

project_users:
    bootstrap:
        env:   bootstrap
        user:  ansible
        group: ansible
        mode:  755
        root:  /cs/ansible/
        home:  /cs/ansible/home/ansible/
        directories:
            - /cs/ansible/
            - /cs/ansible/home/

    live:
        env:   live
        user:  ansible-live
        group: ansible
        mode:  755
        root:  /cs/ansible/live/
        home:  /cs/ansible/home/ansible-live/

    demo:
        env:   demo
        user:  ansible-demo
        group: ansible
        mode:  755
        root:  /cs/ansible/demo/
        home:  /cs/ansible/home/ansible-demo/

    sandbox:
        env:   sandbox
        user:  ansible-sandbox
        group: ansible
        mode:  755
        root:  /cs/ansible/sandbox/
        home:  /cs/ansible/home/ansible-sandbox/    

project_env:  bootstrap
project_user: "{{ ansible_users[project_env] }}" ;; this will be retroactively updated if project_env is redefined later

roles/deploy/vars/main.yml

project defaults

ansible_project:
  node_env:   development
  node_port:  4200
  nginx_port: 4400

roles/deploy/vars/project_1.yml

defaults for project_1

ansible_project:
  node_port:  4201
  nginx_port: 4401

roles/deploy/vars/live/main.yml

defaults for live environment, overrides project defaults

ansible_project:
  node_env: production

roles/deploy/vars/live/project_1.yml

final overrides for project_1 in the live environment

ansible_project:
  nginx_port: 80

playbooks/demo.yml

Configure separate playbooks for each environment

- hosts: shared_server
  remote_user: ansible-demo
  vars:
    project_env: demo
  pre_tasks:
    - debug: "msg='{{ facter_gid }}@{{ facter_fqdn }} ({{ server_pseudonym }})'"
    - debug: var=project_ssh_user
  roles:
    - { role: deploy, project_name: project_1 }

WARNING: Because all environments live on a single host, all playbooks must be run individually, otherwise Ansible will brokenly attempt to run all scripts as the first ssh login user and only use the variables for the first user. If you need to run all scripts sequentially, then use xargs to run them each as separate commands.

find ./playbooks/*.yml | xargs -L1 time ansible-playbook

Solution 4:

- hosts: all
  vars_files: vars/vars.default.yml
  vars:
    optional_vars_file: "{{ lookup('first_found', 'vars/vars.yml', errors='ignore') }}"
  tasks:
  - when: optional_vars_file is file
    include_vars: "{{ optional_vars_file }}"

Note: The path tests (is file, is exists,...) work only with absolute paths or paths relative to the current working directory when running ansible-playbook command. This is the reason we used the lookup. the lookup accepts paths relative to the playbook directory and returns the absolute path when the file exists.

Tags:

Ansible