I am trying to pass variables to another playbook through var with an if else condition
- hosts: localhost
- import_playbook: merge.yml
vars:
variables:
- "{{ {'test: 'xyz'} if ( flag == true) else {'test': 'abc'} }}"
when I print the value inside merge.yml, it always prints test: abc. I am executing the playbook with the command, ansible-playbook test.yml -e flag=true
Is this a supported syntax? Is there anyway I can use if else with vars ?
I think your core problem is that Ansible will treat your -e "flag=true" as a string, not a bool. Therefore whatever you set it to, it will always be true.
Also, you have to be careful with what you place inside {{}}. It is tempting to think it is full blown Python, when in fact it is Jinja2. Therefore you would be better sticking to pure Jinja2 as much as possible. As an alternative, this works for me:
---
- import_playbook: merge.yml
vars:
variables: "{{ ( flag | default(false) ) | bool | ternary( {'test': 'xyz'}, {'test': 'abc'} ) }}"
( flag | default(false) ) provides a default value for flag in the event that it is not passed by -e. Note, it all needs to be wrapped in (), otherwise only the output of default(false) will be evaluated by the rest of the statement.
bool forces Ansible to translate the string into a boolean
ternary( {'test': 'xyz'}, {'test': 'abc'} ) will use the first value in the case it is passed true and the second if false
A couple of other thoughts:
you don't need - hosts: localhost given you have no tasks attached to it.
do you have a specific need to use import_playbook? A more common pattern would be to have your merge tasks in an include file, rather than a full blown playbook:
playbook.yml
---
- hosts: localhost
connection: local
tasks:
- include: merge.yml
vars:
variables: "{{ ( flag | default(false) ) | bool | ternary( {'test': 'xyz'}, {'test': 'abc'} ) }}"
then merge.yml
---
- debug:
var: variables
Related
How do I get a value from an environment variable, but use a default if the environment variable is unset?
This is an example that does not work
---
- name: a playbook
hosts: all
vars:
build_dir: "{{ lookup('env','BUILD_DIR') | default('builds/1.0.0/LATEST') }}"
tasks:
- debug: msg="{{ build_dir }}"
Running this playbook returns an empty string instead of the default.
$ ansible-playbook build.yml
TASK [debug] ********************
ok: [amber] => {
"msg": ""
}
However, it works as expected to obtain the environment variable.
$ BUILD_DIR=LOL ansible-playbook build.yml
TASK [debug] ****************
ok: [amber] => {
"msg": "LOL"
}
Discovered this that is more concise and easier to read than some other options I have seen
"{{ lookup('env','BUILD_DIR') or 'builds/1.0.0/LATEST' }}"
The last parameter to Jinja's default template built-in function should be true, like this:
vars:
build_dir: "{{ lookup('env','BUILD_DIR')|d('builds/1.0.0/LATEST', true) }}"
Better not to have too many sources of truth, but I always try to set intelligent defaults in defaults/main.yml. I also make frequent use of the default() filter, like this:
db_url : "{{ DB_HOST }}:{{ db_port | default(1521) }}:{{ DB_SVC | default(SID|default('')) }}"
Then a playbook can always overwrite a role's variable with a lookup that defaults to a literal -
vars:
db_port: "{{ lookup('env','db_port')|default('9999') }}"
or with a value dynamically written into a vars_file before the play begins, or into the hosts file or groups file, or on the ansible command-line with --extra-vars, etc.
Look at the variable precedence rules, but be careful not to get too complex if it can be avoided. Flexibility is good, but KISS, else "that way lies madness..."
I have an Ansible playbook that populates some variables, here is a snippet:
#myTest playbook
---
- hosts: localhost
connection: local
become: False
vars:
- APP_NAME: "{{lookup( 'env', 'name')| mandatory }}"
I'd like to use another lookup first, and take that value if its been populated. Is this achievable in one line? I'm figuring something like Javascript's ||:
- APP_NAME: "{{lookup( 'env', 'customName') || lookup( 'env', 'name')| mandatory }}"
You can use the default filter with an option to trigger it if the value of the preceding expression is an empty string (as in the case of an undefined environment variable):
- APP_NAME: "{{ lookup('env', 'customName') | default(lookup('env', 'name'), true) | mandatory }}"
I am unable to source from Ansible documents a clear meaning of a conditional such as when: var | d(). Is someone able give a clear explanation?
E.g. Below works whether inputing extra-var value from cli or defaulting to local ENV variable value:
vars:
my_var: "{{ e_var | default(ansible_env.USER | default(False,true)) }}"
tasks:
- name: Conditional check
debug:
msg: "{{ my_var }}"
when: my_var | d()
But this fails:
vars:
my_var: "{{ e_var | default(ansible_env.USER | default(false,true)) }}"
tasks:
- name: Conditional check
debug:
msg: "{{ my_var }}"
when: my_var
What is when: my_var | d() exactly doing? How how does it interplay with the | default(false,true) part in the variable declaration?
d is an alias to the default filter. It is a Jinja2 filter, so head for the Jinja2 docs. They work the same:
default(value, default_value=u'', boolean=False)
[ ]
Aliases: d
Regarding the problem you are facing, it is because Ansible processes a condition consisting of only a variable name differently from a more complex expression (which is passed directly to Jinja2/Python) (the actual code starts here):
If the my_var variable has a value of user01, the conditional will try to find a value of user01 variable and fail because it doesn't exist.
If you just add a logical conjunction (which in common sense is redundant), Ansible will process the whole expression differently and it will work:
when: my_var and true
In your case using another default filter in the expression is also redundant, but it prevents Ansible from trying to resolve a "nested" variable value.
Given the following playbook (deployment.yml):
---
- name: Debug
hosts: applicationservers
tasks:
- debug: msg="{{add_host_entries | default('false')}}"
- debug: msg="{{add_host_entries | default('false') == 'true'}}"
- debug: msg="Add host entries = {{add_host_entries | default('false') == 'true'}}"
- include: add_host_entries.yml
when: add_host_entries | default('false') == 'true'
The condition to include add_host_entries.yml always fails, even if all of the above debug messages print some sort of true (I know that in the first debug message it's a String, whereas the other two result in Booleans).
When I omit the part with the default value, add_host_entries.yml will be executed:
when: add_host_entries
I need this default value behaviour though, because it's an optional value which is only set on certain stages.
Other Attempts (without success)
Brackets
when: (add_host_entries | default('false')) == 'true'
Casting to boolean
when: add_host_entries|default('false')|bool
Other Sources and Information
Here are all the resources needed to reproduce the problem.
add_host_entries.yml
---
- name: add_host_entries
hosts: applicationservers
gather_facts: false
tasks:
- debug: msg="Add Host Entries"
inventory
[applicationservers]
127.0.0.1
[all:vars]
add_host_entries=true
Call
markus#lubuntu:~/foobar$ ansible-playbook deployment.yml -i inventory
Versions
markus#lubuntu:~/foobar$ ansible --version
ansible 2.1.1.0
config file = /etc/ansible/ansible.cfg
configured module search path = Default w/o overrides
markus#lubuntu:~/foobar$ ansible-playbook --version
ansible-playbook 2.1.1.0
config file = /etc/ansible/ansible.cfg
configured module search path = Default w/o overrides
You try to conditionally include playbook. See my other answer about different include types.
The thing is, this only works when variable is defined before Ansible parses your playbook.
But you try to define add_host_entries as host-level fact (group variable) – these variables are not yet defined during parse time.
If you call your playbook with -e add_host_entries=true your condition will work as expected, because extra-vars are known during parse time.
Use bool to convert the string value of add_host_entries into a boolean and then the condition will work.
---
- name: Debug
hosts: applicationservers
tasks:
- debug: msg="{{add_host_entries | default('false')}}"
- debug: msg="{{add_host_entries | default('false') == 'true'}}"
- debug: msg="Add host entries = {{add_host_entries | default('false') == 'true'}}"
- include: add_host_entries.yml
when: add_host_entries | default('false') | bool
I'm customizing linux users creation inside my role. I need to let users of my role customize home_directory, group_name, name, password.
I was wondering if there's a more flexible way to cope with default values.
I know that the code below is possible:
- name: Create default
user:
name: "default_name"
when: my_variable is not defined
- name: Create custom
user:
name: "{{my_variable}}"
when: my_variable is defined
But as I mentioned, there's a lot of optional variables and this creates a lot of possibilities.
Is there something like the code above?
user:
name: "default_name", "{{my_variable}}"
The code should set name="default_name" when my_variable isn't defined.
I could set all variables on defaults/main.yml and create the user like that:
- name: Create user
user:
name: "{{my_variable}}"
But those variables are inside a really big hash and there are some hashes inside that hash that can't be a default.
You can use Jinja's default:
- name: Create user
user:
name: "{{ my_variable | default('default_value') }}"
Not totally related, but you can also check for both undefined AND empty (for e.g my_variable:) variable. (NOTE: only works with ansible version > 1.9, see: link)
- name: Create user
user:
name: "{{ ((my_variable == None) | ternary('default_value', my_variable)) \
if my_variable is defined else 'default_value' }}"
If anybody is looking for an option which handles nested variables, there are several such options in this github issue.
In short, you need to use "default" filter for every level of nested vars. For a variable "a.nested.var" it would look like:
- hosts: 'localhost'
tasks:
- debug:
msg: "{{ ((a | default({})).nested | default({}) ).var | default('bar') }}"
or you could set default values of empty dicts for each level of vars, maybe using "combine" filter. Or use "json_query" filter. But the option I chose seems simpler to me if you have only one level of nesting.
In case you using lookup to set default read from environment you have also set the second parameter of default to true:
- set_facts:
ansible_ssh_user: "{{ lookup('env', 'SSH_USER') | default('foo', true) }}"
You can also concatenate multiple default definitions:
- set_facts:
ansible_ssh_user: "{{ some_var.split('-')[1] | default(lookup('env','USER'), true) | default('foo') }}"
If you are assigning default value for boolean fact then ensure that no quotes is used inside default().
- name: create bool default
set_fact:
name: "{{ my_bool | default(true) }}"
For other variables used the same method given in verified answer.
- name: Create user
user:
name: "{{ my_variable | default('default_value') }}"
If you have a single play that you want to loop over the items, define that list in group_vars/all or somewhere else that makes sense:
all_items:
- first
- second
- third
- fourth
Then your task can look like this:
- name: List items or default list
debug:
var: item
with_items: "{{ varlist | default(all_items) }}"
Pass in varlist as a JSON array:
ansible-playbook <playbook_name> --extra-vars='{"varlist": [first,third]}'
Prior to that, you might also want a task that checks that each item in varlist is also in all_items:
- name: Ensure passed variables are in all_items
fail:
msg: "{{ item }} not in all_items list"
when: item not in all_items
with_items: "{{ varlist | default(all_items) }}"
The question is quite old, but what about:
- hosts: 'localhost'
tasks:
- debug:
msg: "{{ ( a | default({})).get('nested', {}).get('var','bar') }}"
It looks less cumbersome to me...
#Roman Kruglov mentioned json_query. It's perfect for nested queries.
An example of json_query sample playbook for existing and non-existing value:
- hosts: localhost
gather_facts: False
vars:
level1:
level2:
level3:
level4: "LEVEL4"
tasks:
- name: Print on existing level4
debug:
var: level1 | json_query('level2.level3.level4') # prints 'LEVEL4'
when: level1 | json_query('level2.level3.level4')
- name: Skip on inexistent level5
debug:
var: level1 | json_query('level2.level3.level4.level5') # skipped
when: level1 | json_query('level2.level3.level4.level5')
You can also use an if statement:
# Firewall manager: firewalld or ufw
firewall: "{{ 'firewalld' if ansible_os_family == 'RedHat' else 'ufw' }}"