Conditionals against variables - linux

I am building a snapshot management playbook and that checks for an existing snap before taking a snap. I am having trouble testing a conditional when the playbook finds an existing snap. Here is a sample code:
---
- name: Test Snapshot
hosts: axwayT
gather_facts: false
vars_files:
- vault/creds.yml
vars:
mail_body_file: "/tmp/ansible_mail"
pre_tasks:
- name: Delete mail body file
file:
state: absent
path: "{{ mail_body_file }}"
delegate_to: localhost
run_once: true
- name: Create mail body file
file:
state: touch
path: "{{ mail_body_file }}"
delegate_to: localhost
run_once: true
tasks:
- name: find guest's folder using name
vmware_guest_find:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
validate_certs: no
name: "{{ vcenter_hostgroup_name }}"
datacenter: "{{ datacenter_name }}"
register: vm_folder
delegate_to: localhost
- name: Check for existing snapshots
vmware_guest:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
validate_certs: no
name: "{{ vcenter_hostgroup_name }}"
datacenter: "{{ datacenter_name }}"
folder: "{{vm_folder.folders[0]}}"
name: "{{ vcenter_hostgroup_name }}"
state: present
register: vm_state
delegate_to: localhost
- name: debug
debug:
var: vm_state.instance.current_snapshot
- name: Test
shell: echo "Found!"
when: vm_state.instance.current_snapshot !=""
I am working with an inventory of two servers for testing, nothing special. Here is the ouput I want to use a conditional against:
TASK [debug] ********************************************************************************************************************************************************************************************************************************
ok: [swipe901.test.com] => {
"vm_state.instance.current_snapshot": ""
}
ok: [swipe902.test.com] => {
"vm_state.instance.current_snapshot": {
"creation_time": "2023-02-07T21:14:06.812901+00:00",
"description": "",
"id": 13,
"name": "VM Snapshot 2%2f7%2f2023, 3:14:05 PM",
"state": "poweredOn"
}
}
I tried two when statements:
when: vm_state.instance.current_snapshot =""
when: vm_state.instance.current_snapshot is defined
My logic is way off and I am seeing my own limitations with programing logic, which I plan to fix soon.
My plan for the logic is to, skip a step, in my playbook if vm_state.instance.current_snapshot is "". How would this be handled?

Q: "I would like the condition to evaluate false if there is anything populated in current_snapshot."
A: For example, the playbook
shell> cat pb.yml
- name: Evaluate false if there is anything populated in current_snapshot
hosts: localhost
tasks:
- debug:
msg: The variable current_snapshot is either empty or does not exist.
when: current_snapshot|d('', true)|length == 0
gives
shell> ansible-playbook pb.yml
PLAY [Evaluate false if there is anything populated in current_snapshot] *************************************************************
TASK [debug] *************************************************************************************************************************
ok: [localhost] =>
msg: The variable current_snapshot is either empty or does not exist.
PLAY RECAP ***************************************************************************************************************************
localhost: ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
shell> ansible-playbook pb.yml -e current_snapshot=''
PLAY [Evaluate false if there is anything populated in current_snapshot] *************************************************************
TASK [debug] *************************************************************************************************************************
ok: [localhost] =>
msg: The variable current_snapshot is either empty or does not exist.
PLAY RECAP ***************************************************************************************************************************
localhost: ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
shell> ansible-playbook pb.yml -e current_snapshot='anything'
PLAY [Evaluate false if there is anything populated in current_snapshot] *************************************************************
TASK [debug] *************************************************************************************************************************
skipping: [localhost]
PLAY RECAP ***************************************************************************************************************************
localhost: ok=0 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
You can also use assert. For example,
shell> cat pb.yml
- name: Evaluate false if there is anything populated in current_snapshot
hosts: localhost
tasks:
- assert:
that: current_snapshot|d('', true)|length == 0
success_msg: The variable current_snapshot is either empty or does not exist.
fail_msg: The variable current_snapshot is populated.

Related

filter data From ansible_default_ipv4.macaddress

Dear community i ask for your help please.
I would create a playbook that will take a macaddress given in input and it should respect the Unix format. Then, if the address given respects the required format, another task will check if the given macaddress match an existed one in the hosts, **if it exists it will show the interface related to that macaddress if not the task will fail with an error message "No interfaces found with mac address '$MACADDRESS'. Fatal error."
here is what was done by me, i don't what should be done to achieve the goal:
- hosts: all
gather_facts: no
vars_prompt:
- name: "MACADDRESS"
prompt: "mac address?(please use Unix format ex- 1a:2b:3c:4d:5e:6f )"
private: no
tasks:
- name: check macaddresssyntax
debug:
msg: "{{ MACADDRESS | ansible.utils.hwaddr('unix') }}"
register: mac_syntax
failed_when: mac_syntax failed
- name: check local mac
debug:
var: mac_syntax |intersect (ansible_facts['default_ipv4']|selectattr('macaddress') | map(attribute='interface')|list)
You don't normally register the result of a debug task (because it's not useful). If you want to fail when someone enters an invalid MAC address, use the fail task:
- name: check mac address syntax
fail:
msg: "Invalid mac address syntax"
when: not MACADDRESS|ansible.utils.hwaddr('unix')
You have gather_facts set to false, but with fact gathering
disabled Ansible won't provide you with any facts. You need to
enable fact gathering if you want Ansible to provide you with
information about host interfaces.
I'm not entirely sure what you're trying to do in your check local mac task, but this seems to accomplish what you were asking about:
- hosts: localhost
gather_facts: true
vars_prompt:
- name: "MACADDRESS"
prompt: "mac address?(please use Unix format ex- 1a:2b:3c:4d:5e:6f )"
private: no
tasks:
- name: check macaddresssyntax
fail:
msg: "Invalid mac address syntax"
when: not MACADDRESS | ansible.utils.hwaddr('unix')
# In this task, we loop over `ansible_interfaces`, which is
# a list of interface names. We set the `matched_interface`
# variable to the current interface name if it has a
# matching MAC address.
- name: check local mac
set_fact:
matched_interface: "{{ ifname }}"
when: >-
lookup('vars', 'ansible_{}'.format(ifname)).macaddress|default(false) == MACADDRESS
vars:
# For whatever reason, ansible replaces `-` in interface
# names with `_`.
ifname: "{{ item.replace('-', '_') }}"
loop: "{{ ansible_interfaces }}"
- debug:
msg: "the interface related to {{ MACADDRESS }} is {{ matched_interface }}"
when: matched_interface|default(false)
- fail:
msg: "no interfaces found with mac address {{ MACADDRESS }}"
when: not matched_interface|default(false)
Running the above playbook on my system, which has an interface with address a2:29:ab:4e:e7:74, looks like this:
$ ansible-playbook playbook.yaml -e MACADDRESS='a2:29:ab:4e:e7:74
PLAY [localhost] ***************************************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [check macaddresssyntax] **************************************************
skipping: [localhost]
TASK [check local mac] *********************************************************
skipping: [localhost] => (item=veth92b9a89f)
...
ok: [localhost] => (item=example)
skipping: [localhost] => (item=lo)
...
TASK [debug] *******************************************************************
ok: [localhost] => {
"msg": "the interface related to a2:29:ab:4e:e7:74 is example"
}
TASK [fail] ********************************************************************
skipping: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
If I run it with a MAC address that doesn't exist on my system, I get:
TASK [check local mac] *********************************************************
skipping: [localhost] => (item=veth168ad56c)
...
TASK [debug] *******************************************************************
skipping: [localhost]
TASK [fail] ********************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "no interfaces found with mac address a2:29:ab:4e:e7:77"}
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=1 skipped=3 rescued=0 ignored=0
Collect the network's facts and get the list of interfaces. For example,
- setup:
gather_subset: network
- debug:
var: ansible_facts.interfaces
gives
ansible_facts.interfaces:
- lo0
- lo1
- pflog0
- wlan0
Put the declaration below as appropriate
_ifcs: "{{ ansible_facts.interfaces|map('extract', ansible_facts)|list }}"
_macs: "{{ _ifcs|json_query('[].macaddress') }}"
_devs: "{{ _ifcs|json_query('[].device') }}"
_macs_valid: "{{ _macs|map('ansible.utils.hwaddr', 'bool')|
map('ternary', true, false)|list }}"
_dev_mac_valid: "{{ _devs|zip(_macs)|zip(_macs_valid)|
map('flatten')|
selectattr(2) }}"
dev_mac_valid: "{{ _dev_mac_valid|map('zip', ['dev', 'mac', 'valid'])|
map('map', 'reverse')|
map('community.general.dict') }}"
dev_mac: "{{ dev_mac_valid|items2dict(key_name='dev', value_name='mac') }}"
mac_dev: "{{ dev_mac_valid|items2dict(key_name='mac', value_name='dev') }}"
For example, these evaluate to
_macs: [unknown, unknown, unknown, '00:12:00:34:56:78']
_macs_valid: [false, false, false, true]
_devs: [lo0, lo1, pflog0, wlan0]
_dev_mac_valid:
- [wlan0, '00:12:00:34:56:78', true]
dev_mac_valid:
- dev: wlan0
mac: 00:12:00:34:56:78
valid: true
dev_mac:
wlan0: 00:12:00:34:56:78
mac_dev:
00:12:00:34:56:78: wlan0
Q: "Check if the given macaddress match an existing one in the hosts"
A: Use the dictionary mac_dev. For example,
- pause:
prompt: "Enter MAC address"
register: result
- set_fact:
macaddress: "{{ result.user_input }}"
- assert:
that: macaddress|ansible.utils.hwaddr('bool')
fail_msg: Wrong MAC address
- assert:
that: macaddress in mac_dev.keys()|list
fail_msg: "No interfaces found with mac address {{ macaddress }}"
success_msg: "Interfaces {{ mac_dev[macaddress] }} found
with mac address {{ macaddress }}"
If there are more hosts declare a dictionary of all hosts
mac_dev_hosts: "{{ dict(ansible_play_hosts|
zip(ansible_play_hosts|
map('extract', hostvars, 'mac_dev'))) }}"
Then, putting it all together, for example, the playbook
- hosts: test_11,test_12,test_13
gather_facts: false
vars:
_ifcs: "{{ ansible_facts.interfaces|map('extract', ansible_facts)|list }}"
_macs: "{{ _ifcs|json_query('[].macaddress') }}"
_devs: "{{ _ifcs|json_query('[].device') }}"
_macs_valid: "{{ _macs|map('ansible.utils.hwaddr', 'bool')|
map('ternary', true, false)|list }}"
_dev_mac_valid: "{{ _devs|zip(_macs)|zip(_macs_valid)|
map('flatten')|
selectattr(2) }}"
dev_mac_valid: "{{ _dev_mac_valid|map('zip', ['dev', 'mac', 'valid'])|
map('map', 'reverse')|
map('community.general.dict') }}"
dev_mac: "{{ dev_mac_valid|items2dict(key_name='dev', value_name='mac') }}"
mac_dev: "{{ dev_mac_valid|items2dict(key_name='mac', value_name='dev') }}"
mac_dev_hosts: "{{ dict(ansible_play_hosts|
zip(ansible_play_hosts|
map('extract', hostvars, 'mac_dev'))) }}"
tasks:
- setup:
gather_subset: network
- set_fact:
mac_dev: "{{ mac_dev }}"
- block:
- debug:
var: mac_dev_hosts|to_nice_yaml
- pause:
prompt: "Enter MAC address"
register: result
- set_fact:
macaddress: "{{ result.user_input }}"
- assert:
that: macaddress|ansible.utils.hwaddr('bool')
fail_msg: Wrong MAC address
- assert:
that: macaddress in mac_dev_hosts|json_query('*.keys(#)')|flatten
fail_msg: "No interface found with mac address {{ macaddress }}"
- debug:
msg: "Interface {{ dev }} found with mac address {{ macaddress }}
at {{ hst }}"
vars:
itm: "{{ mac_dev_hosts|dict2items|
selectattr('value', 'contains', macaddress)|first }}"
hst: "{{ itm.key }}"
dev: "{{ itm.value[macaddress] }}"
run_once: true
gives
PLAY [test_11,test_12,test_13] ***************************************************************
TASK [setup] *********************************************************************************
ok: [test_13]
ok: [test_12]
ok: [test_11]
TASK [set_fact] ******************************************************************************
ok: [test_13]
ok: [test_11]
ok: [test_12]
TASK [debug] *********************************************************************************
ok: [test_11] =>
mac_dev_hosts|to_nice_yaml: |-
test_11:
00:12:00:34:56:AA: wlan0
test_12:
00:12:00:34:56:BB: wlan0
test_13:
00:12:00:34:56:CC: wlan0
TASK [pause] *********************************************************************************
[pause]
Enter MAC address:
00:12:00:34:56:CC^Mok: [test_11]
TASK [set_fact] ******************************************************************************
ok: [test_11]
TASK [assert] ********************************************************************************
ok: [test_11] => changed=false
msg: All assertions passed
TASK [assert] ********************************************************************************
ok: [test_11] => changed=false
msg: All assertions passed
TASK [debug] *********************************************************************************
ok: [test_11] =>
msg: Interface wlan0 found with mac address 00:12:00:34:56:CC at test_13
PLAY RECAP ***********************************************************************************
test_11: ok=8 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test_12: ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
test_13: ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
If the MAC is missing the play will fail, for example
TASK [assert] ********************************************************************************
fatal: [test_11]: FAILED! => changed=false
assertion: macaddress in mac_dev_hosts|json_query('*.keys(#)')|flatten
evaluated_to: false
msg: No interface found with mac address 00:12:00:34:56:FF

Ansible - set fact for key of dict based on sub-key value

Pfoef, describing my issue at hand is quite difficult. Please bear with me.
I have this dict:
my_dict:
FIRST:
some_key: first_value
SECOND:
some_key: second_value
My Ansible task is:
- shell: "echo {{ item.value['some_key'] }}"
register: exec_output
loop: "{{ my_dict | dict2items }}"
# This is something where I do not know what to do
- set_fact:
desired_output: ???
when: <some_key_contains_a_value>
When Ansible executes, it will execute the shell command twice, because there are 2 items in the dict.
Q: How can I configure Ansible so that: Ansible will set a fact and add the key (FIRST or SECOND) if the value of some_key is e.g. 'second_value'. In this example case, the fact will contain "SECOND".
You can find back the whole item that was part of a loop when register'ing its output via the item.item property.
So, in your case, you will find, in the items under exec_output.results:
item:
key: FIRST
value:
some_key: first_value
item:
key: SECOND
value:
some_key: second_value
So, based on that you could have a playbook like:
- hosts: localhost
gather_facts: no
tasks:
- shell: "echo {{ item.value.some_key }}"
register: exec_output
loop: "{{ my_dict | dict2items }}"
vars:
my_dict:
FIRST:
some_key: first_value
SECOND:
some_key: second_value
- set_fact:
desired_output: "{{ item.item.key }}"
when: some_key in item.stdout
loop: "{{ exec_output.results }}"
vars:
some_key: second
loop_control:
label: "{{ item.stdout }}"
- debug:
var: desired_output
That would give you the expected result:
PLAY [localhost] *******************************************************************************************************************
TASK [shell] ***********************************************************************************************************************
changed: [localhost] => (item={'key': 'FIRST', 'value': {'some_key': 'first_value'}})
changed: [localhost] => (item={'key': 'SECOND', 'value': {'some_key': 'second_value'}})
TASK [set_fact] ********************************************************************************************************************
skipping: [localhost] => (item=first_value)
ok: [localhost] => (item=second_value)
TASK [debug] ***********************************************************************************************************************
ok: [localhost] =>
desired_output: SECOND
PLAY RECAP *************************************************************************************************************************
localhost : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

How to Club multiple ansible condition into one based on the set_facts

While going through one of the SO post for Ansible, was interested an usage of set_fact with different conditional checks.
However, I answered on the post based on my approach, but, I still see there can be an improvement to club multiple conditions for similar task like determine the location resource group and vnet.
Below is the playbook and variables file azure_vars.yml
Playbook:
---
- name: create azure vm
hosts: localhost
connection: local
tasks:
- include_vars: azure_vars.yml
- set_fact:
host: "{{ azure_vm_name.split('.') }}"
- set_fact:
domain: "{{ host.1 }}.{{ host.2 }}"
- name: Domain
debug:
msg: "{{ domain }}"
- set_fact:
location: "{{ azure_location[0] }}"
when: 'domain == azure_domain[0]'
- set_fact:
location: "{{ azure_location[1] }}"
when: 'domain == azure_domain[1]'
- set_fact:
location: "{{ azure_location[2] }}"
when: 'domain == azure_domain[2]'
- name: Location
debug:
msg: "{{ location }}"
- set_fact:
res_group: "{{ azure_res_group[0] }}"
when: 'domain == azure_domain[0]'
- set_fact:
res_group: "{{ azure_res_group[1] }}"
when: 'domain == azure_domain[1]'
- set_fact:
res_group: "{{ azure_res_group[2] }}"
when: 'domain == azure_domain[2]'
- name: Resource Group
debug:
msg: "{{ res_group }}"
- set_fact:
vnet: "{{ azure_nprod_vnet }}"
when: 'domain == azure_domain[0]'
- set_fact:
vnet: "{{ azure_prod03_vnet }}"
when: 'domain == azure_domain[2]'
- set_fact:
vnet: "{{ azure_prod02_vnet }}"
when: 'domain == azure_domain[1]'
- name: Vnet
debug:
msg: "{{ vnet }}"
Variable files: this file contains all the variable which will be a part of playbook and imported as include_vars under task section.
azure_vars.yml
---
azure_nprod_vnet: "/subscriptions/XXXXXXXX-XXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg001/providers/Microsoft.Network/virtualNetworks/vnet"
azure_prod02_vnet: "/subscriptions/XXXXXXXX-XXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg003/providers/Microsoft.Network/virtualNetworks/vnet"
azure_prod03_vnet: "/subscriptions/XXXXXXXX-XXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/rg002/providers/Microsoft.Network/virtualNetworks/vnet"
# Azure domain
azure_domains:
- us-sea01
- us-azrc2
- eu-azrc1
# Resource group
azure_res_group:
- rg001
- rg002
- rg003
# Azure locations
azure_location:
- westus2
- southcentralus
- westeurope
Expected would be to club below three into one condition:
- set_fact:
location: "{{ azure_location[0] }}"
when: 'domain == azure_domain[0]'
- set_fact:
location: "{{ azure_location[1] }}"
when: 'domain == azure_domain[1]'
- set_fact:
location: "{{ azure_location[2] }}"
when: 'domain == azure_domain[2]'
May be something like:
- set_fact:
location:
- azure_location[0]
- azure_location[1]
- azure_location[2]
when:
- 'domain == azure_domain[0]
- 'domain == azure_domain[1]
- 'domain == azure_domain[2]
You can solve this using a loop and zip filter, or in the older fashion way, with a with_together — not recommended anymore, if you want to future-proof your playbooks.
Given the playbook:
- hosts: all
vars:
azure_vm_name: foo.bar.us-sea01.whatever.example.org
tasks:
- include_vars: azure_vars.yml
- set_fact:
host: "{{ azure_vm_name.split('.') }}"
- set_fact:
domain: "{{ host.2 }}"
- name: Domain
debug:
msg: "{{ domain }}"
- set_fact:
location: "{{ item.1 }}"
loop: "{{ azure_domains | zip(azure_location) | list }}"
when: 'domain == item.0'
- name: Location
debug:
msg: "{{ location }}"
And the variable file azure_vars.yml:
azure_domains:
- us-sea01
- us-azrc2
- eu-azrc1
azure_location:
- westus2
- southcentralus
- westeurope
This yields the recap:
PLAY [all] *******************************************************************************************************
TASK [Gathering Facts] *******************************************************************************************
ok: [localhost]
TASK [include_vars] **********************************************************************************************
ok: [localhost]
TASK [set_fact] **************************************************************************************************
ok: [localhost]
TASK [set_fact] **************************************************************************************************
ok: [localhost]
TASK [Domain] ****************************************************************************************************
ok: [localhost] => {
"msg": "us-sea01"
}
TASK [set_fact] **************************************************************************************************
ok: [localhost] => (item=['us-sea01', 'westus2'])
skipping: [localhost] => (item=['us-azrc2', 'southcentralus'])
skipping: [localhost] => (item=['eu-azrc1', 'westeurope'])
TASK [Location] **************************************************************************************************
ok: [localhost] => {
"msg": "westus2"
}
PLAY RECAP *******************************************************************************************************
localhost : ok=7 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
This said there is another possibility, using Python's capacities of Ansible. You could use the index() method of a list to target the element of azure_location being at the same position as domain in azure_domains.
e.g. if the domain is at position 2 of azure_domains, you'll get the element of position 2 in azure_location.
Given the playbook:
- hosts: all
vars:
azure_vm_name: foo.bar.us-sea01.whatever.example.org
tasks:
- include_vars: azure_vars.yml
- set_fact:
host: "{{ azure_vm_name.split('.') }}"
- set_fact:
domain: "{{ host.2 }}"
- name: Domain
debug:
msg: "{{ domain }}"
- set_fact:
location: "{{ azure_location[azure_domains.index(domain)] }}"
- name: Location
debug:
msg: "{{ location }}"
And the same variable file azure_vars.yml, this yields the recap:
PLAY [all] ******************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************
ok: [localhost]
TASK [include_vars] *********************************************************************************************************
ok: [localhost]
TASK [set_fact] *************************************************************************************************************
ok: [localhost]
TASK [set_fact] *************************************************************************************************************
ok: [localhost]
TASK [Domain] ***************************************************************************************************************
ok: [localhost] => {
"msg": "us-sea01"
}
TASK [set_fact] *************************************************************************************************************
ok: [localhost]
TASK [Location] *************************************************************************************************************
ok: [localhost] => {
"msg": "westus2"
}
PLAY RECAP ******************************************************************************************************************
localhost : ok=7 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Which is not using a loop, so makes an easier recap.

ansible-playbook extra vars passing from command line

My playbook looks like:
---
- name: Install and configure AD authentication
hosts: test
become: yes
become_user: root
vars:
hostname: "{{ host_name }}"
vars_prompt:
- name: "bind_password"
prompt: "Password for xxx.local\\user"
private: yes
tasks:
- name: Ansible prompt example.
debug:
msg: "{{ bind_password }}"
- name: Ansible prompt example.
debug:
msg: "{{ host_name }}"
and i am using below command to pass the variable
ansible-playbook hostname_set.yml --extra-vars "host_name= 'xxx.xxx.local'"
but i am not getting exact variable value what i am using for setting up hostname.
Password for xxx.xxx\user:
PLAY [Install and configure AD authentication]
TASK [Gathering Facts]
ok: [x.x.x.x]
TASK [Ansible prompt example.]
ok: [x.x.x.x] => {
"msg": "wel"
}
TASK [Ansible prompt example.]
ok: [x.x.x.x] => {
"msg": ""
}
TASK [Setup the hostname]
changed: [x.x.x.x]
PLAY RECAP
x.x.x.x : ok=4 changed=1 unreachable=0 failed=0
skipped=0 rescued=0 ignored=0
You have an extra space in your command line that ruins how ansible is interpreting the extra vars. Just remove it:
--extra-vars "host_name='xxx.xxx.local'"
Note that you don't even need all those quotes. The following should also work as expected:
--extra-vars host_name=xxx.xxx.local

How to skip all other plays in ansible playbook if some condition is not met?

I have multiple plays in below playbook. I want to ignore all other plays if some condition is not met.
So for below example - If I cannot find any new file in Play1 then I don't want to execute Play2 and Play3 at all (it should skip it). How can I do that?
I have end_play in Play1 but it only skips Play1 and it still executes Play2 and Play3
---
- name: Play 1
hosts: 127.0.0.1
tasks:
- name: find the latest file
find: paths=/var/lib/jenkins/jobs/process/workspace/files
file_type=file
age=-1m
age_stamp=mtime
register: files
- meta: end_play
when: files.files|count == 0
- name: Copy file, if found
copy:
src: "some stuff here"
dest: "some other stuff here"
when: files.files|count > 0
- name: Play 2
hosts: all
serial: 5
tasks:
- name: copy latest file
copy: src=data_init/goldy.init.qa dest=/data01/admin/files/goldy.init.qa owner=golden group=golden
- name: copy latest file
copy: src=data_init/goldy.init.qa dest=/data02/admin/files/goldy.init.qa owner=golden group=golden
- name: Play 3
hosts: 127.0.0.1
tasks:
- name: execute command
shell: ./data_init --init_file ./goldy.init.qa
args:
chdir: /var/lib/jenkins/jobs/process/workspace/data_init/
Update:
So block module solution doesn't work looks like because you cannot use nested play in block like that..
There are two ways to approach your issue:
Although it is understandable that you tried defining a hosts: localhost to run some tasks on the controller, you don't really need this. Using delegate_to: localhost and run_once: true on the steps you want to run on your controller is a far better approach.
If you really want to go this way (using hosts: localhost), for some reason, then you'll need to save the return of your find, with set_fact in order to reuse it in other hosts with the help of the global hostvars
Using delegation
Any task in the play can be delegated to another host. This is quite simple to do and would just use one extra delegate_to options on your task:
- name: Delegate a find to localhost
find:
path: /test
file_type: any
register: localhost_find
delegate_to: localhost
run_once: true
When doing so, I would advice you to also use run_once: true, because, if you delegate the task to another host, there is no need to, let's say run it ten times if you have ten hosts in your host group.
Here is a little example about that
---
- hosts: hosts
become: true
gather_facts: false
tasks:
- name: Delegate directory creation to localhost
file:
path: /test
state: directory
delegate_to: localhost
run_once: true
- name: Create directory on hosts
file:
path: /test
state: directory
- name: Delegate file creation to localhost
file:
path: /test/localhost.txt
state: touch
delegate_to: localhost
run_once: true
- name: Create file on hosts
file:
path: /test/host.txt
state: touch
- name: Delegate a find to localhost
find:
path: /test
file_type: any
register: localhost_find
delegate_to: localhost
run_once: true
- name: Find in the hosts for comparison
find:
path: /test
file_type: any
register: host_find
- name: List /test of localhost
debug:
msg: "{{ localhost_find.files | map(attribute='path') | list }}"
- name: List /test of host
debug:
msg: "{{ host_find.files | map(attribute='path') | list }}"
- name: Remove /test folder on localhost
file:
path: /test
state: absent
delegate_to: localhost
run_once: true
- name: Delegate an empty find to localhost
find:
path: /test
file_type: any
register: empty_find
delegate_to: localhost
run_once: true
- name: Here are our hostnames from the inventory
debug:
msg: "{{ inventory_hostname }}"
- name: I am the evil host killer
meta: end_host
when: empty_find.files | count == 0 and inventory_hostname != 'host1'
- debug:
msg: "I am a sad message, because I will never display :'( But hopefully host1 likes me :')"
- name: I am the evil playbook killer
meta: end_play
when: empty_find.files | count == 0
- debug:
msg: "I am a sad message, because I will never display :'( No one likes me..."
Where you can see, that, I am skipping the very last debug message by ending the play, when I ended two out of the three hosts I have in my hosts group on the step before.
Output of that playbook:
PLAY [hosts] **************************************************************************************************************************
TASK [Delegate directory creation to localhost] ***************************************************************************************
ok: [host1 -> localhost]
TASK [Create directory on hosts] ****************************************************************************************************
ok: [host3]
ok: [host2]
ok: [host1]
TASK [Delegate file creation to localhost] ********************************************************************************************
changed: [host1 -> localhost]
TASK [Create file on hosts] ****************************************************************************************************
changed: [host2]
changed: [host1]
changed: [host3]
TASK [Delegate a find to localhost] ***************************************************************************************************
ok: [host1 -> localhost]
TASK [Find in the host for comparison] ************************************************************************************************
ok: [host1]
ok: [host3]
ok: [host2]
TASK [List /test of localhost] ********************************************************************************************************
ok: [host1] => {
"msg": [
"/test/localhost.txt"
]
}
ok: [host2] => {
"msg": [
"/test/localhost.txt"
]
}
ok: [host3] => {
"msg": [
"/test/localhost.txt"
]
}
TASK [List /test of host] *************************************************************************************************************
ok: [host1] => {
"msg": [
"/test/host.txt"
]
}
ok: [host2] => {
"msg": [
"/test/host.txt"
]
}
ok: [host3] => {
"msg": [
"/test/host.txt"
]
}
TASK [Remove /test folder on localhost] ***********************************************************************************************
changed: [host1 -> localhost]
TASK [Delegate an empty find to localhost] ********************************************************************************************
ok: [host1 -> localhost]
TASK [Here are our hostnames from the inventory] **************************************************************************************
ok: [host1] => {
"msg": "host1"
}
ok: [host2] => {
"msg": "host2"
}
ok: [host3] => {
"msg": "host3"
}
TASK [debug] **************************************************************************************************************************
ok: [host1] => {
"msg": "I am a sad message, because I will never display :'( But hopefully host1 likes me :')"
}
PLAY RECAP ****************************************************************************************************************************
host1 : ok=12 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host2 : ok=6 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host3 : ok=6 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
In the output, you can easily spot the delegation, as is will output
changed: [host1 -> localhost]
When a non-delegated task would just go
changed: [host1]
Using hostvars
I would say the approach is a little bit less state of the art, but could become handy if you would like to delegate on edge cases. I have used it before, so it is not like there is no situation where it could not end up has being the right solution.
Here is the example
---
- hosts: localhost
gather_facts: false
tasks:
- name: Find in localhost
find:
path: /not/existing/folder
file_type: any
register: find
- name: This will register the list under find.files as a variable on the host, making it accessible via hostvars
set_fact:
find_result: "{{ find.files }}"
- meta: end_play
when: find.files | count == 0
- debug:
msg: I am the sanity check message, proving the end_play did happen
- hosts: hosts
gather_facts: false
tasks:
- name: Just to show we are not cheating with an empty host group, we display the hosts names
debug:
msg: "{{ inventory_hostname }}"
- name: To show it is really our empty list and not an empty string or a null variable
debug:
msg: "{{ hostvars['localhost']['find_result'] }}"
- meta: end_play
when: "hostvars['localhost']['find_result'] | count == 0"
- debug:
msg: I am a first sanity check message, proving the end_play did happen
- debug:
msg: I am a second sanity check message, proving the end_play did happen
- hosts: localhost
gather_facts: false
tasks:
- name: To show it is really our empty list and not an empty string or a null variable
debug:
msg: "{{ hostvars['localhost']['find_result'] }}"
- meta: end_play
when: "hostvars['localhost']['find_result'] | count == 0"
- debug:
msg: I am a first sanity check message, proving the end_play did happen
- debug:
msg: I am a second sanity check message, proving the end_play did happen
Where you can see that, at the beginning of each new tasks group for a host, I just run the meta to end the play, based on the variable find_result registered out of the find result
Here is the output for this one
PLAY [localhost] **********************************************************************************************************************
TASK [Find in localhost] **************************************************************************************************************
ok: [localhost]
TASK [This will register the list under find.files as a variable on the host, making it accessible via hostvars] *********************
ok: [localhost]
PLAY [hosts] **************************************************************************************************************************
TASK [Just to show we are not cheating with an empty host group, we display the hosts names] ******************************************
ok: [host1] => {
"msg": "host1"
}
ok: [host2] => {
"msg": "host2"
}
ok: [host3] => {
"msg": "host3"
}
TASK [To show it is really our empty list and not an empty string or a null variable] ************************************************
ok: [host1] => {
"msg": []
}
ok: [host2] => {
"msg": []
}
ok: [host3] => {
"msg": []
}
PLAY [localhost] **********************************************************************************************************************
TASK [To show it is really our empty list and not an empty string or a null variable] ************************************************
ok: [localhost] => {
"msg": []
}
PLAY RECAP ****************************************************************************************************************************
host1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host3 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Try using a block that only triggers your second two tasks after you do your first:
https://docs.ansible.com/ansible/latest/user_guide/playbooks_blocks.html
---
- name: Play 1
hosts: 127.0.0.1
tasks:
- name: find the latest file
find: paths=/var/lib/jenkins/jobs/process/workspace/files
file_type=file
age=-1m
age_stamp=mtime
register: files
- name: Play 2 & 3 if Play 1 has a file
block:
- name: Play 2
hosts: all
serial: 5
tasks:
- name: copy latest file
copy: src=data_init/goldy.init.qa dest=/data01/admin/files/goldy.init.qa owner=golden group=golden
- name: copy latest file
copy: src=data_init/goldy.init.qa dest=/data02/admin/files/goldy.init.qa owner=golden group=golden
- name: Play 3
hosts: 127.0.0.1
tasks:
- name: execute command
shell: ./data_init --init_file ./goldy.init.qa
when: files != ""
...
Using Variables in hosts value
One trick you can use is set the value of target hosts for the other plays in Play1 this way you can set it to a value that doesn't exist in your inventory (DONOTEXECUTE in the case shown below) the other plays will simply be skipped
- name: Play 1
hosts: 127.0.0.1
tasks:
- name: find the latest file
find: paths=/var/lib/jenkins/jobs/process/workspace/files
file_type=file
age=-1m
age_stamp=mtime
register: files
- name: Copy file, if found
copy:
src: "some stuff here"
dest: "some other stuff here"
when: files.files|count > 0
- name: end_plays
set_fact:
play2_hosts: DONOTEXECUTE
play3_hosts: DONOTEXECUTE
when: files.files|count == 0
- name: continue_plays
set_fact:
play2_hosts: all
play3_hosts: 127.0.0.1
when: files.files|count > 0
- name: Play 2
hosts: "{{hostvars['localhost']['play2_hosts']}}"
serial: 5
tasks:
- name: copy latest file
copy: src=data_init/goldy.init.qa dest=/data01/admin/files/goldy.init.qa owner=golden group=golden
- name: copy latest file
copy: src=data_init/goldy.init.qa dest=/data02/admin/files/goldy.init.qa owner=golden group=golden
- name: Play 3
hosts: "{{hostvars['localhost']['play3_hosts']}}"
tasks:
- name: execute command
shell: ./data_init --init_file ./goldy.init.qa
args:
chdir: /var/lib/jenkins/jobs/process/workspace/data_init/

Resources