When condition is just skipping - ansible

I have an Ansible playbook as shown below. The only problem is every time I run it is just skipping/ignoring the task.
Can you please help me figure out what is the problem?
- name: Register Host to Dynamic Inventory
hosts: localhost
gather_facts: false
tasks:
- add_host:
name: "{{ myhost }}"
- name: Enabling the services
hosts: "{{ myhost }}"
gather_facts: true
tasks:
- name: Make the service available persistently after a reboot for SLES11
command: systemctl enable after.local
with_items: "{{ ansible_distribution_major_version }}"
when: ansible_distribution_major_version == 11
- name: Make the service available persistently after a reboot for SLES12
command: systemctl enable after.local
with_items: "{{ ansible_distributioni_major_version }}"
when: ansible_distribution_major_version == 12
TASK [add_host] ****************************************************************03:22:06
changed: [localhost]
PLAY [Enabling the services] ***************************************************03:22:06
TASK [Gathering Facts] *********************************************************03:22:06
ok: [hostname]
TASK [Make the service available persistently after a reboot for SLES11] ******03:22:10
skipping: [hostname] => (item=12)
TASK [Make the service available persistently after a reboot for SLES12] ******03:22:10
skipping: [hostname]

The tasks get skipped because ansible_distribution_major_version is a string and you compare it to an integer value.
You should either fix your conditions to:
when: ansible_distribution_major_version == "12"
Or cast the value:
when: ansible_distribution_major_version | int == 12
Having fixed that, the remaining code makes little sense and will produce a syntax error.

Related

Ansible How to store multiple "register" in one file with one playbook

Ansible store only the first output in a file
Example
I have 3 hosts inside the inventory
My playbook ask for memory info.
with
- name: Check memory
hosts: all
tasks:
- name: Check Memory
shell: free
register: memory_output
- name: save
lineinfile:
path: "mypc/test.log"
line: "--{{ memory_output.stdout }}% "
create: yes
delegate_to: localhost
output write in file sometimes all the hosts memory,sometimes only the first,sometimes only the last
How i append every result from every hosts in one file.
Sometimes it export all the results but not every time
For example, given the inventory
shell> cat hosts
test_11
test_12
test_13
declare the below variable and put it into the vars
vmstat: "{{ out.stdout|community.general.jc('vmstat') }}"
Get the free memory
- command: vmstat
register: out
- set_fact:
free_mem: "{{ vmstat.1.free_mem }}"
- debug:
var: free_mem
gives (abridged)
ok: [test_11] =>
free_mem: '3434124'
ok: [test_12] =>
free_mem: '3496908'
ok: [test_13] =>
free_mem: '3434992'
Q: "How to store multiple 'register' in one file with one playbook."
A: Write it to the log
- lineinfile:
create: true
path: /tmp/test.log
line: >-
{{ '%Y-%m-%d %H:%M:%S'|strftime() }}
{{ item }}
{{ hostvars[item].free_mem }}
loop: "{{ ansible_play_hosts }}"
delegate_to: localhost
run_once: true
gives
shell> cat /tmp/test.log
2022-09-12 13:39:48 test_11 3434124
2022-09-12 13:39:49 test_12 3496908
2022-09-12 13:39:49 test_13 3434992
Example of a complete playbook for testing
- hosts: test_11,test_12,test_13
vars:
vmstat: "{{ out.stdout|community.general.jc('vmstat') }}"
tasks:
- command: vmstat
register: out
- set_fact:
free_mem: "{{ vmstat.1.free_mem }}"
- debug:
var: free_mem
- lineinfile:
create: true
path: /tmp/test.log
line: >-
{{ '%Y-%m-%d %H:%M:%S'|strftime() }}
{{ item }}
{{ hostvars[item].free_mem }}
loop: "{{ ansible_play_hosts }}"
delegate_to: localhost
run_once: true
Here's the simplest example. Assuming that you are running ansible in controller machine and you have to append the output of executing tasks in remote machines. The host list will obviously be different for you and will have all the remote machines.
- hosts: localhost
tasks:
## Playbook to copy the file from controller machine to remote machine
- name: Copy the file from controller machine to remote machine
copy:
src: /tmp/tmpdir/output.txt
dest: /tmp/tmpdir/output.txt
## Playbook to store the shell output to a variable
- name: Store the output of the shell command to a variable
shell: "echo '\nHello World'"
register: output
- name: Print the output of the shell command
debug:
msg: "{{ output.stdout }}"
## Playbook to append output to a file
- name: Append output to a file
lineinfile:
path: /tmp/tmpdir/output.txt
line: "{{ output.stdout }}"
create: yes
## Playbook to copy the file from remote machine to controller machine
- name: Copy the file from remote machine to controller machine
fetch:
src: /tmp/tmpdir/output.txt
dest: /tmp/tmpdir/output.txt
flat: yes
After running it the third time
╰─ ansible-playbook test.yaml
[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 [Copy the file from controller machine to remote machine] *********************************************************************************************
ok: [localhost]
TASK [Store the output of the shell command to a variable] *************************************************************************************************
changed: [localhost]
TASK [Print the output of the shell command] ***************************************************************************************************************
ok: [localhost] => {
"msg": "\nHello World"
}
TASK [Append output to a file] *****************************************************************************************************************************
changed: [localhost]
TASK [Copy the file from remote machine to controller machine] *********************************************************************************************
ok: [localhost]
PLAY RECAP *************************************************************************************************************************************************
localhost : ok=6 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
╰─ cat output.txt
Hello World
Hello World
Hello World
So on every machine you run the controller machine will get the latest file output. We will copy the file to remote, add contents to the file and then copy it back to controller. Continue the same until all the hosts have been completed.
If you want to take the result from selective servers then the last task can be replaced by following. Replace the hostnames with required values
## Playbook to copy the file from remote machine to controller machine if the hostname maches localhost1 or localhost2
- name: Copy the file from remote machine to controller machine if the hostname maches localhost1 or localhost2
fetch:
src: /tmp/tmpdir/output.txt
dest: /tmp/tmpdir/output.txt
flat: yes
fail_on_missing: yes
when: inventory_hostname == 'localhost1' or inventory_hostname == 'localhost2'

Loop over group IP's

I have a group with hosts and to probe those with GlusterFS I need to turn the group into a list of IP's.
I have searched here and on Google and tried a lot of things to get this done, some include lots of code and others templates and regex and what not.
Currently I have not found a single solution to just list the IP's of a group of hosts.
This is what I made myself, it takes the group, turns this dict into a list and then loops over the list, great! But when I loop over it the results are strings (why?!) and I cannot get the IP's anymore.
- name: workit
vars:
main_nodes_ips: "{{ groups['glusterpeers'] | list }}"
loop: "{{ main_nodes_ips }}"
debug:
msg: "{{ item['hostvars'] }}"
I found this
- debug: var=hostvars[inventory_hostname]['ansible_default_ipv4']['address']
But how to turn it into this (example not working)
- debug: var=groups['glusterpeers']['ansible_default_ipv4']['address']
I do not understand why a list of hosts turns into a list of strings all of a sudden, but maybe I should not think of it as objects.
My Result
This is what I ended up with to probe all the peers for the GlusterFS. In the 'when' blocks I hard code the hostname of the first node, not great. I still wonder if there is not a more elegant way of probing the peers, it must be something all GlusterFS users do.
Als taking the second IP may give trouble when this order of IP's changes so hostname is probably better.
- hosts: all
gather_facts: true
tasks:
- set_fact:
main_nodes_ips: "{{ groups.zicluster|
map('extract', hostvars, 'ansible_all_ipv4_addresses')|
map(attribute='1')|
list }}"
run_once: true
- debug:
var: main_nodes_ips[1:]
when: inventory_hostname == 'zi01'
- name: probe the peers
gluster.gluster.gluster_peer:
state: present
nodes: "{{ main_nodes_ips[1:] }}"
when: inventory_hostname == 'zi01'
For example, the playbook below takes the first IP address of a remote host and creates the list
- hosts: glusterpeers
gather_facts: true
tasks:
- debug:
var: ansible_all_ipv4_addresses
- set_fact:
main_nodes_ips: "{{ groups.glusterpeers|
map('extract', hostvars, 'ansible_all_ipv4_addresses')|
map('first')|
list }}"
run_once: true
- debug:
var: main_nodes_ips
run_once: true
gives
PLAY [glusterpeers] ***************************************
TASK [Gathering Facts] ************************************
ok: [host02]
ok: [host01]
ok: [host03]
TASK [debug] **********************************************
ok: [host01] =>
ansible_all_ipv4_addresses:
- 10.1.0.61
ok: [host02] =>
ansible_all_ipv4_addresses:
- 10.1.0.62
ok: [host03] =>
ansible_all_ipv4_addresses:
- 10.1.0.63
TASK [set_fact] *******************************************
ok: [host01]
TASK [debug] **********************************************
ok: [host01] =>
main_nodes_ips:
- 10.1.0.61
- 10.1.0.62
- 10.1.0.63
Notes
If you want to take any other index from the list map attribute. For example, to get the 2nd item, replace
map('first')|
by
map(attribute='1')|
In your code, you remove the first IP from the list for a particular host. This can work for the first host only
- debug:
msg: "{{ main_nodes_ips[1:] }}"
when: inventory_hostname == 'host01'
Create a dictionary if you want to remove the IP for any host. e.g.
- set_fact:
main_nodes: "{{ dict(groups.glusterpeers|zip(main_nodes_ips)) }}"
run_once: true
gives
main_nodes:
host01: 10.1.0.61
host02: 10.1.0.62
host03: 10.1.0.63
Then, use this dictionary to remove the IP of the particular host from the list
- debug:
msg: "{{ main_nodes_ips|difference(main_nodes[inventory_hostname]) }}"
when: inventory_hostname == 'host01'
gives
TASK [debug] **************************************************
skipping: [host02]
skipping: [host03]
ok: [host01] =>
msg:
- 10.1.0.62
- 10.1.0.63

How to create a one-time user prompt input in ansible?

I have a the following oversimplified ansible playbook:
- name: Prepare worker nodes
hosts: "{{ hosts }}"
serial:
- 1
- 3
remote_user: root
any_errors_fatal: true
vars:
hosts: nodes
reboot: false
tasks:
- pause:
prompt: "Reboot server(s) to make sure things are working during setup? (Y/n)"
echo: true
register: confirm_reboot
tags: [ untagged, hostname, netplan, firewalld ]
- set_fact:
reboot: "{{ (confirm_reboot.user_input == '' or confirm_reboot.user_input == 'Y' or confirm_reboot.user_input == 'y' ) | ternary('True', 'False') }}"
tags: [ untagged, hostname, netplan, firewalld, firewalld-install, firewalld-config ]
- debug:
msg: "{{ reboot }}"
It asks for the user's input so it can decide on some reboot policies.
This works just fine when you have just one node, but when you have multiple nodes it will prompt for each one. Suppose you have 42 nodes -- it will ask you 42 times.
I'm trying to figure out if there is an easy way to make the prompt appear just once and share the result among the nodes. Maybe I have missed something in the docs?
Given the inventory
shell> cat hosts
[test]
host1
host2
host3
host4
host5
the playbook
shell> cat playbook.yml
---
- hosts: test
serial:
- 1
- 3
gather_facts: false
tasks:
- pause:
prompt: "Reboot? (Y/n)"
echo: true
register: confirm_reboot
run_once: true
- debug:
msg: "Reboot {{ inventory_hostname }}"
when: confirm_reboot.user_input|lower == 'y'
works as expected
shell> ansible-playbook -i hosts playbook.yml
PLAY [test] *********************************
TASK [pause] ********************************
[pause]
Reboot? (Y/n):
ok: [host1]
TASK [debug] ********************************
ok: [host1] =>
msg: Reboot host1
PLAY [test] *********************************
TASK [pause] ********************************
[pause]
Reboot? (Y/n):
ok: [host2]
TASK [debug] ********************************
ok: [host2] =>
msg: Reboot host2
ok: [host3] =>
msg: Reboot host3
ok: [host4] =>
msg: Reboot host4
PLAY [test] *********************************
TASK [pause] ********************************
[pause]
Reboot? (Y/n):
ok: [host5]
TASK [debug] ********************************
ok: [host5] =>
msg: Reboot host5
Q: "Require the input just once for the entire playbook and be propagated to all hosts."
A: Split the playbook, e.g.
shell> cat playbook.yml
---
- hosts: test
gather_facts: false
tasks:
- pause:
prompt: "Reboot? (Y/n)"
echo: true
register: confirm_reboot
run_once: true
- hosts: test
serial:
- 1
- 3
gather_facts: false
tasks:
- debug:
msg: "Reboot {{ inventory_hostname }}"
when: confirm_reboot.user_input|lower == 'y'
the variable from the first play will be shared among all hosts in the second play
shell> ansible-playbook -i hosts playbook.yml
PLAY [test] *********************************
TASK [pause] ********************************
[pause]
Reboot? (Y/n):
ok: [host1]
PLAY [test] *********************************
TASK [debug] ********************************
ok: [host1] =>
msg: Reboot host1
PLAY [test] *********************************
TASK [debug] ********************************
ok: [host3] =>
msg: Reboot host3
ok: [host2] =>
msg: Reboot host2
ok: [host4] =>
msg: Reboot host4
PLAY [test] *********************************
TASK [debug] ********************************
ok: [host5] =>
msg: Reboot host5
It looks like the only way this will work is by using delegate_to and delegate_facts. I came up with something like this:
- name: Prepare worker nodes
hosts: "{{ hosts }}"
serial:
- 1
- 3
remote_user: root
any_errors_fatal: true
vars:
hosts: nodes
reboot: true
pre_tasks:
- pause:
prompt: "Reboot server(s) to make sure things are working during setup? (Y/n)"
echo: true
register: confirm_reboot
run_once: true
delegate_to: localhost
delegate_facts: true
tags: [ untagged, hostname, netplan, firewalld, firewalld-install, firewalld-config ]
when: "'reboot' not in hostvars['localhost']"
- set_fact:
reboot: "{{ (confirm_reboot.user_input == '' or confirm_reboot.user_input == 'Y' or confirm_reboot.user_input == 'y' ) | ternary('True', 'False') }}"
run_once: true
delegate_to: localhost
delegate_facts: true
tags: [ untagged, hostname, netplan, firewalld, firewalld-install, firewalld-config ]
when: "'reboot' not in hostvars['localhost']"
- set_fact:
reboot: "{{ hostvars['localhost']['reboot'] }}"
run_once: true
tasks:
- debug:
msg: "{{ hostvars['localhost'] }}"
tags: [ untagged, hostname, netplan, firewalld, firewalld-install, firewalld-config ]
- debug:
msg: "{{ reboot }}"
tags: [ untagged, hostname, netplan, firewalld, firewalld-install, firewalld-config ]
This works by delegating the fact to the localhost (control node) and then it uses it by reference that seems to be kept between the different nodes. It is a hackish workaround to me, but since I don't have that much time to dig deeper into the "why", it'll have to do for now.
If anybody figures out a better way - feel free to post your answer.

How to print command output in Ansible when there is a condition?

My ansible code looks something like the following. Problem is this only works when my inventory has dev,qa, perf and prod servers. For example, if my inventory has only dev servers then it fails. Is there any way to avoid this failure ?
I tried changing both cmd_dev_out and cmd_qa_out to cmd_out but that did not help either.
- name: Execute
hosts: all
tasks:
- name: Execute against dev
shell: some command
register: cmd_dev_out
when: ( servers are dev )
- debug: msg="{{ cmd_dev_out }}"
- name: Execute against qa
shell: some command
register: cmd_qa_out
when: ( servers are qa )
- debug: msg="{{ cmd_qa_out }}"
....... More conditions below .....
The best would be to use a block in here to logically group the execution of the command and the print of the output of the said command.
Because, if there is no command run, well, obviously you won't get anything as a result.
This is an example using block
- name: Execute
hosts: all
tasks:
- block:
- name: Execute against dev
shell: 'echo "dev"'
register: cmd_dev_out
- debug:
msg: "{{ cmd_dev_out }}"
when: "'dev' in inventory_hostname"
- block:
- name: Execute against qa
shell: 'echo "qa"'
register: cmd_qa_out
- debug:
msg: "{{ cmd_qa_out }}"
when: "'qa' in inventory_hostname"
Mind that this means that the condition when is now tied to the block and both the command and the debug will be skipped when the condition is false.
Example of recap:
PLAY [localhost] **************************************************
TASK [Execute against dev] ****************************************
skipping: [localhost]
TASK [debug] ******************************************************
skipping: [localhost]
TASK [Execute against qa] *****************************************
changed: [localhost]
TASK [debug] ******************************************************
ok: [localhost] =>
msg:
changed: true
cmd: echo "qa"
delta: '0:00:00.002641'
end: '2023-01-25 20:51:04.049532'
failed: false
msg: ''
rc: 0
start: '2023-01-25 20:51:04.046891'
stderr: ''
stderr_lines: []
stdout: qa
stdout_lines:
- qa
Another, but maybe less elegant solution would be to use the default Jinja filter on your command result in case the command was skipped.
- name: Execute
hosts: all
tasks:
- name: Execute against dev
shell: 'echo "dev"'
register: cmd_dev_out
when: "'dev' in inventory_hostname"
- debug:
msg: "{{ cmd_dev_out | default('cmd_dev was skipped') }}"
- name: Execute against qa
shell: 'echo "qa"'
register: cmd_qa_out
when: "'qa' in inventory_hostname"
- debug:
msg: "{{ cmd_qa_out | default('cmd_qa was skipped') }}"
Figured out another solution. Its given below (add the when condition to the debug statement) :
- name: Execute
hosts: all
tasks:
- name: Execute against dev
shell: some command
register: cmd_dev_out
when: ( servers are dev )
- debug: msg="{{ cmd_dev_out }}"
when: ( servers are dev )
- name: Execute against qa
shell: some command
register: cmd_qa_out
when: ( servers are qa )
- debug: msg="{{ cmd_qa_out }}"
when: ( servers are qa )
....... More conditions below .....

Ansible vmware_vm_facts: get mac address for a specific VM and write it to existing inventory

I have to goals with my plabook:
a) get mac address from a specific VM running on vsphere
b) add it to my inventory file
My test environment is:
-Vsphere 6.5
-Ansible 2.7 running on Centos 7.6
I have been able to make point a) following this post How retrieve the name of specific dictionary - Ansible
Playbook:
# Deploy a guest from a template*
- hosts: 127.0.0.1
vars:
vcenter_hostname: virtvcsami1.virtlab.local
vcenter_user: administrator#vsphere.local
vcenter_pass: Esxilab!1
vcenter_datastore: vsanDatastore
vcenter_datacenter: DatacenterMI
tasks:
- name: Gather VMware guest facts
vmware_vm_facts:
validate_certs: False
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_user }}"
password: "{{ vcenter_pass }}"
vm_type: vm
delegate_to: localhost
register: vm_guest_facts
- debug: msg="{{ item.value.mac_address }}"
loop: "{{ vm_guest_facts.virtual_machines|dict2items }}"
but now I still have two problems to solve:
Problem 1)
Playbook gets ALL virtual machines while I need to get just a VM named "testvm"
[root#nlnxmi1 testvmcdromiso]# ansible-playbook getvminfo.yml
PLAY [127.0.0.1] ***********************************************************************************************************************************************************************************************
TASK [Gathering Facts] *****************************************************************************************************************************************************************************************
ok: [127.0.0.1]
TASK [Gather VMware guest facts] *******************************************************************************************************************************************************************************
ok: [127.0.0.1 -> localhost]
TASK [debug] ***************************************************************************************************************************************************************************************************
ok: [127.0.0.1] => (item={'key': u'kubemst01', 'value': {u'guest_fullname': u'CentOS 7 (64-bit)', u'vm_network': {u'00:50:56:be:de:b9': {u'ipv4': [u'192.168.40.31'], u'ipv6': [u'fe80::250:56ff:febe:deb9']}, u'52:54:00:62:fe:fe': {u'ipv4': [u'192.168.122.1'], u'ipv6': []}}, u'cluster': u'VirtlabMI', u'esxi_hostname': u'virtesxmi3.virtlab.local', u'mac_address': [u'00:50:56:be:de:b9'], u'power_state': u'poweredOn', u'ip_address': u'192.168.40.31', u'uuid': u'423e7580-1ca4-a6ca-5cb4-5b8fa963d527'}}) => {
"msg": [
"00:50:56:be:de:b9"
]
}
ok: [127.0.0.1] => (item={'key': u'testvm', 'value': {u'guest_fullname': >u'CentOS 7 (64-bit)', u'vm_network': {}, u'cluster': u'VirtlabMI', >u'esxi_hostname': u'virtesxmi1.virtlab.local', u'mac_address': >[u'00:50:56:be:a3:a0'], u'power_state': u'poweredOn', u'ip_address': u'', >u'uuid': u'423e8645-ca2a-1097-2e1c-624810a461d1'}}) => {
"msg": [
"00:50:56:be:a3:a0"
]
}
......
Problem 2)
Add mac address to existing inventory file or, if not possible, at least in some file
I tried adding the following code at the end:
- set_fact: vm_mac_address="prova"
- name: Register host to Inventory
lineinfile:
path: /etc/ansible/testvmcdromiso/inventory
regexp: '(testvm)'
line: '\1 macaddres={{ vm_mac_address }}'
backrefs: yes
[root#nlnxmi1 testvmcdromiso]# cat inventory
[testhost]
testvm macaddress=prova
but as you can see I just used a "fixed" string instead I need to get the mac address from the running vm but never figure it out even after 2 days of attempts :(
I'm just a beginner with ansible. Could you please help me?
best
Marco
Playbook gets ALL virtual machines while I need to get just a VM named "testvm"
That's not a problem. It looks like the vmware_vm_facts module returns a dictionary, so just ask for the vm you want:
- debug: msg="{{ vm_guest_facts.virtual_machines['testvm'].mac_address }}"
Add mac address to existing inventory file or, if not possible, at least in some file
Since you now know how to retrieve the MAC address, you modify your lineinfile task make use of that information:
- name: Register host to Inventory
lineinfile:
path: /etc/ansible/testvmcdromiso/inventory
regexp: '(testvm)'
line: '\1 macaddres={{ vm_guest_facts.virtual_machines['testvm'].mac_address }}'
backrefs: yes

Resources