Checking whether nested variable is defined in the when statement - ansible

Nested data structures can be accessed either by indexing using the key name or dot syntax with the key name - https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#accessing-complex-variable-data
But I'm not able to access nested data structure from variables in the when statement. Here is a minimal sample to demonstrate the issue
# cat play.yml
- hosts: localhost
vars:
parent:
child1: true tasks:
- name: "Check if variable is defined"
fail:
msg: "mandatory variable {{ item }} not passed as extra args when invoking playbook"
when: item not in vars
loop:
- parent
- parent.child1
- parent.child2
Here is the sample output
ansible-playbook play.yml (livingstone-dev/monitoring)
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] *********************************************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [localhost]
TASK [Check if variable is defined] **************************************************************************************************************************
skipping: [localhost] => (item=parent)
failed: [localhost] (item=parent.child1) => {"ansible_loop_var": "item", "changed": false, "item": "parent.child1", "msg": "mandatory variable parent.child1 not passed as extra args when invoking playbook"}
failed: [localhost] (item=parent.child2) => {"ansible_loop_var": "item", "changed": false, "item": "parent.child2", "msg": "mandatory variable parent.child2 not passed as extra args when invoking playbook"}
PLAY RECAP ***************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
Any idea how I can have nested variables in ansible when statements. Here are some things which I've already tried and didn't work:
Using dotted notation for accessing variable
Changing the when statement to "item is not defined" without the quotes

Below code worked for me. You have to provide the values in loop within "{{}}"
- name: "Check if variable is defined"
fail:
msg: "mandatory variable {{ item }} not passed as extra args when invoking playbook"
loop:
- "{{parent.child1}}"
- "{{parent}}"
- "{{parent.child2}}"
when: item is not defined

To get the same result without warning change
when: "{{item}} is not defined"
to
when: not vars|json_query(item)

Here is the working playbook. Thanks to #smily for pointing me in the right direction. Since the loop variable item needs to be evaluated before the when condition is evaluated, I had to expand the variable item. I've done it by encompassing the entire when condition in double quotes and just expanding the item loop variable.
$ cat play.yml (livingstone-dev/monitoring)
- hosts: localhost
vars:
parent:
child1: true
tasks:
- name: show variable
debug:
var: parent
- name: "Check if variable is defined"
fail:
msg: "mandatory variable item not passed as extra args when invoking playbook"
when: "{{item}} is not defined"
loop:
- parent
- parent.child1
- parent.child2
Here is the playbook output.
$ ansible-playbook play.yml (livingstone-dev/monitoring)
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] *********************************************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************************************
ok: [localhost]
TASK [show variable] *****************************************************************************************************************************************
ok: [localhost] => {
"parent": {
"child1": true
}
}
TASK [Check if variable is defined] **************************************************************************************************************************
[WARNING]: conditional statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found: {{item}} is not defined
skipping: [localhost] => (item=parent)
skipping: [localhost] => (item=parent.child1)
failed: [localhost] (item=parent.child2) => {"ansible_loop_var": "item", "changed": false, "item": "parent.child2", "msg": "mandatory variable item not passed as extra args when invoking playbook"}
PLAY RECAP ***************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0

Related

pass a list/array of strings from my bash script, to an ansible script

I am trying to pass a list/array of strings from my bash script, to my ansible script:
Excerpt from bash script:
configureChrony() {
ntpServer="initVal"
ntpServers=()
while ! [[ -z "$ntpServer" ]]; do
read -e -p "Please input the ip address or domain name of the TP server you wish to add: " ntpServer
if ! [[ -z "$ntpServer" ]]; then
ntpServers+=($ntpServer)
fi
done
ansible-playbook -i localhost, test.yml --extra-vars="ntp_list = $ntpServers"
}
test.yml
---
- name: "This is a test"
hosts: all
gather_facts: no
tasks:
- name: print variable - with_items
debug:
msg: "{{ item.name }}"
with_items:
- "{{ ntp_list }}"
When testing the bash script, I get this error:
Which timekeeping service would you like to use [ntp/chrony]: chrony
Please input the ip address or domain name of the TP server you wish to add: Test1
Please input the ip address or domain name of the TP server you wish to add: Test2
Please input the ip address or domain name of the TP server you wish to add:
PLAY [This is a test] ****************************************************************************************************************************************
TASK [print variable - with_items] ***************************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "'ntp_list' is undefined"}
PLAY RECAP ***************************************************************************************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
The issue is the way I am passing the list/array from the bash script to ansible script as they both run given the required data.
The desired output is for each element of the list to be outputted to the screen.
Test1
Test2
Any help appreciated.
Direct answer
You cannot really pass the bash array as a list inside an extra var to ansible. It is possible but would require to loop over the bash array and transform it into a json parsable format that you inject inside the extra var.
The easiest way IMO is to pass a concatenation of all array element inside a string that you will later split in the playbook.
Using the form ${somevar[#]} won't work here as every bash array element will end up being parsed as a new argument to your ansible command. You will have to use instead ${somevar[*]}. You also need to quote the extra var correctly so that both bash and ansible are able to successfully parse it. The correct command call in your script is then:
ansible-playbook test.yml --extra-vars "ntp_list_raw='${ntpServers[*]}'"
You now need a bit of rework on your ansible playbook to split the received value into a list:
---
- name: "This is a test"
hosts: localhost
gather_facts: no
vars:
ntp_list: "{{ ntp_list_raw.split(' ') }}"
tasks:
- name: Print split variable items
debug:
var: item
loop: "{{ ntp_list }}"
And now entering values and calling the playbook from your script gives:
Please input the ip address or domain name of the TP server you wish to add: toto
Please input the ip address or domain name of the TP server you wish to add: pipo
Please input the ip address or domain name of the TP server you wish to add: bingo
Please input the ip address or domain name of the TP server you wish to add:
PLAY [This is a test] *********************************************************************
TASK [Print split variable items] *********************************************************************
ok: [localhost] => (item=toto) => {
"ansible_loop_var": "item",
"item": "toto"
}
ok: [localhost] => (item=pipo) => {
"ansible_loop_var": "item",
"item": "pipo"
}
ok: [localhost] => (item=bingo) => {
"ansible_loop_var": "item",
"item": "bingo"
}
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Going further
If your only goal is to ask for your ntp servers interactively, you can do all of the above directly in your playbook using vars_prompt
---
- name: "This is a test"
hosts: localhost
gather_facts: no
vars_prompt:
- name: ntp_list_raw
prompt: "Please enter ntp servers separated by a comma without spaces"
private: no
vars:
ntp_list: "{{ ntp_list_raw.split(',') }}"
tasks:
- name: Print split variable
debug:
var: item
loop: "{{ ntp_list }}"
which gives:
$ ansible-playbook test.yaml
Please enter ntp servers separated by a comma without spaces []: toto,pipo,bingo
PLAY [This is a test] *********************************************************************
TASK [Print split variable] *********************************************************************
ok: [localhost] => (item=toto) => {
"ansible_loop_var": "item",
"item": "toto"
}
ok: [localhost] => (item=pipo) => {
"ansible_loop_var": "item",
"item": "pipo"
}
ok: [localhost] => (item=bingo) => {
"ansible_loop_var": "item",
"item": "bingo"
}
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
You can even bypass the prompt giving the value as an extra var directly:
$ ansible-playbook test.yaml -e ntp_list_raw=toto,pipo,bingo
PLAY [This is a test] *********************************************************************
TASK [Print split variable] *********************************************************************
ok: [localhost] => (item=toto) => {
"ansible_loop_var": "item",
"item": "toto"
}
ok: [localhost] => (item=pipo) => {
"ansible_loop_var": "item",
"item": "pipo"
}
ok: [localhost] => (item=bingo) => {
"ansible_loop_var": "item",
"item": "bingo"
}
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

How to pass URLs as a variable to an ansible playbook

I created a playbook which clears cache, I'm trying to pass a url to a variable, and when I execute the playbook I get an empty array for that parameter.
In my playbook I have a vars module with this variable (environment), it gets defined when you pass in a variable to the ansible-playbook command
vars:
environment: "{{testenv}}"
-e testenv=https://www.test1.com
When I execute the playbook I get this error.
Do I need to format the url in someway?
fatal: [localhost]: FAILED! => {"changed": false, "msg": "unknown url type: '[]/content/clear_cache?
Your issue is coming from the fact that environment is a reserved variable, as pointed in the second row of this table in the documentation:
Valid variable names
 Not valid
 foo
 *foo, Python keywords such as async and lambda
 foo_env
 playbook keywords such as environment
 foo_port
 foo-port, foo port, foo.port
 foo5, _foo
 5foo, 12
Source: https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#creating-valid-variable-names
So, you just need to change your variable name to something else and it will work.
Given the playbook:
- hosts: all
gather_facts: no
tasks:
- debug:
msg: "{{ _environment }}"
vars:
_environment: "{{ testenv }}"
When run:
$ ansible-playbook play.yml -e testenv=https://www.test1.com
PLAY [all] **********************************************************************************************************
TASK [debug] ********************************************************************************************************
ok: [localhost] => {
"msg": "https://www.test1.com"
}
PLAY RECAP **********************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

How to initilize a dictonary to some value in ansible

I am trying to set a default value to element of the dictornary, following is my playbook showing incorrect way to set the value. Can someone tell to set dictname.key.value to default. ?
-->cat initilize_dict.yml
---
- hosts: localhost
vars:
dictname:
key: 'default'
value: 'default'
tasks:
- debug: var=dictname.key.value
- debug: var=dictname.key.['value']
Current output
-->ansible-playbook initilize_dict.yml
[WARNING]: Unable to parse /etc/ansible/hosts as an inventory source
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] *************************************************************************************************************************************************************************************************************************
TASK [Gathering Facts] *******************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] *****************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"dictname.key.value": "VARIABLE IS NOT DEFINED!"
}
TASK [debug] *****************************************************************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "template error while templating string: expected name or number. String: {{dictname.key.['value']}}"}
to retry, use: --limit #/home/monk/samples/initilize_dict.retry
PLAY RECAP *******************************************************************************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=1
You would have to use following syntax:
- set_fact:
dictname:
key:
{ 'value': 'default' }
To validate:
- debug: var=dictname.key.value

How to check few keywords in a string and assert if not present in Ansible tasks

I have a string like this
"3000 Native active Po121, Po123"
I need to check if 3000 is present and active is present in this string. If not assert
I wanted to use when command and set_fact. Check if the variable present and assert. (which I haven't finished). Right now I am just printing a message. This is not the good to way to do it. If I can assert directly when 3000 and active not present, that would be great.
Also another question about when, if it matches first condition, it prints the debug message. It should match both right as its an and?
var:
vlan_output: "3000 Native active Po121, Po123"
item={vlan_id: 3000, state: present}
I tried like this
- name: Validate vlan for delete
debug: msg="VLAN FAILED "
when: item.state == "present" and "item.vlan_id not in vlan_output"
We can directly use the when condition if "3000" and "active" is present in the output
I believe the vlan_id would be a registered variable so the value can be accessed using vlan_id.stdout.
The below play works with when and assert module of ansible
for assertion +ve:
command -->
ansible-playbook tmp.yml --extra-vars "vlan_output='3000 active'"
playbook -->
---
- hosts: localhost
tasks:
- debug:
msg: "Strings Matched"
when: vlan_output | search("3000") and vlan_output | search("active")
- debug:
var: vlan_output
- assert:
that:
- "'3000' in vlan_output"
- "'active' in vlan_output"
output -->
ok: [localhost] => {
"msg": "Strings Matched"
}
TASK [debug] *****************************************************************************************************************************
ok: [localhost] => {
"vlan_output": "3000 active"
}
TASK [assert] ****************************************************************************************************************************
ok: [localhost] => {
"changed": false,
"msg": "All assertions passed"
}
PLAY RECAP *******************************************************************************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0
for assertion -ve:
command -->
ansible-playbook tmp.yml --extra-vars "vlan_output='is'"
playbook -->
---
- hosts: localhost
tasks:
- debug:
msg: "Strings Matched"
when: vlan_output is not search("3000") and vlan_output is not search("active")
- debug:
var: vlan_output
- assert:
that:
- "'3000' not in vlan_output"
- "'active' not in vlan_output"
output -->
PLAY [localhost] *******************************************************************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************************************************************
ok: [localhost]
TASK [debug] ***********************************************************************************************************************************************************
ok: [localhost] => {
"msg": "Strings Matched"
}
TASK [debug] ***********************************************************************************************************************************************************
ok: [localhost] => {
"vlan_output": "is"
}
TASK [assert] **********************************************************************************************************************************************************
ok: [localhost] => {
"changed": false,
"msg": "All assertions passed"
}
PLAY RECAP *************************************************************************************************************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0
There's a couple of issues here
Firstly, you should not quote "item.vlan_id not in vlan_output" - this is a string and will always evaluate to True.
Secondly, the not in test requires the operands to be type string (currently vlan_id is an integer).
You should see the behaviour you are looking for with these changes:
vars:
vlan_output: "3000 Native active Po121, Po123"
item:
vlan_id: "3000"
state: present
tasks:
- debug: msg="VLAN FAILED"
when: item.state == "present" and item.vlan_id not in vlan_output

ansible: accessing register variables from other plays within same playbook

I'm trying to access the variable called "count" from the first "play" in my playbook in the second playbook. I found some other posts here about the same issue and I thought I was following the right steps, but the code below is still failing.
The Code
- hosts: group1
tasks:
- name: count registrations on primary node
shell: psql -U widgets widgets -c 'SELECT COUNT(*) FROM location' -t
register: count
- debug: var=count.stdout
- hosts: group2
tasks:
#the line below works...
# - debug: msg={{ hostvars['myserver1.mydomain.com']['count']['stdout'] }}
# but this one fails
- debug: msg={{ hostvars['group1']['count']['stdout'] }}
This produces the following output:
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ok: [myserver1.mydomain.com]
TASK [count registrations on node] **************************************
changed: [myserver1.mydomain.com]
TASK [debug] *******************************************************************
ok: [myserver1.mydomain.com] => {
"count.stdout": " 2"
}
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ok: [myserver2.mydomain.com]
TASK [debug] *******************************************************************
fatal: [myserver1.mydomain.com]: FAILED! => {"failed": true, "msg": "'ansible.vars.hostvars.HostVars object' has no attribute 'can_sip1'"}
NO MORE HOSTS LEFT *************************************************************
[ERROR]: Could not create retry file 'playbooks/test.retry'. The error was: [Errno 13] Permission denied: 'playbooks/test.retry'
PLAY RECAP *********************************************************************
myserver1.mydomain.com : ok=3 changed=1 unreachable=0 failed=0
myserver2.mydomain.com : ok=1 changed=0 unreachable=0 failed=1
The other post that I referring to is found here:
How do I set register a variable to persist between plays in ansible?
It's probably something simple, but I can't see where the bug lies.
Thanks.
EDIT 1
I've also tried to use set_fact like this:
- hosts: group1
tasks:
- name: count registrations on primary node
shell: psql -U widget widget -c 'SELECT COUNT(*) FROM location' -t
register: result
- debug: var=result.stdout
- set_fact: the_count=result.stdout
- debug: var={{the_count}}
- hosts: group2
tasks:
- name: retrieve variable from previous play
shell: echo hello
- debug: var={{hostvars}}
The results I get are:
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ok: [myserver1.mydomain.com]
TASK [count reg on primary] ****************************************************
changed: [myserver1.mydomain.com]
TASK [debug] *******************************************************************
ok: [myserver1.mydomain.com] => {
"result.stdout": " 2"
}
TASK [set_fact] ****************************************************************
ok: [myserver1.mydomain.com]
TASK [debug] *******************************************************************
ok: [myserver1.mydomain.com] => {
"result.stdout": " 2"
}
PLAY ***************************************************************************
TASK [setup] *******************************************************************
ok: [myserver2.mydomain.com]
TASK [retrieve variable from previous play] ************************************
changed: [myserver2.mydomain.com]
TASK [debug] *******************************************************************
ok: [myserver2.mydomain.com] => {
"<ansible.vars.hostvars.HostVars object at 0x7f3b6602b290>": "VARIABLE IS NOT DEFINED!"
}
PLAY RECAP *********************************************************************
myserver1.mydomain.com : ok=5 changed=1 unreachable=0 failed=0
myserver2.mydomain.com : ok=3 changed=1 unreachable=0 failed=0
So It looks like there are no objects in the hostvars...
EDIT 3
This is what the playbook looks like this morning.
- hosts: group1
tasks:
- name: count reg on primary
shell: psql -U widgets widgets -c 'SELECT COUNT(*) FROM location' -t
register: result
- debug: var=result.stdout
- set_fact: the_count={{result.stdout}}
- debug: var={{the_count}}
- hosts: group2
tasks:
- name: retrieve variable from previous play
shell: echo hello
- debug: var={{hostvars}}
The "debug: var={{the_count}}" line from the first play prints out the correct value for the count but it also says the VARIABLE IS NOT DEFINED... like so:
TASK [set_fact] ****************************************************************
task path: /etc/ansible/playbooks/test.yml:8
ok: [myserver1.mydomain.com] => {"ansible_facts": {"the_count": " 2"}, "changed": false, "invocation": {"module_args": {"the_count": " 2"}, "module_name": "set_fact"}}
TASK [debug] *******************************************************************
task path: /etc/ansible/playbooks/test.yml:10
ok: [myserver1.mydomain.com] => {
" 2": "VARIABLE IS NOT DEFINED!"
}
And then once I hit the second play, I still get the message
TASK [debug] *******************************************************************
task path: /etc/ansible/playbooks/test.yml:16
ok: [myserver2.mydomain.com] => {
"<ansible.vars.hostvars.HostVars object at 0x7fb077fdc310>": "VARIABLE IS NOT DEFINED!"
}
In your example, you are suggestion that I use "debug: var={{hostlers}}". If you can clarify that for me please. It looks like it's a typo.
EDIT 4:
If you take a look at Edit 3 carefully, you will see that I have implemented "debug:var={{hostvars}}" as you suggest in your answer. But it gives me the same error that the variable is not defined.
I'm not just trying to pass variables from one play to another.. but from one set of hosts to another. Notice how play 1 uses group1 and play two applies only to group2.
Register variables, like facts, are per host. The values can differ depending on the machine. So you can only use host/ip defined in the inventory as key, not the group name. I think you have already knowed this, as you marked this in code snippet 1.
In the code snippet 2, the set_fact line (- set_fact: the_count=result.stdout) actually set the key the_count to the text value result.stdout, since result.stdout is treated as plain text, not a variable. If you want to treat it as a variable, you'd better use {{ result.stdout }}. You can verify this via running the playbook with -v option.
Tasks:
set_fact: the_content1=content.stdout
set_fact: the_content2={{ content.stdout }}
Output:
TASK [set_fact] ****************************************************************
ok: [192.168.1.58] => {"ansible_facts": {"the_content1": "content.stdout"}, "changed": false}
TASK [set_fact] ****************************************************************
ok: [192.168.1.58] => {"ansible_facts": {"the_content2": "hello world"}, "changed": false}
The debug module has two possible parameter: var and msg. The var parameter expect a variable name.
debug: var={{hostvars}}
In this line, first of all, Ansible extracts the value of hostvars, since it is enclosed with two brackets. Secondly, it tries to find a variable whose name is the value of hostvars, since var parameter expects a variable name directly. That is why you see the following strange output. This means Ansible couldn't find a variable whose name is <ansible.vars.hostvars.HostVars object at 0x7f3b6602b290>.
"<ansible.vars.hostvars.HostVars object at 0x7f3b6602b290>": "VARIABLE IS NOT DEFINED!"
You can use the following:
debug: var=hostvars
debug: msg={{hostvars}}
References:
Register variables don't survive across plays with different hosts
set_fact - Set host facts from a task
debug - Print statements during execution

Resources