Reputation: 33
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):
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?
Upvotes: 3
Views: 6839
Reputation: 68439
There is no way to do it using a single variable name.
The only workaround I can think of is using set_fact
:
- set_fact:
location: "{{ lookup('env', 'LOCATION' ) | ternary (lookup('env', 'LOCATION' ), omit) }}"
This way if environment variable LOCATION
does not exist/is empty, the task will not assign a value (omit
) and the default from the role will be used (i.e. it will not get overridden).
You could either run place it in pre_tasks
before roles are called, or at the top of roles tasks/main.yml
Rationale:
The whole problem you brought is using a single variable name throughout the process, but:
vars
section, it has precedence over the one defined in defaults
(even though evaluation will take place later - lookup plugin is irrelevant to the problem)Ansible processes the "precedence chain" for variables (before running any task) and once it encounters a definition, it stops. There is no further "going back" at execution time, when the actual values are evaluated.
Remark:
I have been able to come up with a way to "work around" this:
[ ]
now I am defining "defaults" in the "vars" area instead of in "defaults" area.
But you don't have to. There is no requirement to put your default_location
variable into vars
. You can define it in defaults
(the value is used at the execution time).
Upvotes: 2