Nested loops with multiple variables ansible - ansible

So I have a variable file that looks like this:
etherchannels:
channel1:
- groupid: 1
mode: on
members:
- Ethernet1/0
- Ethernet1/2
channel2:
- groupid: 2
mode: on
members:
- Ethernet2/0
- Ethernet2/2
and I want to pass it into something like this:
tasks:
- name: configure etherchannel
cisco.ios.ios_lag_interfaces:
config:
- name: Port-channel{{ item.groupid }}
members:
- member: "{{ item.member }}"
mode: "{{ item.mode }}"
loop: "{{ etherchannels }}"
My expected outcome should be something like this:
Channel 1:
name: portchannel1
members:
- member: Ethernet1/0
mode: on
- member: Ethernet1/1
mode:on
Channel 2:
name: portchannel2
members:
- member: Ethernet2/0
mode: on
- member: Ethernet2/1
mode:on
I'm stuck trying to figure out how to loop through the members variables without having to manually add a member and mode line for every interfaces.

See community.general dictionaries. For example, the playbook
- hosts: localhost
vars:
etherchannels:
channel1:
- groupid: 1
mode: on
members:
- Ethernet1/0
- Ethernet1/2
channel2:
- groupid: 2
mode: on
members:
- Ethernet2/0
- Ethernet2/2
tasks:
- debug:
msg: |
name: {{ _name }}
members:
{{ _members|to_yaml|indent(2) }}
loop: "{{ etherchannels|dict2items }}"
loop_control:
label: "{{ item.key }}"
vars:
_name: "portchannel{{ item.value.0.groupid }}"
_members: "{{ item.value.0.members|
map('community.general.dict_kv', 'member')|
map('combine', {'mode': item.value.0.mode})|list }}"
gives (abridged)
ok: [localhost] => (item=channel1) =>
msg: |-
name: portchannel1
members:
- {member: Ethernet1/0, mode: true}
- {member: Ethernet1/2, mode: true}
ok: [localhost] => (item=channel2) =>
msg: |-
name: portchannel2
members:
- {member: Ethernet2/0, mode: true}
- {member: Ethernet2/2, mode: true}

Related

How can nest loops inside of module functions in ansible?

Hopefully I worded this question correctly.
I have a group_vars file that defines the following:
etherchannels:
access2:
- channelnumber: 1
interfaces:
- FastEthernet 1/14
- FastEthernet 1/15
access1:
- channelnumber: 1
interfaces:
- FastEthernet 1/14
- FastEthernet 1/15
And my playbook looks like this:
- name: LAN Switches
hosts: access
tasks:
- name: config unused access ports
cisco.ios.ios_lag_interfaces:
config:
- name: "{{ item.channelnumber }}"
members:
- member: "{{ item.interface }}"
mode: on
loop: "{{ etherchannels[inventory_hostname] }}"
The expected outcome is:
ansible will run through the task for each hostnames under etherchannels(access 1 and 2)
for each hostnames it would run the "name" function, defining the etherchannel and then
for each etherchannel it would run the "members" function and add every listed interface
So it would function something like this, assuming I added more etherchannels:
name: 1
members:
- member: FastEthernetX
mode: on
- member: FastEthernetY
mode: on
name: 2
members:
- member: FastEthernetZ
mode: on
- member: FastEthernetA
mode: on
I've tried to add a loop inside the "cisco.ios.ios_lag_interfaces" module command but no dice.
This looks like a job for the subelements filter. In the following example, I've wrapped your task in a debug task so that I can run it locally and demonstrate the concept:
- hosts: all
gather_facts: false
vars:
etherchannels:
access2:
- channelnumber: 1
interfaces:
- FastEthernet 1/14
- FastEthernet 1/15
access1:
- channelnumber: 1
interfaces:
- FastEthernet 1/14
- FastEthernet 1/15
tasks:
- debug:
msg: |
cisco.ios.ios_lag_interfaces:
config:
- name: "{{ item.0.channelnumber }}"
members:
- member: "{{ item.1 }}"
mode: on
loop: "{{ etherchannels[inventory_hostname]|subelements('interfaces') }}"
If I run the above playbook, the output looks like:
TASK [debug] ******************************************************************************************************************************************************************************************************
ok: [access1] => (item=[{'channelnumber': 1, 'interfaces': ['FastEthernet 1/14', 'FastEthernet 1/15']}, 'FastEthernet 1/14']) => {
"msg": "cisco.ios.ios_lag_interfaces:\n config:\n - name: \"1\"\n members:\n - member: \"FastEthernet 1/14\"\n mode: on\n"
}
ok: [access2] => (item=[{'channelnumber': 1, 'interfaces': ['FastEthernet 1/14', 'FastEthernet 1/15']}, 'FastEthernet 1/14']) => {
"msg": "cisco.ios.ios_lag_interfaces:\n config:\n - name: \"1\"\n members:\n - member: \"FastEthernet 1/14\"\n mode: on\n"
}
ok: [access1] => (item=[{'channelnumber': 1, 'interfaces': ['FastEthernet 1/14', 'FastEthernet 1/15']}, 'FastEthernet 1/15']) => {
"msg": "cisco.ios.ios_lag_interfaces:\n config:\n - name: \"1\"\n members:\n - member: \"FastEthernet 1/15\"\n mode: on\n"
}
ok: [access2] => (item=[{'channelnumber': 1, 'interfaces': ['FastEthernet 1/14', 'FastEthernet 1/15']}, 'FastEthernet 1/15']) => {
"msg": "cisco.ios.ios_lag_interfaces:\n config:\n - name: \"1\"\n members:\n - member: \"FastEthernet 1/15\"\n mode: on\n"
}
I think that's exactly what you were looking for. Your actual task would of course drop the debug wrapper:
- hosts: all
gather_facts: false
vars:
etherchannels:
access2:
- channelnumber: 1
interfaces:
- FastEthernet 1/14
- FastEthernet 1/15
access1:
- channelnumber: 1
interfaces:
- FastEthernet 1/14
- FastEthernet 1/15
tasks:
= cisco.ios.ios_lag_interfaces:
config:
- name: "{{ item.0.channelnumber }}"
members:
- member: "{{ item.1 }}"
mode: on
loop: "{{ etherchannels[inventory_hostname]|subelements('interfaces') }}"

Ansible with VMWare to create Dictionary using Ansible

I am trying to create a dictionary with lists of items for a VMWare details collection. I was able to create the list individually. But not sure how to merge this.
- name: Gather DC info
community.vmware.vmware_datacenter_info:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
validate_certs: false
register: datacenter_infor
- name: Set DC_name variable
set_fact:
# dc_name: "{{ item.name }}"
dc_name: >-
{{ (dc_name | default([]))
+ [item.name]
}}
loop: "{{ datacenter_infor.datacenter_info }}"
- name: Gather cluster info
vmware_cluster_info:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
validate_certs: false
datacenter: "{{ item }}"
register: cluster_info
loop: "{{ dc_name }}"
- name: Set Host_name variable
set_fact:
host_name_list: >-
{{ (host_name_list | default([]))
+ data
}}
vars:
data: "{{ item.clusters.values() }}"
loop: "{{ cluster_info.results }}"
This will result in below output:
TASK [Set Host_name variable] *********************************************************************************************************************************************************************************************
ok: [localhost] => (item={'changed': False, 'clusters': {'PQR-CLU01': {'hosts': [{'name': 'PQR-cn0001.myhost.com', 'folder': '/PQR/host/PQR-CLU01'}, {'name': 'PQR-cn0002.myhost.com', 'folder': '/PQR/host/PQR-CLU01'}})
ok: [localhost] => (item={'changed': False, 'clusters': {'ABC-CLU01': {'hosts': [{'name': 'ABC-cn0002.myhost.com', 'folder': '/ABC/host/ABC-CLU01'}, {'name': 'ABC-cn0001.myhost.com', 'folder': '/ABC/host/ABC-CLU01'}})
How can I create a dictionary with above items like using ansible:
{'PQR-CLU01': ['PQR-cn0001.myhost.com','PQR-cn0002.myhost.com'],'ABC-CLU01':['ABC-cn0002.myhost.com','ABC-cn0001.myhost.com']
Given the data
cluster_info:
results:
- changed: false
clusters:
PQR-CLU01:
hosts:
- folder: /PQR/host/PQR-CLU01
name: PQR-cn0001.myhost.com
- folder: /PQR/host/PQR-CLU01
name: PQR-cn0002.myhost.com
- changed: false
clusters:
ABC-CLU01:
hosts:
- folder: /ABC/host/ABC-CLU01
name: ABC-cn0002.myhost.com
- folder: /ABC/host/ABC-CLU01
name: ABC-cn0001.myhost.com
Q: "Create dictionary (below)."
cluster_dict:
ABC-CLU01:
- ABC-cn0002.myhost.com
- ABC-cn0001.myhost.com
PQR-CLU01:
- PQR-cn0001.myhost.com
- PQR-cn0002.myhost.com
A: Create the list of clusters
cluster_list: "{{ cluster_info.results|
map(attribute='clusters')|
map('dict2items')|
flatten }}"
gives
cluster_list:
- key: PQR-CLU01
value:
hosts:
- folder: /PQR/host/PQR-CLU01
name: PQR-cn0001.myhost.com
- folder: /PQR/host/PQR-CLU01
name: PQR-cn0002.myhost.com
- key: ABC-CLU01
value:
hosts:
- folder: /ABC/host/ABC-CLU01
name: ABC-cn0002.myhost.com
- folder: /ABC/host/ABC-CLU01
name: ABC-cn0001.myhost.com
Create a list of keys
cluster_keys: "{{ cluster_list|
map(attribute='key')|
list }}"
gives
cluster_keys:
- PQR-CLU01
- ABC-CLU01
Create a list of values
cluster_vals: "{{ cluster_list|
map(attribute='value.hosts')|
map('map', attribute='name')|
list }}"
gives
cluster_vals:
- - PQR-cn0001.myhost.com
- PQR-cn0002.myhost.com
- - ABC-cn0002.myhost.com
- ABC-cn0001.myhost.com
Crate the dictionary
cluster_dict: "{{ dict(cluster_keys|zip(cluster_vals)) }}"
gives
cluster_dict:
ABC-CLU01:
- ABC-cn0002.myhost.com
- ABC-cn0001.myhost.com
PQR-CLU01:
- PQR-cn0001.myhost.com
- PQR-cn0002.myhost.com
Example of a complete playbook
- hosts: localhost
vars:
cluster_info:
results:
- changed: false
clusters:
PQR-CLU01:
hosts:
- folder: /PQR/host/PQR-CLU01
name: PQR-cn0001.myhost.com
- folder: /PQR/host/PQR-CLU01
name: PQR-cn0002.myhost.com
- changed: false
clusters:
ABC-CLU01:
hosts:
- folder: /ABC/host/ABC-CLU01
name: ABC-cn0002.myhost.com
- folder: /ABC/host/ABC-CLU01
name: ABC-cn0001.myhost.com
cluster_list: "{{ cluster_info.results|
map(attribute='clusters')|
map('dict2items')|
flatten }}"
cluster_keys: "{{ cluster_list|
map(attribute='key')|
list }}"
cluster_vals: "{{ cluster_list|
map(attribute='value.hosts')|
map('map', attribute='name')|
list }}"
cluster_dict: "{{ dict(cluster_keys|zip(cluster_vals)) }}"
tasks:
- debug:
var: cluster_dict

ansible variable basing on hostname's suffix

I need to create a user on a system depending on it's function (test or production)
Test systems are named with suffix -tXX while prod ones are with -pXX (where XX is two digits numbering).
in variable file I have set:
auser: "{{ 'usertest' if {{ ansible_hostname|lower }} | regex_search('t[0-9]{2}$') else 'userprod' }}"
the error I get is during playbook run is:
fatal: [192.168.1.10]: FAILED! => {"msg": "An unhandled exception occurred while templating '{{ 'usertest' if {{ ansible_hostname|lower }} | regex_search('t[0-9]{2}$') else 'userprod' }}'. Error was a <class 'ansible.errors.AnsibleError'>, original message: template error while templating string: expected token ':', got '}'. String: {{ 'usertest' if {{ ansible_hostname|lower }} | regex_search('t[0-9]{2}$') else 'userprod' }}"}
Complete playbook is:
---
- hosts: testgrp
become: yes
vars:
- auser: "{{ 'usertest' if {{ ansible_hostname|lower }} | regex_search('t[0-9]{2}$') else 'userprod' }}"
tasks:
- name: Add groups
group:
name: "{{ item.name }}"
gid: "{{ item.gid }}"
state: present
loop:
- { name: 'group1', gid: '1101' }
- { name: 'group2', gid: '1102' }
- { name: 'group3', gid: '1103' }
- name: Add users
user:
name: "{{ item.name }}"
group: "{{ item.group }}"
groups: "{{ item.groups }}"
state: present
loop:
- { name: "{{ auser }}", group: 'group1', groups: 'group2,group3', uid: '1101' }
For example
- hosts: srv-t01,srv-p01
gather_facts: false
vars:
auser: "{{ inventory_hostname | lower is match('^.*-t[0-9]{2}$') |
ternary('usertest', 'userprod') }}"
tasks:
- debug:
var: auser
gives
ok: [srv-t01] =>
auser: usertest
ok: [srv-p01] =>
auser: userprod
A more robust solution is to select an index, e.g. the play below gives the same result
- hosts: srv-t01,srv-p01
gather_facts: false
vars:
auser_dict:
t: usertest
p: userprod
aindex: "{{ inventory_hostname | lower |
regex_replace('^.*-(.+)[0-9]{2}$', '\\1') }}"
auser: "{{ auser_dict[aindex] }}"
tasks:
- debug:
var: auser

How to parse text with ansible using a nested list/dict?

I have a text file that needs to be parsed so i can use it via a REST API with ansible.
10.0.0.0/16 Building-A
10.1.0.0/16 Building-A
10.2.0.0/16 Building-B
10.3.0.0/16 Building-B
I need to convert this text to something like this:
{
"parsed":[
{
"Building-A":[
"10.0.0.0/16",
"10.1.0.0/16"
]
},
{
"Building-B":[
"10.2.0.0/16",
"10.3.0.0/16"
]
}
]
}
Currently i run the following playbook to test the textparsing, without success. The list created is not unique.
- name: Test
hosts: localhost
tasks:
- name: Combine
ansible.builtin.set_fact:
parsed: "{{ (parsed | default([])) | union( [{item.split()[1]: item.split()[0] }] ) }}"
loop: "{{ lookup('file','hostgroups.txt').strip().splitlines() }}"
- name: Debug
ansible.builtin.debug:
var: parsed
ok: [localhost] => {
"parsed": [
{
"Building-A": "10.0.0.0/16"
},
{
"Building-A": "10.1.0.0/16"
},
{
"Building-B": "10.2.0.0/16"
},
{
"Building-B": "10.3.0.0/16"
}
]
}
Thanks for the tip with the helper variables and the groupby filter. Here is the final playbook:
- name: Test
hosts: localhost
# strategy: free
tasks:
- name: Get List
ansible.builtin.set_fact:
parsed_list: "{{ parsed_list | default([]) + [_item] }}"
loop: "{{ lookup('file','hostgroups.txt').strip().splitlines() }}"
vars:
_list: "{{ item.split() }}"
_item: "{{ {'Name': _list[1], 'Subnet': _list[0] } }}"
- name: Debug parsed_list
ansible.builtin.debug:
var: parsed_list
- name: Group parsed_list
ansible.builtin.set_fact.set_fact:
parsed_group: "{{ parsed_group | default([]) + [{_key: _value}] }}"
loop: "{{ parsed_list | groupby('Name') }}"
vars:
_key: "{{ item.0 }}"
_value: "{{ item.1 | map(attribute='Subnet') | list }}"
- name: Debug parsed_group
ansible.builtin.debug:
var: parsed_group
Parse the content, e.g.
- set_fact:
parsed_list: "{{ parsed_list|d([]) + [_item] }}"
loop: "{{ lookup('file','hostgroups.txt').splitlines() }}"
vars:
_array: "{{ item.split() }}"
_item: "{{ {'building': _array.1, 'ip': _array.0} }}"
gives
parsed_list:
- building: Building-A
ip: 10.0.0.0/16
- building: Building-A
ip: 10.1.0.0/16
- building: Building-B
ip: 10.2.0.0/16
- building: Building-B
ip: 10.3.0.0/16
Then, use filter groupby and create the list
- set_fact:
parsed: "{{ parsed|d([]) + [{_key: _val}] }}"
loop: "{{ parsed_list|groupby('building') }}"
vars:
_key: "{{ item.0 }}"
_val: "{{ item.1|map(attribute='ip')|list }}"
gives
parsed:
- Building-A:
- 10.0.0.0/16
- 10.1.0.0/16
- Building-B:
- 10.2.0.0/16
- 10.3.0.0/16
In some cases, a dictionary might be a better structure, e.g.
- set_fact:
parsed_dict: "{{ parsed_dict|d({})|combine({_key: _val}) }}"
loop: "{{ parsed_list|groupby('building') }}"
vars:
_key: "{{ item.0 }}"
_val: "{{ item.1|map(attribute='ip')|list }}"
gives
parsed_dict:
Building-A:
- 10.0.0.0/16
- 10.1.0.0/16
Building-B:
- 10.2.0.0/16
- 10.3.0.0/16

Ansible: Merge list to new list with item.key and item.value

Example:
file: storages.yml
---
storages:
- storageA
- storageB
file: storageA_list_vv.yml
---
vv:
- storageA_lun01
- storageA_lun02
file: storageB_list_vv.yml
---
vv:
- storageB_lun01
Expected new list: vvset.yml
---
vvset:
- key: serverA
value: serverA_lun01
- key: serverA
value: serverA_lun02
- key: serverB
value: serverB_lun01
Appreciate your guidance and advise on how to combine three list storages.yml, storageA_list_vv.yml and storageB_list_vv.yml into new list vvset.yml.
Thanks in advance.
Below is the step by step "brute-force" approach. Custom filter would make it much simpler.
Read the files and create the list of all items
- set_fact:
vv: "{{ vv|default([]) +
(lookup('file', item)|from_yaml).values()|list }}"
loop:
- storageA_list_vv.yml
- storageB_list_vv.yml
- debug:
var: vv|flatten
gives
vv|flatten:
- storageA_lun01
- storageA_lun02
- storageB_lun01
Extract the data
- set_fact:
vvset1: "{{ vvset1|default([]) +
[{'index': index, 'lun': lun}] }}"
loop: "{{ vv|flatten }}"
vars:
index: "{{ item.split('_').0|
regex_replace('^storage(.*)$', '\\1')}}"
lun: "{{ item.split('_').1 }}"
- debug:
var: vvset1
gives
vvset1:
- index: A
lun: lun01
- index: A
lun: lun02
- index: B
lun: lun01
Use groupby
- set_fact:
vvset2: "{{ vvset2|default([]) +
[{'index': item.0, 'lun': item.1|map(attribute='lun')|list}] }}"
loop: "{{ vvset1|groupby('index') }}"
- debug:
var: vvset2
gives
vvset2:
- index: A
lun:
- lun01
- lun02
- index: B
lun:
- lun01
Use subelements
- set_fact:
vvset3: "{{ vvset3|default([]) +
[{'key': key, 'value': val}] }}"
with_subelements:
- "{{ vvset2 }}"
- lun
vars:
key: "{{ 'server' ~ item.0.index }}"
val: "{{ 'server' ~ item.0.index ~ '_' ~ item.1 }}"
- debug:
var: vvset3
gives
vvset3:
- key: serverA
value: serverA_lun01
- key: serverA
value: serverA_lun02
- key: serverB
value: serverB_lun01

Resources