Ansible: assign and loop through list dynamically - ansible

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.

Related

Ansible: Delete interfaces that are not in my host_vars

I've an ansible playbook that creates l3_subinterfaces on a Palo Alto firewall, the creating is based on the host_vars of the firewall.
- l3_subinterfaces:
- tag: "9"
vr_name: "vr_production"
ip: "10.0.9.2/24"
comment: "VLAN9 Subinterface"
parent_if: "ethernet1/1"
zone: "Infrastructuur"
- tag: "13"
vr_name: "vr_production"
ip: "10.0.13.2/24"
comment: "VLAN13 Subinterface"
parent_if: "ethernet1/2"
zone: "Infrastructuur"
And the playbook task which create the interfaces:
- name: Configure l3_subinterfaces
panos_l3_subinterface:
provider: "{{ panos_provider }}"
name: "{{ item.parent_if }}.{{ item.tag }}"
tag: "{{ item.tag }}"
ip: ["{{ item.ip }}"]
vr_name: "{{ item.vr_name }}"
zone_name: "{{ item.zone }}"
comment: "{{ item.comment }}"
enable_dhcp: false
with_items:
- "{{ l3_subinterfaces }}"
when: l3_subinterfaces is defined
So at this point everything is working fine. However the thing I'm trying to achieve is holding the state of the firewall in the Ansible inventory.
So for example I'm now delete the l3_subinterface with tag 13 and run the task again, it still have the l3_subinterface with tag 13 configured on the Palo Alto firewall.
I'm trying to figure out how I can delete the l3_subinterfaces which exists on the firewall, but doesn't exists in my host_vars. I think I need to compare something like te facts with the host_vars, but really have no clue how to do it.
Actually I've already found my own answer. The solution is to compare the list l3_subinterfaces against the palo alto interfaces:
- name: Get interfaces facts
panos_facts:
provider: '{{ panos_provider }}'
gather_subset: ['interfaces']
- name: Delete unused l3_subinterfaces
panos_l3_subinterface:
provider: "{{ panos_provider }}"
name: "{{ item }}"
tag: "{{ item|regex_search('\\d+$') }}"
state: "absent"
with_items:
- "{{ ansible_net_interfaces|selectattr('tag', 'defined')|map(attribute='name')|list | difference(l3_subinterfaces|map(attribute='name')|list) }}"

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

Omit os_subnet's variable dns_nameservers in Ansible

I would like to omit the dns_nameservers variable from the following Openstack function if the value does not appear in the variable file:
os_subnet:
cloud: "{{ item.cloud }}"
state: present
validate_certs: no
no_gateway_ip: yes
dns_nameservers:
- "{{ item.dns | default(None) }}"
enable_dhcp: yes
name: "{{ item.subnet }}"
network_name: "{{ item.network }}"
cidr: "{{ item.cidr }}"
allocation_pool_start: "{{ item.allocation_pool_start }}"
allocation_pool_end: "{{ item.allocation_pool_end }}"
host_routes: "{{ item.host_routes | default(omit) }}"
with_items:
- "{{ subnets }}"
tags: subnets
Until now, I have tried to omit it with | default(omit) and | default(None), but it is not working. Is any filter that might help or any other way?
EDIT:
Variable file:
- cloud: tenant1
network: nw
subnet: nw_subnet
cidr: 172.12.17.64/26
dns:
- 8.8.8.8
- 8.8.8.9
allocation_pool_start: 172.12.17.68
allocation_pool_end: 172.12.17.70
host_routes:
- destination: 0.0.0.0/0
nexthop: 172.12.17.65
I am getting the following error:
Reason: '[u'8.8.8.8', u'8.8.8.9']' is not a valid
nameserver. '[u'8.8.8.8', u'8.8.8.9']' is not a valid
IP address.\", \"type\": \"HTTPBadRequest\", \"detail\": \"\"}}"}
You want to either pass a list with a single element or pass an omit keyword (placeholder object), which tells Ansible not to pass the whole parameter (dns_nameservers here) to the module:
dns_nameservers: "{{ [item.dns] if item.dns is defined else omit }}"
In your example, if item.dns was undefined, you passed a list with a single element being an omit placeholder. In such case the dns_nameservers parameter is defined (that list which is hardcoded in the code) and behaviour is undefined (likely depends on module).

Ansible Pass multiple vaules with single defined Variable

I need to add a server to service group every time I create a new server using the following task.
Task
- name: Create a service group
a10_service_group_v3:
validate_certs: no
host: "{{ item.0.a10_host }}"
state: "{{ item.1.service_state }}"
username: "{{ item.0.user }}"
password: "{{ item.0.pass }}"
service_group: "{{ item.1.group_name }}"
reset_on_server_selection_fail: yes
servers:
- name: "{{ item.1.server_name1 }}"
port: "{{ item.1.server_port1 }}"
overwrite: yes
write_config: yes
ignore_errors: yes
with_nested:
- "{{ a10 }}"
- "{{ service_group }}"
Variables:
service_group:
- group_name: bif_sg
service_state: present
server_name1: bif01
server_port1: 80
I need help with passing variables for server_name and server_port, let's say If I have 3 servers to add to service group in the task I need to add 3 times server_name1, server_port1
server_name2, server_port2 ......
Everytime I add server I need to update in the task as well :(
Is there a way to pass multiple times sever_name and serer_port with single defined value in the task.
I you expect server_group to have a list of servers, refactor your variable to have a list of servers and not a bunch of separate subkeys:
service_group:
- group_name: bif_sg
service_state: present
servers:
- name: bif01
port: 80
- name: bif02
port: 8080
And in your task:
...
servers: "{{ item.1.servers }}"
...

ansible: create temporary inventory with multiple groups with add_host or group_by

Is there any way to create an in memory inventory during provisioning tasks with add_host or group_by modules such:
[SET]
1.1.1.1
[SET:vars]
ip_address={{ inventory_hostname }}
[SET1]
1.1.1.2
[SET:vars]
ip_address={{ inventory_hostname }}
Yes. You can do something like this (if you provide more information in your question, we can provide more specificity ourselves):
- add_host:
hostname: 1.1.1.1
groups: SET
- add_host:
hostname: 1.1.1.2
groups: SET1
This will dynamically add 1.1.1.1 to the inventory as part of the SET group and 1.1.1.2 to the inventory as part of the SET1 group. there are a couple of good example of doing this during provision steps for rackspace
tasks:
- name: Provision a set of instances
local_action:
module: rax
name: "{{ rax_name }}"
flavor: "{{ rax_flavor }}"
image: "{{ rax_image }}"
count: "{{ rax_count }}"
group: "{{ group }}"
wait: yes
register: rax
- name: Add the instances we created (by public IP) to the group 'raxhosts'
local_action:
module: add_host
hostname: "{{ item.name }}"
ansible_host: "{{ item.rax_accessipv4 }}"
ansible_ssh_pass: "{{ item.rax_adminpass }}"
groups: raxhosts
with_items: "{{ rax.success }}"
when: rax.action == 'create'

Resources