Looping over list data in Ansible playbooks - ansible

In my quest to automate some of our network environment I would like to know how you could loop over different items in nested lists. To make it more clear I will explain what I want to do using my existing inventory and playbook.
Inventory looks as followed, (Dummy Content)
parameters:
- mode: ""
speed: ""
duplex: ""
interfaces:
- Int_One
- Int_Two
So as you can see I have a list containing some network information (parameters) and another list containing two interfaces. The action that I want to accomplish in my playbook is to configure those two interfaces with the information found in the "parameters" list. In other words, loop over the "interfaces" using the information in the "parameters" list. But as of right now I can't get it to use the right data in the right time
Good to know is that I'm using a predefined "Cisco network module" in my playbook.
Playbook looks as followed,
- name: Deploy Network Interfaces
"Some network module":
mode: '{{ item.0.mode }}'
speed: '{{ item.0.speed }}'
duplex: '{{ item.0.duplex }}'
interface: '{{ item.1.interfaces }}'
state: present
delegate_to: localhost
loop:
- "{{ parameters }}"
- "{{ parameters|subelements('interfaces') }}"
As you can see the network module requires the "interface" to be provided. So again I want to iterate over the "interfaces" list and deploy them with the defined data in the "parameters" list.
Anyone that can tell me how to handle this issue?
Thanks in advance!

Your question is a little confusing. You have two variables; a list named parameters:
parameters:
- mode: ""
speed: ""
duplex: ""
And a list named interfaces:
interfaces:
- Int_One
- Int_Two
parameters has a single item, while interfaces has two. The fact that they have different numbers of items makes it hard to figure out how they are related. If each interface has unique parameters, you probably want something like this instead:
interfaces:
- name: Int_One
mode: ""
speed: ""
duplex: ""
- name: Int_Two
mode: ""
speed: ""
duplex: ""
In which case you would write your playbook like this:
- name: Deploy Network Interfaces
"Some network module":
mode: '{{ item.mode }}'
speed: '{{ item.speed }}'
duplex: '{{ item.duplex }}'
interface: '{{ item.name }}'
state: present
delegate_to: localhost
loop: "{{ interfaces }}"
On the other hand, if all interfaces will have the same parameters, then maybe you would structure your data like this:
parameters:
mode: ""
speed: ""
duplex: ""
interfaces:
- Int_One
- Int_Two
And write your playbook like this:
- name: Deploy Network Interfaces
"Some network module":
mode: '{{ parameters.mode }}'
speed: '{{ parameters.speed }}'
duplex: '{{ parameters.duplex }}'
interface: '{{ item.name }}'
state: present
delegate_to: localhost
loop: "{{ interfaces }}"

Given your example, a solution would be the following:
- hosts:
- localhost
gather_facts: False
vars:
parameters:
- mode: "auto"
speed: "1000"
duplex: "full"
interfaces:
- Int_One
- Int_Two
tasks:
- name: DBEUG
debug:
msg: >
mode: {{parameters.0.mode}},
speed: {{parameters.0.speed}},
duplex: {{parameters.0.duplex}},
interface: {{item}}
loop:
"{{interfaces}}"

Related

Ansible: assign and loop through list dynamically

I'm new at Ansible and trying to automate a Fortigate configuration using the fortinet.fortios modules.
I'm having a problem with fortios_firewall_addrgrp specifically that does not support the append of a firewall address to a group.
I have this set in my variables:
addresses:
host_0:
subnet: 192.168.1.10/24
group: group_0
host_1:
subnet: 192.168.1.11/24
group: group_0
host_2:
subnet: 192.168.10.10/24
group: group_1
host_3:
subnet: 192.168.10.11/24
group: group_1
And I'm running this task to generate an address group.
- name: Configure IPv4 address groups.
fortios_firewall_addrgrp:
vdom: '{{ vdom }}'
state: present
firewall_addrgrp:
name: '{{ item.value.group }}'
member:
- name: '{{ item.key }}'
loop: '{{ addresses | dict2items }}'
It loops through each host and generates an address group, but it results in having 2 groups containing only the last hostname in the list.
Ideally, the module should support the append of a hostname to an existing group, but it doesn't, so I'm trying to work around it to make the following happening:
member:
- name: host_0
- name: host_1
The example above would work, but I cannot know in advance groups and hostnames in the variable.
I could generate or filter the input variable into a dictionary of host and groups and give it to members. Still, I cannot understand how to loop through it dynamically.
The problem you are having is related to the data structure you are using for the loop. As you mentioned, the fortios_firewall_addrgrp module expects a list of dictionaries for the members key, representing each host.
So, you need to create a new data structure that fits the input of the fortios_firewall_addrgrp module. Here is an example of how to do it:
---
- hosts: localhost
gather_facts: 'no'
vars:
addresses:
host_0:
subnet: 192.168.1.10/24
group: group_0
host_1:
subnet: 192.168.1.11/24
group: group_0
host_2:
subnet: 192.168.10.10/24
group: group_1
host_3:
subnet: 192.168.10.11/24
group: group_1
tasks:
- set_fact:
addresses_by_group: |
{{
addresses_by_group | default({}) | combine({
item.value.group: (addresses_by_group[item.value.group] | default([])) + [{"name": item.key}]
})
}}
loop: '{{ addresses | dict2items }}'
- name: Configure IPv4 address groups.
fortios_firewall_addrgrp:
vdom: '{{ vdom }}'
state: present
firewall_addrgrp:
name: '{{ item.key }}'
member: '{{ item.value }}'
loop: '{{ addresses_by_group | dict2items }}'
We create a new variable called addresses_by_group that will store a list of hosts for each group. The combine filter allows you to combine two different dictionaries, while the default filter sets a default value for an undefined variable. We use the + operator to concatenate two lists. If you debug the value of the variable addresses_by_group you'll see this:
TASK [debug] *******************************************************************
ok: [localhost] =>
addresses_by_group:
group_0:
- name: host_0
- name: host_1
group_1:
- name: host_2
- name: host_3
Which is just what we need. Bear in mind that we didn't touch the addresses variables so that you can use them later on.

Ansible: need to combine information from 2 files for a task later

I'm running Ansible 2.7.9 on Red Hat Linux 7.4. In my playbook, I need to combine information in two yml files into one data structure that I can then iterate over in a task.
For example, in one .yml file I might have:
---
network_interfaces:
big_computer:
- name: eth0.10
- name: eth1.20
- name: ens224
- name: bond1
type: bond
device: intel
In another .yml file called definitions.yml I might have:
---
eth0.10:
type: vlan
slave: true
master: bond1
subnet: 192.168.10.0/24
eth1.20:
type: vlan
slave: true
master: bond1
subnet: 192.168.20.0/24
ens224:
type: ethernet
subnet: 172.19.22.0/23
bond1:
type: bond
I am able to set, for example, a variable network_interfaces_list from network_interfaces[computer_type]
When computer_type is big_computer, then, I want to take the names of network_interfaces_list, the data from network_interfaces_listand the data from definitions.yml, and create this combined data structure. Then use that data structure in a task. Note that some information may be included in the network_interfaces file, but some of it is found in the definitions file. Or it may remain undefined- there is no "slave" or "master" setting for the bond1 interface, for example.
network_interfaces_list should end up looking like this:
- eth0.10:
type: vlan
slave: true
master: bond1
subnet: 192.168.10.0/24
- eth1.20:
type: vlan
slave: true
master: bond1
subnet: 192.168.20.0/24
- ens224:
type: ethernet
subnet: 172.19.22.0/23
- bond1:
type: bond
device: intel
Entries missing in a dictionary for any given list item are considered unimportant for that item and are essentially null.
I've been wracking my brains all day on this one. I can debug either entry, but trying to put them together- especially without Ansible giving me errors about missing dictionary keys- I'm finding impossible to do.
Essentially what I want is a short file of information, and for those items that are considered unimportant at first glance, I want to put them in another layer that we can peruse to gather more detail, if we wish.
I have done this successfully:
- debug:
msg: "interface_list: {{ network_interfaces_list }}"
- name: MIKE debug
debug:
msg: "thingy: {{ interface['name'] }} \
{{ interface['type']|default('NULL') }} \
{{ interface['device']|default('NULL') }}"
loop: "{{ network_interfaces_list }}"
loop_control:
loop_var: interface
- name: MIKE2 debug
debug:
msg: "thing2: {{ lookup ( 'vars', interface['name'], default='XXX' ) }}"
loop: "{{ network_interfaces_list }}"
loop_control:
loop_var: interface
~~~
But I'm stuck in trying to get merge the members of `interface['name']` from the two data structures.
For example
- set_fact:
network_interfaces_list: "{{ network_interfaces_list|default([]) +
[{item: _val}] }}"
loop: "{{ _keys }}"
vars:
computer_type: big_computer
_keys: "{{ network_interfaces[computer_type]|map(attribute='name')|list }}"
_vals: "{{ network_interfaces[computer_type] }}"
_dict: "{{ dict(_keys|zip(_vals)) }}"
_val: "{{ _dict[item]|combine(definitions[item]) }}"
gives
network_interfaces_list:
- eth0.10:
master: bond1
name: eth0.10
slave: true
subnet: 192.168.10.0/24
type: vlan
- eth1.20:
master: bond1
name: eth1.20
slave: true
subnet: 192.168.20.0/24
type: vlan
- ens224:
name: ens224
subnet: 172.19.22.0/23
type: ethernet
- bond1:
device: intel
name: bond1
type: bond
Notes
The dictionaries keep the redundant attribute name
The names of the variables in definitions.yml are not valid. See Creating valid variable names. Include them in a dictionary, e.g.
- include_vars:
file: definitions.yml
name: definitions
gives
definitions:
bond1:
type: bond
ens224:
subnet: 172.19.22.0/23
type: ethernet
eth0.10:
master: bond1
slave: true
subnet: 192.168.10.0/24
type: vlan
eth1.20:
master: bond1
slave: true
subnet: 192.168.20.0/24
type: vlan
In some cases a dictionary might be a better structure, e.g.
- set_fact:
network_interfaces_dict: "{{ network_interfaces_dict|default({})|
combine({item: _val}) }}"
...
would give
network_interfaces_dict:
bond1:
device: intel
name: bond1
type: bond
ens224:
name: ens224
subnet: 172.19.22.0/23
type: ethernet
eth0.10:
master: bond1
name: eth0.10
slave: true
subnet: 192.168.10.0/24
type: vlan
eth1.20:
master: bond1
name: eth1.20
slave: true
subnet: 192.168.20.0/24
type: vlan

Deploying multiple VM's with different number of disk each VM's need's

I have playbook for deploying multiple VM's with multiple disk it's working fine..
But when I deploying multiple VM's with different number of disk each VM's need's in this situation I tried condition's in playbook but it's not working as I expected..
for example below is requirements
APP : 2 disk
DB: 3 disk
I'm passing through disk variables from my inventory files..
look like below
APP guest_disk1='100' guest_disk2='200'
DB guest_disk1='100' guest_disk2='200' guest_disk3='300'
I modified my playbook with when condition like these
vmware_guest:
....
name: '{{ inventory_hostname }}'
disk:
- size_gb: '{{ guest_disk1 }}'
type: thin
datastore: '{{ datastore1 }}'
when: '"APP or DB" in inventory_hostname'
- size_gb: '{{ guest_disk2 }}'
type: thin
datastore: '{{ datastore1 }}'
when: '"APP or DB" in inventory_hostname
- size_gb: '{{ guest_disk3 }}'
type: thin
datastore: '{{ datastore1 }}'
when: '"DB" in inventory_hostname'
When I ran the playbook got below error msg
ERROR! We were unable to read either as JSON nor YAML, these are the errors we got from each:
JSON: No JSON object could be decoded
Syntax Error while loading YAML.
did not find expected key
The error appears to be in '/opt/myagent/avi/ansible/ansible-deploy-vmware-vm2/roles/vm/tasks/main.yml': line 44, column 13, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
when: '"APP or DB" in inventory_hostname'
- size_gb: '{{ guest_disk2 }}'
^ here
We could be wrong, but this one looks like it might be an issue with
missing quotes. Always quote template expression brackets when they
start a value. For instance:
with_items:
- {{ foo }}
Should be written as:
with_items:
- "{{ foo }}"
I tired this condition in disk task itself but it's still same error
vmware_guest:
....
name: '{{ inventory_hostname }}'
disk:
- size_gb: '{{ guest_disk1 }}'
type: thin
datastore: '{{ datastore1 }}'
when: '"APP or DB" in inventory_hostname'
- size_gb: '{{ guest_disk2 }}'
type: thin
datastore: '{{ datastore1 }}'
when: '"APP or DB" in inventory_hostname'
- size_gb: '{{ guest_disk3 }}'
type: thin
datastore: '{{ datastore1 }}'
when: '"DB" in inventory_hostname'
When I comment out the when condition in playbook it's deploying DB only. not APP it's failed with undefined variables for guest_disk3 because of in my inventory I didn't defined any variables for guest_disk3 for APP.. (I need 2 disk only for APP )
Thanks for advance
Your example is not a valid yaml/task/playbook sample. Moreover, your data structure is far from ideal to deal with this. Don't create independent vars, use lists. If you want to DRY and limit verbosity, you can apply override values to all disks if you always have the same options
All-in-one example inventory:
all:
vars:
datastore1: someds
disk_override_values:
type: thin
datastore: "{{ datastore1 }}"
guest_disks: "{{ disk_sizes | map('combine', disk_override_values) | list }}"
hosts:
app:
disks_sizes:
- size: 100
- size: 200
db:
disks_sizes:
- size: 100
- size: 200
- size: 300
Example task (didn't test the vmware tasks, I don't have an infra for that)
- name: create guest
vmware_guest:
name: '{{ inventory_hostname }}'
disk: "{{ guest_disk }}"
delegate_to: localhost

Ansible-VMware How to loop over a task and supply a value to a key?

Many of the VMware modules for Ansible are structured a bit differently than a normal Ansible module. What I'm running into is needing to supply either a hostname or cluster name to the module. This doesn't scale well and I'm looking for a way to loop over a set of hosts, or even clusters from a vars file (the VMware modules don't use the normal /etc/hosts file) and supply that host or cluster name to the module. In the code below, I would be supplying a hostname to "esxi_hostname".
As you can see by the commented code, I have tried the with_items option, which doesn't work because it's not available to the module. I have tried jinja like so: 'esxi_hostname: '{% for host in hosts %} {{ host }} {% endfor %} as well as "loop: '{{ hosts }}'
---
- hosts: localhost
vars_files:
- credentials.yml
- vars.yml
- se1_hosts.yml
tasks:
- name: Manage Log level setting for an ESXi host
vmware_host_config_manager:
hostname: 'vcsa.host.se1.somewhere.com'
username: '{{ vc_username }}'
password: '{{ vc_pass }}'
esxi_hostname: 'hostname'
# with_items:
# - 'c05n06.esx.se1.csnzoo.com'
# loop: '{{ hosts }}'
validate_certs: False
options:
'Config.HostAgent.log.level': 'info'
delegate_to: localhost
I would expect I can supply a var to esxi_hostname to be utlized, and am looking for a way to do that with a loop, so it runs against host1, host2, host3, etc..
Thanks in advance!
loops can be applied to modules (in this case module vmware_host_config_manager)
so loop keyword shall be at same indent level :
- name: Manage Log level setting for an ESXi host
vmware_host_config_manager:
hostname: '{{ vcenter_hostname }}'
username: '{{ vcenter_username }}'
password: '{{ vcenter_password }}'
esxi_hostname: '{{ item }}'
options:
'Config.HostAgent.log.level': 'info'
loop: "{{ groups['esxi'] }}"
delegate_to: localhost

Ansible how to select a nested list in a attribut of list

Hi I would like to known how I can select a list nested in an attibut of a list. I've done a research on my F5 BIG IP and it give me a list of the attibutes of the virtuals servers like that:
"/Common/vs_portailopal_wi_https_virtual_server": {
"last_hop_pool": "",
"name": "vs_portailopal_wi_https_virtual_server",
"nat64_state": "STATE_DISABLED",
"object_status": {
"availability_status": "AVAILABILITY_STATUS_RED",
"enabled_status": "ENABLED_STATUS_DISABLED",
"status_description": "The children pool member(s) are down"
},
"profile": [
{
"profile_context": "PROFILE_CONTEXT_TYPE_CLIENT",
"profile_name": "/Common/vs_portailopal_wi_clientssl_profile",
"profile_type": "PROFILE_TYPE_CLIENT_SSL"
},
{
"profile_context": "PROFILE_CONTEXT_TYPE_ALL",
"profile_name": "/Common/vs_portailopal_wi_http_profile",
"profile_type": "PROFILE_TYPE_HTTP"
},
{
"profile_context": "PROFILE_CONTEXT_TYPE_ALL",
"profile_name": "/Common/vs_portailopal_wi_http_profile-cache",
"profile_type": "PROFILE_TYPE_WEBACCELERATION"
},]
},
},
So I would like to compare the name of the virtual serveur and the name of each profile. I can select the name of the vitual server but I can't enter in the list of profile to select the name because it's a nested list in an attribute
This is I doing:
---
- name: Search
hosts: F5
gather_facts: no
connection: local
vars:
username: '{{ cpt_username }}'
password: '{{ cpt_password }}'
tasks:
- name: Get virtual-servers
bigip_facts:
include:
- virtual_server
server: '{{ inventory_hostname }}'
user: '{{ username }}'
password: '{{ password }}'
validate_certs: no
- name: filter on VIP_nommage when VIP_partition is OK
lineinfile:
line:
- "{{ inventory_hostname }} Virtual Server : {{ item.key }} => POOL: {{ item.value.profile.name }}"
dest: "xxxxx/file.csv"
state: present
with_dict: "{{ virtual_server }}"
I want to store in file all profiles name per virtual serveur and in other task filter on their names.
I am not familiar with the bigip Ansible modules, and I definitely don't have any F5 kit to test against :) Before saying anything else, a big note is that the docs say that 'bigip_facts' is now deprecated, and you should be using 'bigip_device_facts'. But if you want to use this module, can you do a couple of things:
1) Edit your original question to add the detail you posted in your reply. It is better to keep editing your original question with additional information, rather than add answers, as it makes it easier to understand your entire situation.
2) Create a new playbook that contains:
---
- hosts: F5
gather_facts: no
connection: local
vars:
username: '{{ cpt_username }}'
password: '{{ cpt_password }}'
tasks:
- name: Get virtual-servers
bigip_facts:
include:
- virtual_server
server: '{{ inventory_hostname }}'
user: '{{ username }}'
password: '{{ password }}'
validate_certs: no
register: bigip_facts_data
- name: Display collected data
debug:
var: bigip_facts_data
This will show us the data that Ansible is actually working with. Paste the output into your original reply, in place the of the JSON that you originally pasted. It may look like this:
"/Common/vs_portailopal_wi_https_virtual_server":
last_hop_pool: ''
name: vs_portailopal_wi_https_virtual_server
nat64_state: STATE_DISABLED
object_status:
availability_status: AVAILABILITY_STATUS_RED
enabled_status: ENABLED_STATUS_DISABLED
status_description: The children pool member(s) are down
profile:
- profile_context: PROFILE_CONTEXT_TYPE_CLIENT
profile_name: "/Common/vs_portailopal_wi_clientssl_profile"
profile_type: PROFILE_TYPE_CLIENT_SSL
- profile_context: PROFILE_CONTEXT_TYPE_ALL
profile_name: "/Common/vs_portailopal_wi_http_profile"
profile_type: PROFILE_TYPE_HTTP
- profile_context: PROFILE_CONTEXT_TYPE_ALL
profile_name: "/Common/vs_portailopal_wi_http_profile-cache"
profile_type: PROFILE_TYPE_WEBACCELERATION
If it does, then this may produce the output you need (tho I am still not clear I fully understand what you require - hopefully we are closer):
- name: filter on VIP_nommage when VIP_partition is OK
lineinfile:
line:
- "{{ inventory_hostname }} Virtual Server : {{ item.0.name }} => POOL: {{ item.1.profile_name }}"
dest: "xxxxx/file.csv"
state: present
with_subelements:
- "{{ bigip_fact_data }}"
- profile
Really, 'with_subelements' is deprecated now along with the other 'with_' constructs, but if you are using 'bigip_facts' I guess you are using an older version of Ansible.

Resources