Ansible variable 'null' vs. 'None' - ansible

From what I understood reading about the topic, null and "{{ None }}" are basically the same thing in Ansible. The difference is that the former is language agnostic YAML syntax and the latter is Python specific (I do not speak that language so I do not know how correct that is). However, as the following Ansible playbook shows, they are different.
- hosts: localhost
vars:
a: null
b: "{{ None }}"
tasks:
- name: a always
debug:
var: a
- name: a if none
debug:
var: a
when: a==None
- name: a if not none
debug:
var: a
when: a!=None
- name: b always
debug:
var: b
- name: b if none
debug:
var: b
when: b==None
- name: b if not none
debug:
var: b
when: b!=None
The output from the above is this:
PLAY [localhost] *******************************************************************************************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************************************************************************************
ok: [localhost]
TASK [a always] ********************************************************************************************************************************************************************************
ok: [localhost] => {
"a": null
}
TASK [a if none] *******************************************************************************************************************************************************************************
ok: [localhost] => {
"a": null
}
TASK [a if not none] ***************************************************************************************************************************************************************************
skipping: [localhost]
TASK [b always] ********************************************************************************************************************************************************************************
ok: [localhost] => {
"b": ""
}
TASK [b if none] *******************************************************************************************************************************************************************************
skipping: [localhost]
TASK [b if not none] ***************************************************************************************************************************************************************************
ok: [localhost] => {
"b": ""
}
PLAY RECAP *************************************************************************************************************************************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
I am not so much concerned about the output null vs. "", but what really bothers me is the different test results a==None compared to b==None.
Can somebody please help me understand what I am missing here?

Regarding
From what I understood reading about the topic, null and "{{ None }}" are basically the same thing in Ansible.
there was a longer conversation under Ansible Issue #7984 "Ansible substitutes YAML null value with "None" in templates".
The difference is that the former is language agnostic YAML syntax and the latter is Python specific
Right, such is discussed under the above mentioned issue.
From the discussion there I understand also that such test is meant for testing defined. And since
To check if it is defined and truthy ... The string "None" evaluates as truthy and defined, the actual python None evaluates as "falsey" and defined.
a test might look like
---
- hosts: localhost
become: false
gather_facts: false
vars:
a: null
b: "{{ None }}"
tasks:
- name: a if none
debug:
var: a
when: a == None
- name: b if none
debug:
var: b
when: b is defined and not b
Further Discussions and Documentation
Ansible Issue #37441 "Ansible incorrectly evaluates 'null' as a variable name in when statements"
Ansible: How to check if a variable is not null?
How to create a null default in Ansible?
Making variables optional
If you are interested more in they types of the variables you may have a look into
---
- hosts: localhost
become: false
gather_facts: false
vars:
a: null
b: "{{ None }}"
c: !!null
tasks:
- name: a if none
debug:
msg: 'a: "{{ a }}" is of {{ a | type_debug }}'
when: a == None
- name: b if none
debug:
msg: 'b: "{{ b }}" is of {{ b | type_debug }}'
when: b is defined and not b
- name: c if none
debug:
msg: "{{ c }}"
when: c == None
resulting into an output of
TASK [a if none] ************
ok: [localhost] =>
msg: 'a: "" is of NoneType'
TASK [b if none] ************
ok: [localhost] =>
msg: 'b: "" is of unicode'
TASK [c if none] ************
ok: [localhost] =>
msg: null

Related

How to use custom fact in Ansible dynamically to evaluate conditional statement

I'm trying to evaluate the condition
hostvars[inventory_hostname].external_ip == ansible_facts['ipify_ip']
The external_ip is set on the host_variable . I have set the ipify_ip on the custom facts and trying to evaluate this condition
when: hostvars[inventory_hostname].external_ip == ansible_facts['ipify_ip']
I have tried this option also but it failed there too
when: hostvars[inventory_hostname].external_ip == {{ ipify_ip }}
Here is the complete playbook file
- name: Get public ip for the host
ipify_facts:
api_url: https://api.ipify.org/
timeout: 20
tags: always
- name: Set fact
set_fact:
ipify_ip: "{{ ipify_public_ip }}"
tags: always
- name: ipify_ip External IP
debug:
var: ipify_ip
tags: always
- name: is extrnal_ip in hostvars is same with ipify_ip
debug:
var=hostvars[inventory_hostname].external_ip
when: hostvars[inventory_hostname].external_ip == ansible_facts['ipify_ip']
tags: always
The error
fatal: [localhost]: FAILED! => {"msg": "The conditional check 'hostvars[inventory_hostname].external_ip == ansible_facts['ipify_ip']' failed. The error was: error while evaluating conditional (hostvars[inventory_hostname].external_ip == ansible_facts['ipify_ip']): 'dict object' has no attribute 'ipify_ip'\n\nThe error appears to be in '/home/anish/playbook/prereqs.yml': line 29, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - name: extrnal_ip on hostvars\n ^ here\n"}
What is the right way to do this ??
Simply use the variables. For example, given the inventory
shell> cat hosts
host01 external_ip=1.2.3.4
and the playbook
shell> cat playbook.yml
- hosts: host01
tasks:
- set_fact:
ipify_ip: 1.2.3.4
- debug:
var: ipify_ip
- debug:
var: external_ip
- debug:
msg: "{{ external_ip }} is equal to {{ ipify_ip }}"
when: external_ip == ipify_ip
gives
shell> ansible-playbook -i hosts playbook.yml
PLAY [host01] *******************************
TASK [set_fact] *****************************
ok: [host01]
TASK [debug] ********************************
ok: [host01] =>
ipify_ip: 1.2.3.4
TASK [debug] ********************************
ok: [host01] =>
external_ip: 1.2.3.4
TASK [debug] ********************************
ok: [host01] =>
msg: 1.2.3.4 is equal to 1.2.3.4
PLAY RECAP **********************************
host01: ok=4 changed=0 unreachable=0 failed=0 ...

How to run an Ansible task if *any* host has a fact

I'm wrapping my head around something that I'm probably overcomplicating.
I need to check if any of my hosts has ansible_virtualization_type == "openvz"
If this is the case, ALL hosts should execute a specific task.
I'm now trying to set a fact (virt_list) containing a list of hosts with their virtualization_type on localhost:
- name: Set fuse on virtualization OpenVZ
set_fact:
virt_list:
host: "{{item}}"
type: "openvz"
when: hostvars[item].ansible_virtualization_type == "openvz"
with_items: "{{ groups['all'] }}"
delegate_to: localhost
delegate_facts: true
but this doesn't work (both hosts in this play are on openvz):
TASK [roles/testvirt : debug vars ansible_virtualization_type ] ****************************
ok: [host1] => {
"ansible_virtualization_type": "openvz"
}
ok: [host2] => {
"ansible_virtualization_type": "openvz"
}
TASK [roles/testvirt : debug vars virt_list ] **********************************************
ok: [host1] => {
"msg": [
{
"host": "host1",
"type": "openvz"
}
]
}
ok: [host2] => {
"msg": [
{
"host": "host2",
"type": "openvz"
}
]
}
There should be a simpler way, maybe using jinjia2 to combine the lists directly.
Anyone has advices?
Q: "If any of my hosts has ansible_virtualization_type == "openvz" ALL hosts should execute a specific task."
A: For example, given the inventory for testing
shell> cat hosts
host1 ansible_virtualization_type=xen
host2 ansible_virtualization_type=xen
host3 ansible_virtualization_type=openvz
extract the variables
- debug:
msg: "{{ ansible_play_hosts|
map('extract', hostvars, 'ansible_virtualization_type')|
list }}"
run_once: true
gives
msg:
- xen
- xen
- openvz
Test if the type is present
- debug:
msg: OK. ALL hosts should execute a specific task.
when: "'openvz' in vtypes"
vars:
vtypes: "{{ ansible_play_hosts|
map('extract', hostvars, 'ansible_virtualization_type')|
list }}"
run_once: true
gives
msg: OK. ALL hosts should execute a specific task.
If this is working as expected proceed with all hosts
- set_fact:
all_hosts_execute_specific_task: true
when: "'openvz' in vtypes"
vars:
vtypes: "{{ ansible_play_hosts|
map('extract', hostvars, 'ansible_virtualization_type')|
list }}"
run_once: true
- debug:
msg: Execute a specific task.
when: all_hosts_execute_specific_task|default(false)
gives
TASK [set_fact] ************************************************************
ok: [host1]
TASK [debug] ***************************************************************
ok: [host1] =>
msg: Execute a specific task.
ok: [host3] =>
msg: Execute a specific task.
ok: [host2] =>
msg: Execute a specific task.
The task will be skipped if the type is missing.

Ansible loop giving warning found a duplicate dict key (when). Using last defined value only

I am trying to iterate over an array and assign the value to variables hooks_enabled, workflow_artifact_id, workflow_version, one by one in every iteration and perform a specific task (currently debug, later change to Helm install command).
Code:
---
- name: Executing Ansible Playbook
hosts: localhost
become: yes
become_user: someuser
pre_tasks:
- include_vars: global_vars.yaml
- name: Print some debug information
set_fact:
all_vars: |
Content of vars
--------------------------------
{{ vars | to_nice_json }}
tasks:
- name: Iterate over an array
set_fact:
hooks_enabled: '{{ array_item1_hooks_enabled }}'
workflow_artifact_id: '{{ array_item1_workflow_artifact_id }}'
workflow_version: '{{ array_item1_workflow_version }}'
when: "item == 'array_item1'"
set_fact:
hooks_enabled: '{{ array_item2_hooks_enabled }}'
workflow_artifact_id: '{{ array_item2_workflow_artifact_id }}'
workflow_version: '{{ array_item2_workflow_version }}'
when: "item == 'array_item2'"
with_items: "{{ array}}"
# Change debug with helm install command
- debug:
msg: " id= '{{ workflow_artifact_id }}'"
The issue I am facing is, only the last when is considered and others are skipped
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
[WARNING]: While constructing a mapping from /c/ansible-test/second.yaml, line 16, column 7, found a duplicate dict key (set_fact). Using last defined value only.
[WARNING]: While constructing a mapping from /c/ansible-test/second.yaml, line 16, column 7, found a duplicate dict key (when). Using last defined value only.
PLAY [Executing Ansible Playbook] *********************************************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************************************************
ok: [localhost]
TASK [include_vars] ***********************************************************************************************************************************************************************************************
ok: [localhost]
TASK [Print some debug information] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [Iterate over an array] **************************************************************************************************************************************************************************************
skipping: [localhost] => (item=array_item1)
ok: [localhost] => (item=array_item2)
skipping: [localhost] => (item=array_item3)
skipping: [localhost] => (item=array_item4)
skipping: [localhost] => (item=array_item5)
TASK [debug] ******************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": " id= 'algorithm-Workflow'"
}
PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
How do I modify the block to enable all the when statement execute and later use helm install command to take the variables one by one.
I would go with a dynamic variable construction using the vars lookup.
Something along the lines of:
- set_fact:
hooks_enabled: "{{ lookup('vars', item ~ '_hooks_enabled') }}"
workflow_artifact_id: "{{ lookup('vars', item ~ '_workflow_artifact_id') }}"
workflow_version: "{{ lookup('vars', item ~ '_workflow_version') }}"
when: "item in ['array_item1', 'array_item2']"
with_items: "{{ array }}"

Or condition is not working with set_facts in my playbook

I created one playbook to run the tasks based on the test case so I have created like below
Here when I pass the ansible-playbook playbook.yml -e stage=1, it's skipping all the tasks, and when I debug the test_case* values I could see both are in a false state, So can some help me to work this code.
---
- name: test
hosts: localhost
tasks:
- name: setting the level
set_fact:
test_case_1: "{{ stage == 1 }}"
test_case_2: "{{ stage == 1 or stage == 2 }}"
- name: "running ls command"
shell: "ls -l"
register: testing
when:
- test_case_1 == true
- debug:
msg: "{{ testing.stdout_lines }}"
when:
- test_case_1 == true
- name: "kickoff"
shell: "df -Th"
register: kick
when:
- test_case_2 == true
- name: "printing kickoff"
debug:
msg: "{{ kick.stdout_lines }}"
when:
- test_case_2 == true
Below is the error results which I am getting
[root#server ~]# ansible-playbook playbook.yml -e stage=1
PLAY [test] ***********************************************************************************************************
TASK [Gathering Facts] ************************************************************************************************
ok: [localhost]
TASK [setting the level] **********************************************************************************************
ok: [localhost]
TASK [running ls command] *********************************************************************************************
skipping: [localhost]
TASK [debug] **********************************************************************************************************
skipping: [localhost]
TASK [kickoff] ********************************************************************************************************
skipping: [localhost]
TASK [printing kickoff] ***********************************************************************************************
skipping: [localhost]
PLAY RECAP ************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=4 rescued=0 ignored=0
[root#server ~]#
expected results should be, it should execute all the tasks from the play.
Your problem is that you are performing an integer comparison (stage == 1), but when you provide a value on the command line via -e stage=1, you are setting a string value.
You probably want to case the value of stage to an integer using the int filter.
---
- name: test
hosts: localhost
tasks:
- name: setting the level
set_fact:
test_case_1: "{{ stage|int == 1 }}"
test_case_2: "{{ stage|int == 1 or stage|int == 2 }}"
With this change, things seem to work as expected.
Unrelated to your question, but you could rewrite the second test like this:
{{ stage|int in [1, 2] }}
That simplifies things a bit.

How to pair hosts with variables in ansible

I have an inventory like:
all:
children:
server_group1:
hosts:
host1:
server_group2:
children:
app1:
hosts:
host2:
host3:
app2:
hosts:
host4:
host5:
server_group3:
...
I have organized my server variables like so:
> cat group_vars/server_group2/app1
app1:
name1: value1
name2: value2
> cat group_vars/server_group2/app2
app2:
name1: value11
name2: value21
I am trying to name my dict after the group (thus making them unique) and access it in my playbook:
hosts: server_group2
tasks:
- name: check file
local_action: stat path=path/to/test/{{hostvars[0].name1}}
register: payld_txt
- name: conditional transfer
copy:
src: path/to/test/{{hostvars[0].name1}}
dest: /svr/path/{{hostvars[0].name2}}
when: payld_txt.stat.exists
I end up with this error:
The task includes an option with an undefined variable. The error was: 'name1' is undefined
Where am I going wrong?
Before you go any further, you need to fix your inventory which does not respect ansible's structure for yaml sources. A simple command as the following can give you some hints:
$ ansible -i inventories/test.yml all --list-hosts
[WARNING]: Skipping unexpected key (server_group1) in group (all), only "vars", "children" and "hosts" are valid
[WARNING]: Skipping unexpected key (server_group2) in group (all), only "vars", "children" and "hosts" are valid
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
hosts (0):
The correct syntax is:
---
all:
children:
server_group1:
hosts:
host1:
server_group2:
children:
app1:
hosts:
host2:
host3:
app2:
hosts:
host4:
host5:
Which now gives:
$ ansible -i inventories/test.yml all --list-hosts
hosts (5):
host1
host2
host3
host4
host5
``hostvars[0].name1`` The error was: 'name1' is undefined
Q: "Where am I going wrong?"
A: The variable name1 is item of the dictionary app1 or app2. It must be referenced app1.name1 or app2.name1. In addition to this,
hostvars is a dictionary not an array. hostvars[0] does not exist. An item of a dictionary must be referenced by a key. For example the play below
- hosts: server_group2
tasks:
- set_fact:
my_keys: "{{ hostvars.keys()|list }}"
run_once: true
- debug:
var: my_keys
run_once: true
- debug:
msg: "{{ hostvars[item].app1.name1 }}"
loop: "{{ my_keys }}"
when: "item in groups['app1']"
run_once: true
- debug:
msg: "{{ hostvars[item].app2.name1 }}"
loop: "{{ my_keys }}"
when: "item in groups['app2']"
run_once: true
gives
ok: [host5] =>
my_keys:
- host5
- host4
- host3
- host2
- host1
ok: [host5] => (item=host3) =>
msg: value1
ok: [host5] => (item=host2) =>
msg: value1
ok: [host5] => (item=host5) =>
msg: value11
ok: [host5] => (item=host4) =>
msg: value11
Optionally use json_query to create the list of the keys
- set_fact:
my_keys: "{{ hostvars|dict2items|json_query('[].key') }}"
run_once: true
The simplified version of the playbook
- hosts: server_group2
tasks:
- debug:
msg: "{{ hostvars[inventory_hostname].app1.name1 }}"
when: "inventory_hostname in groups['app1']"
- debug:
msg: "{{ hostvars[inventory_hostname].app2.name1 }}"
when: "inventory_hostname in groups['app2']"
gives
skipping: [host5]
skipping: [host4]
ok: [host3] =>
msg: value1
ok: [host2] =>
msg: value1
ok: [host5] =>
msg: value11
ok: [host4] =>
msg: value11
skipping: [host3]
skipping: [host2]
In fact, addressing hostvars[inventory_hostname] is not necessary. The simplified tasks below give the same output.
- debug:
msg: "{{ app1.name1 }}"
when: "inventory_hostname in groups['app1']"
- debug:
msg: "{{ app2.name1 }}"
when: "inventory_hostname in groups['app2']"

Resources