My goal is to have a flexible Ansible role where the value of a variable can be provided in this order of precedence (greatest to least):
- from the command line using '--extra-vars'
- from environment variables
- from the defaults in the role
1 & 3 match the order of precedence documented for Ansible variables (http://docs.ansible.com/ansible/latest/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable), so I have focused my attention on the environment variables. Using the lookup('env',...)
plugin, I am able to read in the environment variable in the vars/main.yml
and the precedence order is exactly what I want.
However, when the environment variable is not defined, the lookup
plugin returns an empty string. This means that the variable is assigned the empty string instead of remaining undefined so that the default value can be assigned.
Playbook (var-exp.yml)
- name: Variable experiment
hosts: all
tasks:
- import_role:
name: ansible-role-variable-experiment
Role (ansible-role-variable-experiment)
tasks/main.yml
- name: Display value of 'location'
debug:
msg: 'location is {{ location }}'
defaults/main.yml
location: from-defaults-main-yml
vars/main.yml
# If used, this will override the value in defaults/main.yml, as expected
# location: from-vars-main-yml
# Since lookup returns '' when the environment variable doesn't exist,
# location gets set to '' instead of being left undefined so that the
# default can be used:
# location: "{{ lookup('env', 'LOCATION' ) }}" # -> location == ''
# When the environment variable does not exist, all of these options generate
# some value assigned to location so that the default cannot be assigned:
# location: "{{ lookup('env', 'LOCATION' ) | default(None, true) }}" # -> location == ''
# location: "{{ lookup('env', 'LOCATION' ) | default(omit, true) }}" # -> location == __omit_place_holder__3e8bdbb6cebc653a758afca99607fcf9ec1f99f4
# location: "{{ lookup('env', 'LOCATION' ) | default('undefined') }}" # -> location == ''
# location: "{{ lookup('env', 'LOCATION' ) | reject('undefined') }}" # -> location == <generator object select_or_reject at 0x10caaef00>
# When the environment variable does not exist, these generate a recursive
# loop that crashes the play:
# location: "{{ lookup('env', 'LOCATION' ) | default(location) }}"
# location: "{{ lookup('env', 'LOCATION' ) | default(location, true) }}"
Execution Examples
With environment variable:
LOCATION=from-env-variable ansible-playbook ./var-exp.yml
Without environment variable:
ansible-playbook ./var-exp.yml
I have not been able to identify a clean way to accomplish my goal. I have been able to come up with a way to "work around" this:
vars/main.yml
default_location: from-default-array-in-vars-main-yml
location: "{{ lookup('env', 'LOCATION' ) | default(default_location, true) }}"
And while this seems to accomplish what I want, now I am defining "defaults" in the "vars" area instead of in "defaults" area.
The Ansible documentation says something like "if you are doing something that seems too complicated, it probably is."
So, my question is: Is there an easier/better/correct way to accomplish this? Or, have I run into a limitation in the way Ansible 2.4.2 (or the lookup('env')
plugin) is currently implemented?