Ansible playbook condition fails when variable has a default value - ansible

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

Related

Ansible if else with vars passing to different playbook

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

Ansible vars using lookups

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 }}"

Ansible : evaluate variable inside a variable with register

I have a task where i need to evaluate a variable attribute , where the name is already a vaiable.
here is the scenario :
i'm executing a shell command ( docker ps ) and i'm registering the output in a variable , where the name is already dynamic:
- name : Display running containers for {{apiType}}
shell: docker ps
register: docker_containers_{{apiType}}
when:
- '"containers" in type'
no i want to display the content of that ouput and not only the string itself , so i need to do something like this:
- name: Display running containers for {{apiType}}
debug:
msg: {{docker_containers_{{apiType}}.stdout}}
when:
- '"containers" in type'
of course , this : {{docker_containers_{{apiType}}.stdout}}
is syntaxically refused
i ve tried this : {{docker_containers_[apiType].stdout}}
but it fails.
Suggestions?
This is a FAQ. You can build a string and use that to index the hostvars for your current host:
- name: Display running containers for {{apiType}}
debug:
msg: "{{ hostvars[inventory_hostname]['docker_containers_' + apiType].stdout}}"
when:
- '"containers" in type'
...this assumes that your docker_containers_... variable is a host fact, rather than, say, something set via group_vars or a vars stanza in your playbook.
Here's a runnable example:
- hosts: localhost
gather_facts: false
vars:
apiType: foo
tasks:
- set_fact:
docker_containers_foo:
stdout: "this is foo"
- set_fact:
docker_containers_bar:
stdout: "this is bar"
- name: Display running containers for {{apiType}}
debug:
msg: "{{ hostvars[inventory_hostname]['docker_containers_' + apiType].stdout}}"

Return Variable from Included Ansible Playbook

I have seen how to register variables within tasks in an ansible playbook and then use those variables elsewhere in the same playbook, but can you register a variable in an included playbook and then access those variables back in the original playbook?
Here is what I am trying to accomplish:
This is my main playbook:
- include: sub-playbook.yml job_url="http://some-jenkins-job"
- hosts: localhost
roles:
- some_role
sub-playbook.yml:
---
- hosts: localhost
tasks:
- name: Collect info from Jenkins Job
script: whatever.py --url "{{ job_url }}"
register: jenkins_artifacts
I'd like to be able to access the jenkins_artifacts results back in main_playbook if possible. I know you can access it from other hosts in the same playbook like this: "{{ hostvars['localhost']['jenkins_artifacts'].stdout_lines }}"
Is it the same idea for sharing across playbooks?
I'm confused what this question is about. Just use the variable name jenkins_artifacts:
- include: sub-playbook.yml job_url="http://some-jenkins-job"
- hosts: localhost
debug:
var: jenkins_artifacts
This might seem complicated but I love doing this in my Playbooks:
rc defines the name of the variable which contains the return value
ar gives the arguments to the include tasks
master.yml:
- name: verify_os
include_tasks: "verify_os/main.yml"
vars:
verify_os:
rc: "isos_present"
ar:
image: "{{ os.ar.to_os }}"
verify_os/main.yml:
---
- name: check image on device
ios_command:
commands:
- "sh bootflash: | inc {{ verify_os.ar.image }}"
register: image_check
- name: check if available
shell: "printf '{{ image_check.stdout_lines[0][0] }}\n' | grep {{ verify_os.ar.image }} | wc -l"
register: image_available
delegate_to: localhost
- set_fact: { "{{ verify_os.rc }}": "{{ true if image_available.stdout == '1' else false }}" }
...
I can now use the isos_present variable anywhere in the master.yml to access the returned value.

Set Ansible variable to undefined through extra-vars or inventory variable

So I have an Ansible playbook that looks like
---
- hosts: mygroup
tasks:
- debug:
msg: "{{ foo | default(inventory_hostname) }}"
My inventory file looks like
[mygroup]
127.0.0.1
Since foo is not defined anywhere, the debug prints 127.0.0.1 as expected.
But suppose my inventory file looks like
[mygroup]
127.0.0.1 foo=null
When I run the playbook, it prints out the string null. I also tried with foo=None and it prints an empty string. How can set the variable to null through inventory or extra-vars?
This may be useful when I want to unset a variable already defined in a playbook.
I am using Ansible version 2.1.1.0.
Python (hence Ansible) differentiates between an undefined variable and a variable with the none value.
There is no way to "undefine" a variable once it has been defined.
In result even if you set the value to none, the condition you specified will never consider the variable as undefined.
You get a "" in the output log because this is how debug module displays the none-value, not because it's an empty string.
Solution 1
Use a ternary operator with the condition to check for the actual value of foo variable:
- debug:
msg: "{{ ((foo is defined) and (foo != None)) | ternary(foo, inventory_hostname) }}"
Solution 2
Use a "wrapper" dictionary:
Define a default value for the variable inside a "wrapper" dictionary:
foodict:
foo: bar
In the play refer the variable as foodict.foo:
---
- hosts: mygroup
tasks:
- debug:
msg: "{{ foodict.foo | default(inventory_hostname) }}"
Override the value in the inventory file by nullifying the "wrapper" dictionary:
[mygroup]
127.0.0.1 foodict=None

Resources