Ansible: How to extract first element from list? - ansible

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.

Related

Ansible - List of dictionaries

Let me introduce my problem. I have some list of dictionary in my Ansible code:
my_example_list = [
{
"key1" : "value_of_first_key"
},
{
"key2": "value_of_second_key"
},
{
"key3": "value_of_third_key"
}
]
I need execute command which will iterate over this list and it should look something like:
- name: 'Example'
shell: 'Here is my {{ item.key }} and here is {{ item.value }}'
What I've do or try to do:
I was trying to do that with with_items but i'm not able to point into value of particular key.
I've also try to filter values using | first and | last but it's not worked in my case.
What I want to achieve:
Creating loop which will iterate via that list and inject separated key and value into command.
I was asked to show how I was trying to resolve my issue:
Here is some code:
# Showing last component failing
- name: "Try to show last component of my list"
debug:
msg: "{{ my_example_list[1] | last }}"
# When i'm trying to show first component of my list i get "key1"
- name: "Try to show first component of my list"
debug:
msg: "{{ my_example_list[1] | first }}"
# This shows me my list of dict
- name: "Trying use with_items"
debug:
msg: "{{ item }}"
with_items: "{{ my_example_list }}"
# But when i'm trying point to key and value for example
- name: "Trying use with_items point to key and value"
debug:
msg: "Here is my {{ item.key }} which keep {{ item.value }}"
with_items: "{{ my_example_list }}"
# It's failing.
Sorry it's not maybe solution with using loop. I'm just stack with that issue over few days... And as first step I want to know how correctly point to pair keys and values.
It also works well:
- name: Correct solution
debug:
msg: "This is my {{ item.key }} and my value {{ item.value }}"
with_dict: "{{ my_example_list }}"
Thanks #U880D for help! I'm not able to add some plus for your solution because I'm new joiner. Appreciate your answer! :)
Your data structure and naming seems to be unfavorable. There is no need to number the key name and therefore it should be avoided. Furthermore counting list elements in Python starts at 0 not 1.
The following minimal example playbook
---
- hosts: localhost
become: false
gather_facts: false
vars:
example_list: |
[
{
"key1" : "value_of_first_key"
},
{
"key2": "value_of_second_key"
},
{
"key3": "value_of_third_key"
}
]
tasks:
- name: Example loop
debug:
msg: "{{ item }} is of type {{ item | type_debug }}"
loop: "{{ example_list }}"
- name: Example loop
debug:
msg: "{{ item.values() }}"
loop: "{{ example_list }}"
will result into an output of
TASK [Example loop] ******************************************
ok: [localhost] => (item={u'key1': u'value_of_first_key'}) =>
msg: '{u''key1'': u''value_of_first_key''} is of type dict'
ok: [localhost] => (item={u'key2': u'value_of_second_key'}) =>
msg: '{u''key2'': u''value_of_second_key''} is of type dict'
ok: [localhost] => (item={u'key3': u'value_of_third_key'}) =>
msg: '{u''key3'': u''value_of_third_key''} is of type dict'
TASK [Example loop] ******************************************
ok: [localhost] => (item={u'key1': u'value_of_first_key'}) =>
msg:
- value_of_first_key
ok: [localhost] => (item={u'key2': u'value_of_second_key'}) =>
msg:
- value_of_second_key
ok: [localhost] => (item={u'key3': u'value_of_third_key'}) =>
msg:
- value_of_third_key
Further Readings
How to work with lists and dictionaries in Ansible
Extended loop variables

Ansible Fact - Accessing the additional facts

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.

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
...

Ansible variables wildcard selection

The only way I've found to select variables by a wildcard is to loop all variables and test match. For example
tasks:
- debug:
var: item
loop: "{{ query('dict', hostvars[inventory_hostname]) }}"
when: item.key is match("^.*_python_.*$")
shell> ansible-playbook test.yml | grep key:
key: ansible_python_interpreter
key: ansible_python_version
key: ansible_selinux_python_present
Is there a more efficient way to do it?
Neither json_query([?key=='name']), nor lookup('vars', 'name') work with wildcards.
Is there any other "wildcard-enabled" test, filter ...?
Note: regex_search is discussed in What is the syntax within the regex_search() to match against a variable?
You can select/reject with Jinja tests:
- debug:
msg: "{{ lookup('vars', item) }}"
loop: "{{ hostvars[inventory_hostname].keys() | select('match', '^.*_python_.*$') | list }}"
gives:
ok: [localhost] => (item=ansible_selinux_python_present) => {
"msg": false
}
ok: [localhost] => (item=ansible_python_version) => {
"msg": "2.7.10"
}
Update for Ansible 2.8 and higher versions.
There is the lookup plugin varnames added in Ansible 2.8. to "List of Python regex patterns to search for in variable names". See details
shell> ansible-doc -t lookup varnames
For example, to list the variables .*_python_.* the task below
- debug:
msg: "{{ item }}: {{ lookup('vars', item) }}"
loop: "{{ query('varnames', '^.*_python_.*$') }}"
gives
TASK [debug] ***************************************************************
ok: [localhost] => (item=ansible_python_interpreter) =>
msg: 'ansible_python_interpreter: /usr/bin/python3'
ok: [localhost] => (item=ansible_python_version) =>
msg: 'ansible_python_version: 3.8.5'
ok: [localhost] => (item=ansible_selinux_python_present) =>
msg: 'ansible_selinux_python_present: True
Moreover, the lookup plugin varnames will find also variables not 'instantiated' yet and therefore not included in the hostvars.

Resources