Ansible Fact - Accessing the additional facts - ansible

I am doing a little research on Ansible facts. I am accessing facts in the debug module using something like: ansible_facts['mounts']. I noticed there are additional facts within the dictionary like "fstype" etc. However, when I try to access this like so ansible_facts['mounts']['fstype'] but I seems this is not the proper way to access this. I was testing a conditional with when to check for the fstype. Anyone know how to access this?
With everyone's help, here is the solution I came up with to assist with my research:
---
- name: Conditionals test
hosts: dev
tasks:
- name: Update the kernel if suff space
package:
name: kernel
state: latest
loop: "{{ ansible_facts['mounts'] }}"
when: item.mount == "/boot" and item.size_available > 20000000
I am looping through the ansible_facts list and checking for /boot and measuring the size. Thank you everyone!

To get a better understanding of the data structure of Ansible facts one can use the following example and test playbook.
---
- hosts: localhost
become: false
gather_facts: true
tasks:
- name: Show amount of mounts
debug:
msg:
- "{{ ansible_facts.mounts | type_debug }}"
- "{{ ansible_facts.mounts | length }}"
- name: Show mount type (sequence)
debug:
msg: "{{ ansible_facts.mounts[item | int].fstype }}"
loop: "{{ range(0, ansible_facts.mounts | length) | list }}"
- name: Show mount type (fact list)
debug:
msg: "{{ item.fstype }}"
loop: "{{ ansible_facts.mounts }}"
loop_control:
extended: true
label: "{{ ansible_loop.index0 }}"
it shows
That ansible_facts.mounts is mostly a list, how to get the data type and how to get the length of it
How to loop over a fact list with_sequence numbers
How to access the list element by index number (ansible_facts.mounts[item | int].fstype)
How to loop over a fact list and control the label
and resulting into an output of
TASK [Show mount type (sequence)] ******
ok: [localhost] => (item=0) =>
msg: ext4
ok: [localhost] => (item=1) =>
msg: ext4
ok: [localhost] => (item=2) =>
msg: ext4
ok: [localhost] => (item=3) =>
msg: vfat
TASK [Show mount type (fact list)] ******
ok: [localhost] => (item=0) =>
msg: ext4
ok: [localhost] => (item=1) =>
msg: ext4
ok: [localhost] => (item=2) =>
msg: ext4
ok: [localhost] => (item=3) =>
msg: vfat
Further Documentation
Ansible facts
with_sequence
Adding control to loops

I think your issue here is that you are trying to acces a list of mounts, you need to get one item from that list and get his fstype something like:
ansible_facts['mounts'][0]['fstype']
Or using a loop
- name: Print fstypes
debug:
var: "{{ item }}.fstype"
loop: "{{ ansible_facts.mounts }}"

or you can do this...
- name: Update the kernel if suff space
package:
name: kernel
state: latest
when: ansible_facts['mounts']|selectattr('mount','equalto','/boot')|map(attribute='size_available')|first > 20000000
rather than looping through the mounts at all.

Related

Ansible: How to extract first element from list?

I have below output
msg: ' [(''N5K'', ''5548UPQ'')] '
I've tried the following
" {{ platform_list[0] }} "
but it returns one character only and I want to extract 'N5K'.
As already mentioned in the comments, the output is not a list but a string.
To get a better insight into the variable type you may use according Managing data type - Discovering the data type type_debug.
- debug:
msg: "{{ platform_list }} of type {{ platform_list| type_debug }}"
Further Q&A
Ansible - Check variable type
Ansible - Type cast
How to proceed further?
To get a better understanding you may have a look into the following Minimal, Reproducible Example
---
- hosts: localhost
become: false
gather_facts: false
vars:
LIST: ['N5K', '5548UPQ']
tasks:
- name: Show type
debug:
msg: "{{ LIST }} and of type {{ LIST | type_debug }}"
- name: Show element
debug:
msg: "{{ LIST[0] }}"
resulting into an output of
TASK [Show type] *********************************
ok: [localhost] =>
msg: '[u''N5K'', u''5548UPQ''] and of type list'
TASK [Show element] ******************************
ok: [localhost] =>
msg: N5K
You can then change the vars in example into a string
vars:
STRING: "['N5K', '5548UPQ']"
tasks:
- name: Show type
debug:
msg: "{{ STRING }} and of type {{ STRING | type_debug }}"
- name: Show element
debug:
msg: "{{ STRING[0] }}"
resulting into an output of
TASK [Show type] *****************************************
ok: [localhost] =>
msg: '[''N5K'', ''5548UPQ''] and of type AnsibleUnicode'
TASK [Show element] **************************************
ok: [localhost] =>
msg: '['
because of Python Slicing.

Unicode error while increment variable value

When I work with static variables it works absolutely fine. But when I try to use dynamic it does not work.
The playbook:
---
- hosts: Swi1
vars:
NewOne: 0
provider:
host: "192.168.0.30"
transport: "cli"
username: "cisco"
password: "cisco"
tasks:
- name: gather facts
register: iosfacts
ios_facts:
provider: "{{ provider }}"
- name: Display the value of the counter
debug:
msg: "NewOne={{ NewOne }} / Data type={{ NewOne | type_debug }}"
- name: interface description
set_fact:
NewOne: " {{ NewOne + 1 }}"
parents: "interface {{ item.key }}"
with_dict: "{{ iosfacts.ansible_facts.ansible_net_interfaces }}"
when: item.value.operstatus == "up"
- debug:
msg: " This is Debug {{ NewOne }}"
Gives the error:
fatal: [Swi1]: FAILED! => {"msg": "Unexpected templating type error
occurred on ({{ NewOne + 1 }}): coercing to Unicode: need string or
buffer, int found"}
If you want to do an increment on a variable, you need to recast it as an int, as set_fact will always make you end up with a string.
As an example, the two tasks:
- set_fact:
NewOne: "{{ NewOne | d(0) + 1 }}"
- debug:
var: NewOne | type_debug
Are giving
TASK [set_fact] ***************************************************************
ok: [localhost]
TASK [debug] ******************************************************************
ok: [localhost] =>
NewOne | type_debug: str
The fix is, then, to use the int filter.
Given:
- set_fact:
NewOne: "{{ NewOne | d(0) | int + 1 }}"
loop: "{{ range(1, 4) }}"
- debug:
var: NewOne
This yields the expected
TASK [set_fact] ***************************************************************
ok: [localhost] => (item=1)
ok: [localhost] => (item=2)
ok: [localhost] => (item=3)
TASK [debug] ******************************************************************
ok: [localhost] =>
NewOne: '3'
But then with your use case, there are more elaborated and shorter way to achieve the same:
- set_fact:
NewOne: >-
{{
iosfacts
.ansible_facts
.ansible_net_interfaces
| selectattr('value.operstatus', '==', 'up')
| length
}}
Given:
- debug:
msg: >-
{{
iosfacts
.ansible_facts
.ansible_net_interfaces
| selectattr('value.operstatus', '==', 'up')
| length
}}
vars:
iosfacts:
ansible_facts:
ansible_net_interfaces:
- value:
operstatus: up
- value:
operstatus: down
- value:
operstatus: up
This yields:
ok: [localhost] =>
msg: '2'
It seems you are trying to implement a loop counter with a programming paradigm, which isn't plain possible in that way since Ansible is not a programming language but a Configuration Management Tool in which you declare a state.
Your current issue is reproducible in the following way:
---
- hosts: localhost
become: false
gather_facts: false
vars:
NewOne: 0
tasks:
- name: Show var
debug:
msg: "{{ NewOne | type_debug }}"
- name: Add value
set_fact:
NewOne: " {{ NewOne + 1 }}"
loop: [1, 2, 3]
- name: Show result
debug:
msg: "{{ NewOne }}
resulting into an output of
TASK [Add value] *************
ok: [localhost] => (item=1)
fatal: [localhost]: FAILED! =>
msg: 'Unexpected templating type error occurred on ( {{ NewOne + 1 }}): coercing to Unicode: need string or buffer, int found'
Possible Solutions
You may have a look into Migrating from with_X to loop and Extended loop variables as an iteration counter is already provided there.
An other approach is given via type casting with filter in the answer of #β.εηοιτ.βε.
There as well if you are just interested in the amount of occurrences of certain status, like interface status up or down.
Further Q&A
Ansible set_fact type cast
Further Documentation
Discovering the data type
Forcing the data type

How do you iterate/parse through a CSV file to use in Ansible playbook?

I am new to Ansible and don't have much coding experience in general. I am trying to figure out how to provision RHEL servers using the DellEMC OpenManage modules.
The first step to this is figuring out how to parse a CSV, that we are putting necessary information for the later templating. (IP, hostname, MAC, etc. ...) I can get it to print the data in general, but cant figure out how to parse/iterate through it.
CSV Sample
server_name,idrac_ip,idrac_user,idrac_pwd,idrac_nw,idrac_gw,idrac_mac,mgmt_ip,mgmt_nw,mgmt_gw,mgmt_mac,bond0_100_ip,bond0_200_ip,dns_1,bond0_100_nw,bond0_100_gw,bond0_100_vip,bond0_200_nw,bond0_200_gw,bond0_200_vip,os,fs,sio_type,mdm_name
test1,1.1.1.10,root,Password1234,1.1.1.0/24,1.1.1.1,00:00:00:00:00:01,1.1.1.142,1.1.62.0/24,1.1.2.1,98:03:9B:46:25:B3,1.1.1.22,1.1.1.21,1.1.1.26,1.1.61.15/24,1.1.61.1,1.1.61.29,1.1.66.0/24,1.1.66.1,1.1.1.29,RHEL 7.6,1,Master,MDM-1
Here is my playbook to print the info in general.
---
- name: Parse
hosts: localhost
connection: local
tasks:
- name: Load CSV Data into object
read_csv:
path: 'Test_Lab_Servers.csv'
fieldnames: server_name,idrac_ip,idrac_user,idrac_pwd,idrac_nw,idrac_gw,idrac_mac,mgmt_ip,mgmt_nw,mgmt_gw,mgmt_mac,bond0_100_ip,bond0_200_ip,dns_1,bond0_100_nw,bond0_100_gw,bond0_100_vip,bond0_200_nw,bond0_200_gw,bond0_200_vip,os,fs,sio_type,mdm_name
delimiter: ','
register: csv_output
delegate_to: localhost
- name: Print data
debug:
msg: "{{ csv_output }}"
Any advice?
According the read_csv module parameter documentation fieldnames are
needed if the CSV does not have a header
only. Therefore you could remove it to get a list object which might be easier to parse.
- name: Print data
debug:
msg: "{{ csv_output.list }}"
As already mentioned within the comments, to iterate over the list elements you may use loop.
- name: Print data
debug:
msg: "{{ item }}"
loop: "{{ csv_output.list }}"
With extended loop variables you will have a better control about the loop output.
- name: Print data
debug:
msg: "{{ item }}"
loop: "{{ csv_output.list }}"
loop_control:
extended: yes
label: "{{ ansible_loop.index0 }}"
Resulting into an output of
TASK [Print data] ***************
ok: [localhost] => (item=0) =>
msg:
bond0_100_gw: 1.1.61.1
bond0_100_ip: 1.1.1.22
bond0_100_nw: 1.1.61.15/24
bond0_100_vip: 1.1.61.29
bond0_200_gw: 1.1.66.1
bond0_200_ip: 1.1.1.21
bond0_200_nw: 1.1.66.0/24
bond0_200_vip: 1.1.1.29
dns_1: 1.1.1.26
fs: '1'
idrac_gw: 1.1.1.1
idrac_ip: 1.1.1.10
idrac_mac: 00:00:00:00:00:01
idrac_nw: 1.1.1.0/24
idrac_pwd: Password1234
idrac_user: root
mdm_name: MDM-1
mgmt_gw: 1.1.2.1
mgmt_ip: 1.1.1.142
mgmt_mac: 98:03:9B:46:25:B3
mgmt_nw: 1.1.62.0/24
os: RHEL 7.6
server_name: test1
sio_type: Master
...
Specific items (fields) like server_name you could get simply by using
msg: "{{ item.server_name }}"
resulting into an output of
TASK [Print data] ************
ok: [localhost] => (item=0) =>
msg: test1
ok: [localhost] => (item=1) =>
msg: test2
ok: [localhost] => (item=2) =>
msg: test3
...

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

In Ansible, how to query hostvars to get a specific value of a key from a list item based on the value of a different key?

EDIT-UPDATE:
I found a way to achieve what was trying to do, using the index_of plugin. The following code outputs what I need.
---
- hosts: CASPOSR1BDAT003
connection: local
gather_facts: no
become: false
tasks:
- ansible.builtin.set_fact:
mac_address: "{{ hostvars[inventory_hostname]['interfaces'][int_idx|int]['mac_address'] }}"
vars:
int_name: 'PCI1.1'
int_idx: "{{ lookup('ansible.utils.index_of', hostvars[inventory_hostname]['interfaces'], 'eq', int_name, 'name') }}"
- debug:
var: mac_address
Output:
PLAY [CASPOSR1BDAT003] ***********************************************************************************************************************************************************************************************
TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************
ok: [CASPOSR1BDAT003]
TASK [debug] *********************************************************************************************************************************************************************************************************
ok: [CASPOSR1BDAT003] =>
mac_address: 20:67:7C:00:36:A0
What I am trying to do:
Use the Netbox dynamic inventory plugin (this works, brings back all the info I need)
Query hostvars for a particular host, and get the value of the MAC address for a particular interface called PCI1.1
What I have tried:
Converting the hostvars to JSON and using json_query: this hasn't worked, and having looked at some issues on GitHub, hostvars isn't a "normal" dictionary. I've logged a couple of issues anyway (https://github.com/ansible/ansible/issues/76289 and https://github.com/ansible-collections/community.general/issues/3706).
Use a sequence loop and conditional "when" to get the value - this sort of works when using the debug module, but still not just returning the value
What works:
I have tried the following, which outputs the mac_address variable as expected. The length of the list is found, and then the conditional matches the name. I do get an warning about using jinja2 templating delimiters but that's not the target of this question.
---
- hosts: CASPOSR1BDAT003
connection: local
gather_facts: no
become: false
tasks:
- debug:
var: hostvars[inventory_hostname]['interfaces'][{{ item }}]['mac_address']
with_sequence: start=0 end="{{ end_at }}"
vars:
- end_at: "{{ (hostvars[inventory_hostname]['interfaces'] | length) - 1 }}"
when: hostvars[inventory_hostname]['interfaces'][{{ item }}]['name'] == "PCI1.1"
The result is:
TASK [debug] *************************************************************************************************************************************
[WARNING]: conditional statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found:
hostvars[inventory_hostname]['interfaces'][{{ item }}]['name'] == "PCI1.1"
skipping: [CASPOSR1BDAT003] => (item=0)
skipping: [CASPOSR1BDAT003] => (item=1)
skipping: [CASPOSR1BDAT003] => (item=2)
skipping: [CASPOSR1BDAT003] => (item=3)
skipping: [CASPOSR1BDAT003] => (item=4)
ok: [CASPOSR1BDAT003] => (item=5) =>
ansible_loop_var: item
hostvars[inventory_hostname]['interfaces'][5]['mac_address']: 20:67:7C:00:36:A0
item: '5'
skipping: [CASPOSR1BDAT003] => (item=6)
skipping: [CASPOSR1BDAT003] => (item=7)
skipping: [CASPOSR1BDAT003] => (item=8)
skipping: [CASPOSR1BDAT003] => (item=9)
I'm trying to use set_fact to store this mac_address variable as I need to use it in a couple of different ways. However, I am unable to use set_fact on this (or any other hostvars data, it seems). For example, the following:
---
- hosts: CASPOSR1BDAT003
connection: local
gather_facts: no
become: false
tasks:
- ansible.builtin.set_fact:
interfaces: "{{ hostvars[inventory_hostname]['interfaces'][item]['mac_address'] }}"
with_sequence: start=0 end="{{ end_at }}"
vars:
- end_at: "{{ (hostvars[inventory_hostname]['interfaces'] | length) - 1 }}"
when: hostvars[inventory_hostname]['interfaces'][{{ item }}]['name'] == "PCI1.1"
- debug:
var: interfaces
results in:
fatal: [CASPOSR1BDAT003]: FAILED! =>
msg: |-
The task includes an option with an undefined variable. The error was: 'list object' has no attribute '5'
The error appears to be in '/Users/kivlint/Documents/GitHub/vmware-automation/ansible/prepare-pxe.yml': line 19, column 7, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
# when: hostvars[inventory_hostname]['interfaces'][{{ item }}]['name'] == "PCI1.1"
- ansible.builtin.set_fact:
^ here
If I hard-code the number 5 in, it works fine:
TASK [ansible.builtin.set_fact] ******************************************************************************************************************
ok: [CASPOSR1BDAT003]
TASK [debug] *************************************************************************************************************************************
ok: [CASPOSR1BDAT003] =>
interfaces: 20:67:7C:00:36:A0
If I use '5' as a var for the task, it also works.
---
- hosts: CASPOSR1BDAT003
connection: local
gather_facts: no
become: false
tasks:
- ansible.builtin.set_fact:
interfaces: "{{ hostvars[inventory_hostname]['interfaces'][int_index]['mac_address'] }}"
vars:
- int_index: 5
So I'm wondering, is this a "bug/feature" in how set_fact does or doesn't work with loops (meaning, the same loop worked fine with debug? Or do I need to re-think the approach and consider trying to use set_fact to set a variable with the index of the list (e.g. 5 in the above example)? Or something else?
There's a lot going on in your code, and achieving the result you want is simpler than you've made it.
Firstly, don't use hostvars[inventory_hostname]; plain variables are the ones belonging to the current host, and going through hostvars introduces some exciting opportunities for things to go wrong. hostvars is for accessing variables belonging to other hosts.
Secondly, using Jinja's built-in filtering capabilities avoids the need to worry about the index of the item that you want.
- hosts: CASPOSR1BDAT003
connection: local
gather_facts: no
become: false
vars:
int_name: PCI1.1
mac_address: "{{ interfaces | selectattr('name', 'eq', int_name) | map(attribute='mac_address') | first }}"
tasks:
- debug:
var: mac_address
there is a confusion between the [5] (6th item of a list) and ['5'] (a key named "5") ,
you see in your error: The error was: 'list object' has no attribute '5'.
with the module debug you have not error because [{{item}}] is replaced by [5] and not by ['5']. Its not the same thing with set_fact.
its the reason you have to use filter int to clarify the situation.
- ansible.builtin.set_fact:
interfaces: "{{ hostvars[inventory_hostname]['interfaces'][item|int]['mac_address'] }}"
with_sequence: start=0 end="{{ end_at }}"
vars:
end_at: "{{ (hostvars[inventory_hostname]['interfaces'] | length) - 1 }}"
when: hostvars[inventory_hostname]['interfaces'][item|int]['name'] == "PCI1.1"
so i suggest you to use loop instead with_sequence:
- ansible.builtin.set_fact:
interfaces: "{{ hostvars[inventory_hostname]['interfaces'][item]['mac_address'] }}"
loop: "{{ range(0, end_at|int, 1)|list }}"
vars:
end_at: "{{ hostvars[inventory_hostname]['interfaces'] | length }}"
when: hostvars[inventory_hostname]['interfaces'][item]['name'] == "PCI1.1"
set_fact works with loops, but not in a way you expect.
This example constructs list with loop from lists of dicts:
- set_fact:
foo: '{{ foo|d([]) + [item.value] }}'
loop:
- value: 1
- value: 2
Basically, each execution of set_fact creates a fact. You may refer to the same fact in jinja expression for set_fact, but you can't expect it to automatically build lists or something like that.

Resources