unable to naviate data structure in ansible using with_subelements - ansible

Can someone please explain why this code does not work? I want to expand it to a larger dataset, but I can't get this simple version working. It looks the same as an example I found to me.
1 ---
2
3 - hosts: localhost
4 vars:
5 GENERAL:
6 - CE_HOSTNAME: ROUTER1
7 CE_NEW: true
8
9 tasks:
10
11 - debug: debug
12 msg: "{{ item.0.CE_HOSTNAME }}"
13 with_subelements:
14 - "{{ GENERAL }}"

There are no sublists (nested lists) in the list GENERAL. with_subelements can't work here. Use simple loop instead. For example,
- hosts: localhost
vars:
GENERAL:
- CE_HOSTNAME: ROUTER1
CE_NEW: true
tasks:
- debug:
var: item
loop: "{{ GENERAL }}"
gives (abridged)
item:
CE_HOSTNAME: ROUTER1
CE_NEW: true
with_subelements can be used if there are nested lists. For example
- hosts: localhost
vars:
GENERAL:
- CE_HOSTNAME: ROUTER1
CE_NEW: true
CE_LIST:
- item1
- item2
- item3
tasks:
- debug:
var: item
with_subelements:
- "{{ GENERAL }}"
- CE_LIST
gives the product of the lists (abridged)
item:
- CE_HOSTNAME: ROUTER1
CE_NEW: true
- item1
item:
- CE_HOSTNAME: ROUTER1
CE_NEW: true
- item2
item:
- CE_HOSTNAME: ROUTER1
CE_NEW: true
- item3

Related

how to select values from lists

I have 6 hosts defined, these hosts have multiple IPv4's. I need a subset of IPv4's from these lists of IP's.
This is what I run
- hosts: gluster_mariadb
gather_facts: true
tasks:
- set_fact:
main_nodes_ips: "{{ groups.zicluster |
map('extract', hostvars, 'ansible_all_ipv4_addresses') |
list }}"
run_once: true
- debug:
var: main_nodes_ips
run_once: true
The response is:
ok: [zi04] => {
"main_nodes_ips": "[ ['172.18.0.1', '192.168.17.209', '172.17.0.1', '169.254.135.46', '169.254.74.141'], ['172.18.0.1', '192.168.17.208', '169.254.118.121', '169.254.53.186', '172.17.0.1'], ['172.18.0.1', '192.168.17.196', '172.17.0.1', '169.254.211.146', '169.254.1.199'], ['172.18.0.1', '192.168.17.159', '169.254.234.110', '169.254.150.56', '172.17.0.1'], ['172.18.0.1', '192.168.17.160', '172.17.0.1', '169.254.113.116', '169.254.97.143'], ['172.18.0.1', '192.168.17.161', '172.17.0.1', '169.254.240.6', '169.254.74.189']]"
}
I try to do a subselection with this:
- debug:
var:
main_nodes_ips | select('match', '^192') | list
This returns no data, I think because it is a list of lists.
So I try to do the subselection earlier. Like this:
- hosts: gluster_mariadb
gather_facts: true
tasks:
- set_fact:
main_nodes_ips: "{{ groups.zicluster |
map('extract', hostvars, 'ansible_all_ipv4_addresses') |
select('match', '^192') |
list }}"
run_once: true
- debug:
var: main_nodes_ips
run_once: true
This also returns no data.
ok: [zi04] => {
"main_nodes_ips": []
}
What I am looking for is something like this:
ok: [zi04] => {
"main_nodes_ips":
[
"192.168.17.100",
"192.168.17.101",
"192.168.17.102",
"192.168.17.103",
"192.168.17.104",
"192.168.17.105"
]
}
The variable main_nodes_ips is a list of lists
main_nodes_ips:
- - 172.18.0.1
- 192.168.17.209
- 172.17.0.1
- 169.254.135.46
- 169.254.74.141
- - 172.18.0.1
- 192.168.17.208
- 169.254.118.121
- 169.254.53.186
- 172.17.0.1
- - 172.18.0.1
- 192.168.17.196
- 172.17.0.1
- 169.254.211.146
- 169.254.1.199
- - 172.18.0.1
- 192.168.17.159
- 169.254.234.110
- 169.254.150.56
- 172.17.0.1
- - 172.18.0.1
- 192.168.17.160
- 172.17.0.1
- 169.254.113.116
- 169.254.97.143
- - 172.18.0.1
- 192.168.17.161
- 172.17.0.1
- 169.254.240.6
- 169.254.74.189
You can't use the filter select directly. You have to map the filter select to each item
main_nodes_192: "{{ main_nodes_ips|
map('select', 'match', '^192.*$')|
flatten }}"
gives
main_nodes_192:
- 192.168.17.209
- 192.168.17.208
- 192.168.17.196
- 192.168.17.159
- 192.168.17.160
- 192.168.17.161

Sorting list in Ansible in natural alphanumeric order

Is there a way to sort a list in Ansible or Jinja in a natural way?
For example this is the list
test
test123
test12
test5
test1234test
test22te
And I need it to take in account the numbers as whole not as individual so test12 is under test5 and so on.
Given the list
l1:
- test
- test123
- test12
- test5
- test1234test
- test22te
Create a list with an attribute index of type integer, e.g.
- set_fact:
l2: "{{ l2|default([]) +
[{'index': (_index|length > 0)|ternary(_index|int, 0),
'name': item}] }}"
loop: "{{ l1 }}"
vars:
_regex: '^test(\d*)\D*$'
_replace: '\1'
_index: "{{ item|regex_replace(_regex, _replace) }}"
- debug:
msg: "{{ l2|sort(attribute='index')|
map(attribute='name')|
list }}"
gives
msg:
- test
- test5
- test12
- test22te
- test123
- test1234test
Without iteration, declare the variables
_regex: '^test(\d*)\D*$'
_replace: '\1'
_index: "{{ l1|map('regex_replace', _regex, _replace)|map('int')|list }}"
l2: "{{ dict(_index|zip(l1))|
dict2items|
sort(attribute='key')|
map(attribute='value')|
list }}"
gives the same result
l2:
- test
- test5
- test12
- test22te
- test123
- test1234test
Example of a complete playbook for testing
- hosts: localhost
vars:
l1:
- test
- test123
- test12
- test5
- test1234test
- test22te
_regex: '^test(\d*)\D*$'
_replace: '\1'
_index: "{{ l1|map('regex_replace', _regex, _replace)|map('int')|list }}"
l2: "{{ dict(_index|zip(l1))|
dict2items|
sort(attribute='key')|
map(attribute='value')|
list }}"
tasks:
- debug:
var: _index
- debug:
var: l2
In the end made jinja filter "natsort_filter.py" that does what I need. Get it here if you like:
https://gitlab.privatecloud.sk/vladoportos/custom-jinja-filters
It does require natsort module for python, but than it works just fine :)

"search" a string in "when:" conditional statement?

In the following playbook, I have to use search in a when: statement. Can you please tell me what is wrong with the statement when: item.name is search (domain_list)? domain_list is an array variable defined in files.yml as shown below.
---
- hosts: localhost
gather_facts: False
connection: local
vars_files:
- files.yml
vars:
infra:
- name: ironman.vcloud-lab.com
connection_state: CONNECTED
- name: hulk.vcloud-lab.com
connection_state: CONNECTED
- name: captain.vcloud-lab.com
connection_state: DISCONNECTED
- name: hawkeye.vcloud-lab.com
connection_state: DISCONNECTED
tasks:
- name: Filter list of only connected esxi
set_fact:
esxilist: "{{esxilist | default([]) + [item]}}"
with_items: "{{infra}}"
when: item.name is search (domain_list) ## <= please correct me here, it doesn't work
- name: Show connected esxi list information
debug:
var: esxilist
files.yml
---
domain_list:
- ironman
- captain
Select the short name and test the presence in the list. For example, use regex_replace
- name: Filter list of only connected esxi
set_fact:
esxilist: "{{ esxilist|default([]) + [item] }}"
loop: "{{ infra }}"
when: _short in domain_list
vars:
_short: "{{ item.name|regex_replace('^(.*?)\\.(.*)$', '\\1') }}"
gives
esxilist:
- connection_state: CONNECTED
name: ironman.vcloud-lab.com
- connection_state: DISCONNECTED
name: captain.vcloud-lab.com
The next option is to split the string and select the first item. For example
vars:
_short: "{{ item.name.split('.')|first }}"
gives the same result.
See How to create a Minimal, Reproducible Example. For example
- hosts: localhost
vars:
domain_list: [a, b]
infra:
- {name: a.vcloud-lab.com, state: CONNECTED}
- {name: b.vcloud-lab.com, state: DISCONNECTED}
- {name: c.vcloud-lab.com, state: CONNECTED}
tasks:
- set_fact:
l1: "{{ l1|default([]) + [item] }}"
with_items: "{{ infra }}"
when: _short in domain_list
vars:
_short: "{{ item.name.split('.')|first }}"
- debug:
var: l1

ansible get result from role and append to a list

I have a play like this
---
- name: List images in ACRs
any_errors_fatal: true
hosts:
- localhost
gather_facts: false
vars:
acrs: ["registry1", "registry2"]
tasks:
- name: list repos
with_items: "{{ acrs }}"
include_role:
name: list_docker_image_repos
vars:
registry_name: "{{ item }}"
list_docker_image_repos will do set_fact which a list of dicts.
How can I append all the facts (from every iteration) to a list?
Or is there are different way to do this?
thanks
In each iteration put the list into a dictionary. For example, given the role
shell> cat roles/list_docker_image_repos/tasks/main.yml
- set_fact:
docker_image_repos: "{{ ['repo1', 'repo2', 'repo3']|
product([registry_name])|
map('join', '-')|
list }}"
- set_fact:
my_lists: "{{ my_lists|
combine({registry_name: docker_image_repos}) }}"
the playbook
- hosts: localhost
vars:
acrs: [reg1, reg2]
my_lists: {}
tasks:
- name: list repos
include_role:
name: list_docker_image_repos
loop: "{{ acrs }}"
vars:
registry_name: "{{ item }}"
- debug:
var: my_lists
gives the dictionary
my_lists:
reg1:
- repo1-reg1
- repo2-reg1
- repo3-reg1
reg2:
- repo1-reg2
- repo2-reg2
- repo3-reg2
A dictionary is a more suitable structure for this purpose compared with a list. There are many ways how to use it. For example, you can
extract the lists
- debug:
msg: "{{ acrs|map('extract', my_lists)|list }}"
gives
msg:
- - repo1-reg1
- repo2-reg1
- repo3-reg1
- - repo1-reg2
- repo2-reg2
- repo3-reg2
Use the filter flatten to put all items into a single list
- debug:
msg: "{{ acrs|map('extract', my_lists)|flatten }}"
gives
msg:
- repo1-reg1
- repo2-reg1
- repo3-reg1
- repo1-reg2
- repo2-reg2
- repo3-reg2
Use the filter dict2items to iterate items
- debug:
var: item
loop: "{{ my_lists|dict2items }}"
gives (abridged)
item:
key: reg1
value:
- repo1-reg1
- repo2-reg1
- repo3-reg1
item:
key: reg2
value:
- repo1-reg2
- repo2-reg2
- repo3-reg2
Or, use the lookup plugin subelements to iterate the items of the lists as well
- debug:
var: item
with_subelements:
- "{{ my_lists|dict2items }}"
- value
gives (abridged)
item:
- key: reg1
- repo1-reg1
item:
- key: reg1
- repo2-reg1
item:
- key: reg1
- repo3-reg1
item:
- key: reg2
- repo1-reg2
item:
- key: reg2
- repo2-reg2
item:
- key: reg2
- repo3-reg2
Example of a complete playbook for testing
- hosts: localhost
vars:
acrs: [reg1, reg2]
my_lists: {}
tasks:
- name: list repos
include_role:
name: list_docker_image_repos
loop: "{{ acrs }}"
vars:
registry_name: "{{ item }}"
- debug:
var: my_lists
- debug:
msg: "{{ acrs|map('extract', my_lists)|list }}"
- debug:
msg: "{{ acrs|map('extract', my_lists)|flatten }}"
- debug:
var: item
loop: "{{ my_lists|dict2items }}"
- debug:
var: item
with_subelements:
- "{{ my_lists|dict2items }}"
- value

How to run Ansible task to a limit number of items?

I have a dynamic inventory which returns me my hosts addresses.
But sometimes, I would like to apply some configuration to a limited number of hosts.
A sample with N hosts but only 5 are echoed:
- name: "Run silly shell script"
shell: |
echo {{ item }}
exit 0
with_items: "{{ hosts | only(5) }}"
to get the first X elements from a list, use: list_var[:X].
full example as PB below:
---
- hosts: localhost
gather_facts: false
vars:
list_var:
- 1
- 2
- 3
- 4
- 5
- 6
tasks:
- name: print full list
debug:
var: list_var
- name: print list of first 3 elements
debug:
var: list_var[:3]
hope it helps.

Resources