How can nest loops inside of module functions in ansible? - 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') }}"

Related

Ansible - Remove key-value Pair

I have a list of dictionaries
member:
- name: test2
orig: test2
- name: test1
orig: test1
and would like to remove the orig key and value from all dictionaries in the list.
- name: Print Firewall Group Member
debug:
msg: "{{ item }}"
loop: "{{ member }}"
TASK [Print Firewall Group Member] *****************************************************************************************
ok: [fortigate01] => (item={'name': 'test2', 'orig': 'test2'}) => {
"msg": {
"name": "test2",
"orig": "test2"
}
}
ok: [fortigate01] => (item={'name': 'test1', 'orig': 'test1'}) => {
"msg": {
"name": "test1",
"orig": "test1"
}
}
Use the filter ansible.utils.remove_keys. For example, given simplified data
member:
- name: test2
orig: test2
- name: test1
orig: test1
Declare the variable
names: "{{ member|ansible.utils.remove_keys(target=['orig']) }}"
gives
names:
- name: test2
- name: test1
Optionally, use the filter ansible.utils.keep_keys. The declaration below gives the same result
names: "{{ member|ansible.utils.keep_keys(target=['name']) }}"
The next option is json_query
names: "{{ member|json_query('[].{name: name}') }}"
Example of a complete playbook for testing
- hosts: localhost
vars:
member:
- name: test2
orig: test2
- name: test1
orig: test1
names: "{{ member|ansible.utils.remove_keys(target=['orig']) }}"
name2: "{{ member|ansible.utils.keep_keys(target=['name']) }}"
name3: "{{ member|json_query('[].{name: name}') }}"
tasks:
- debug:
var: names
- debug:
var: name2
- debug:
var: name3

Nested loops with multiple variables 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}

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

Extract a value from the output and then use it in next task

I'd like to extract just a one value from below output and to be exactly, the host line.
Like:
host: host-1.example.com
The playbook itself:
---
- name: Get fortios_configuration_fact
hosts: fortigate
connection: httpapi
collections:
- fortinet.fortios
gather_facts: no
vars:
ansible_httpapi_port: 4443
ansible_httpapi_use_ssl: yes
ansible_httpapi_validate_certs: no
username: some-name
tasks:
- name: Retrieve Facts of FortiOS Configurable Objects
fortinet.fortios.fortios_configuration_fact:
access_token: "{{ vault_access_token }}"
selector: "vpn.ssl.web_user-bookmark"
formatters:
- bookmarks
params:
name: "{{ username }}"
sorters:
- logon-user
register: ssl_vpn_results
- ansible.builtin.debug:
msg: "{{ ssl_vpn_results.meta.results }}"
And I have this Ansible output:
- bookmarks:
- additional-params: ''
apptype: vnc
color-depth: '16'
description:
domain: ''
folder: ''
form-data: []
host: host-1.example.com
keyboard-layout: en-us
load-balancing-info: ''
logon-password: ENC XXXX
logon-user: some-user
name: some-name
port: 5909
preconnection-blob: ''
preconnection-id: 0
q_origin_key: some-description
restricted-admin: disable
security: rdp
send-preconnection-id: disable
sso: disable
sso-credential: sslvpn-login
sso-credential-sent-once: disable
sso-password: ''
sso-username: ''
url: ''
custom-lang: ''
name: some-name
q_origin_key: some-description
Without '.meta.results' in debug message I have this output:
ok: [fortinet_vd] =>
msg:
changed: false
failed: false
meta:
build: 234
http_method: GET
http_status: 200
mkey: user-mkey
name: user-bookmark
path: vpn.ssl.web
results:
- bookmarks:
- additional-params: ''
apptype: vnc
color-depth: '16'
description: some-description
domain: ''
folder: ''
form-data: []
host: host-1.example.com
keyboard-layout: en-us
load-balancing-info: ''
logon-password: ENC XXXX
logon-user: some-user
name: some-name
port: 5909
preconnection-blob: ''
preconnection-id: 0
q_origin_key: some-user
restricted-admin: disable
security: rdp
send-preconnection-id: disable
sso: disable
sso-credential: sslvpn-login
sso-credential-sent-once: disable
sso-password: ''
sso-username: ''
url: ''
revision: 9e0c1a3432bb884fdebaeaefdb0a51be
serial: serial-nbr
status: success
vdom: root
version: v7.0.2
I tried many ansible filters to manipulate the data, but failed.
Can you help with any ideas?
Both attributes results and bookmarks are lists. You can get the first items from the lists, e.g.
- debug:
msg: "{{ ssl_vpn_results.meta.results.0.bookmarks.0.host }}"
gives
msg: host-1.example.com
There might be more items both in the results and bookmarks. You can use json_query to select all host, e.g.
- debug:
msg: "{{ ssl_vpn_results.meta.results|
json_query('[].bookmarks[].host') }}"
gives the list
msg:
- host-1.example.com
You can also iterate results and list all hosts for a particular item, e.g.
- debug:
msg: "{{ item.name }}: {{ item.bookmarks|json_query('[].host') }}"
loop: "{{ ssl_vpn_results.meta.results }}"
loop_control:
label: "{{ item.name }}"
gives
TASK [debug] *******************************************************
ok: [localhost] => (item=some-name) =>
msg: 'some-name: [''host-1.example.com'']'
you have to do this task: results and bookmarks are lists
- name: display
debug:
msg: the value of host is {{ ssl_vpn_results.meta.results.0.bookmarks.0.host }}
and you have the result wanted!!

Ansible assign random number and increase the retry attempts

I am trying to assign a random number between 7000 and 7005 to a variable which is not present in a list.
the list has 7000, 7001, 7004 and 7002.
- name: Set fact
set_fact:
val: "{{ 7004 | random(start=7000) }}"
until: val not in list
The above playbook tried to assign 3 times and failed. It did not assign the value 7003.
TASK [xxx] ******************************
task path: /tmp/awx_164677__l9__xmu/project/roles/xxxxx/tasks/main.yml:26
FAILED - RETRYING: Set fact (3 retries left).
FAILED - RETRYING: Set fact (2 retries left).
FAILED - RETRYING: Set fact (1 retries left).
fatal: [xxxprod]: FAILED! => {"ansible_facts": {"val": "7000"}, "attempts": 3, "changed": false}
How to increase the retry value from 3 to some other value?
Note: the above list was updated by this playbook, only the last value did not get updated.
Thanks.
retries playbook keyword can be used to specify the number of retries before giving up in a until loop. This setting is only used in combination with until Keyword.
- name: Set fact
set_fact:
val: "{{ 7004 | random(start=7000) }}"
until: val not in list
retries: 5
Here is the refereance for you to look more Retries Playbook Keyword
Make the random choice from the missing items only. The playbook below solves the general problem of randomly completing a sequence
- hosts: localhost
vars:
vals: [7000, 7003]
vals_all: [7000, 7001, 7002, 7003, 7004]
vals_missing: "{{ vals_all|difference(vals)|length }}"
tasks:
- set_fact:
vals2: "{{ vals }}"
- set_fact:
vals2: "{{ vals2 + [vals_all|difference(vals2)|random] }}"
with_sequence: end="{{ vals_missing }}"
- debug:
var: vals2
gives
vals2:
- 7000
- 7003
- 7002
- 7001
- 7004
If you want to iterate the random generation of the missing items the playbook below
- hosts: localhost
vars:
vals: [7000, 7003]
vals_all: [7000, 7001, 7002, 7003, 7004]
vals_missing: "{{ vals_all|difference(vals)|length }}"
tasks:
- set_fact:
vals2: "{{ vals }}"
- debug:
var: val
with_sequence: end="{{ vals_missing }}"
vars:
val: "{{ [vals_all|difference(vals2)|random] }}"
vals2: "{{ vals2 + [vals] }}"
gives
ok: [localhost] => (item=1) =>
ansible_loop_var: item
item: '1'
val:
- 7002
ok: [localhost] => (item=2) =>
ansible_loop_var: item
item: '2'
val:
- 7004
ok: [localhost] => (item=3) =>
ansible_loop_var: item
item: '3'
val:
- 7001
Change the variable vals: [7000, 7001, 7004, 7002] to solve your problem. (But, it's trivial because only a single item is missing.)

Resources