Loop with subelements filter - ansible

Variable:
customers:
- name: CompanyX
destination_addresses:
- 192.168.0.0/24
- 192.168.1.0/24
- name: CompanyY
destination_addresses:
- 192.168.2.0/24
- 192.168.3.0/24
I'm trying to create address objects for each address in destination_addresses, and create an address group object that stores all addresses per customer.
The creation of each address works as expected like this:
- name: add address object for destination networks
fortinet.fortios.fortios_firewall_address:
state: present
firewall_address:
name: "{{ item.0.name }}-{{ item.1 }}"
subnet: "{{ item.1 }}"
loop: "{{ customers | subelements('destination_addresses') }}"
This creates:
CompanyX-192.168.0.0/24
CompanyX-192.168.1.0/24
CompanyY-192.168.2.0/24
CompanyY-192.168.3.0/24
But I'm struggling how to group the address objects.
This is what I use now:
- set_fact:
grp_members: "{{ grp_members | default([]) + [{ 'name': item.0.name ~ '-' ~ item.1 }] }}"
loop: "{{ customers | subelements('destination_addresses') }}"
loop_control:
extended: yes
- name: create address group
fortinet.fortios.fortios_firewall_addrgrp:
state: present
firewall_addrgrp:
name: "{{ item.name }}"
member: "{{ grp_members }}"
loop: "{{ customers }}"
Which creates the group CompanyX and CompanyY but with all addresses in each group, because the grp_members variable contains all addresses.
How can I limit the group members to only contain the addresses for CompanyX & CompanyY separately?
Current output:
- debug:
var: grp_members
"grp_members": [
{
"name": "CompanyX-192.168.0.0/24"
},
{
"name": "CompanyX-192.168.1.0/24"
},
{
"name": "CompanyY-192.168.2.0/24"
},
{
"name": "CompanyY-192.168.3.0/24"
}
]
Desired result for each customer:
"grp_members": [
{
"name": "CompanyX-192.168.0.0/24"
},
{
"name": "CompanyX-192.168.1.0/24"
}
]
"grp_members": [
{
"name": "CompanyY-192.168.2.0/24"
},
{
"name": "CompanyY-192.168.3.0/24"
}
]
The fortinet.fortios.fortios_firewall_addrgrp module expects a dictionary in the above syntax.

With the changed conditions you need the following:
- name: create address group
fortinet.fortios.fortios_firewall_addrgrp:
state: present
firewall_addrgrp:
name: "{{ item.name }}"
member: "{{ grp_members }}"
vars:
grp_members: "{{ [item.name] | product(item.destination_addresses) | map('join', '-') | map('community.general.dict_kv', 'name') }}"
loop: "{{ customers }}"
You continue iterating over customers. The variable grp_members is generated locally for each iteration.
via product a cross product of the customer with each IP is created
via join the two elements customer name and IP are connected.
dict_kv creates a dict from the list with the key name.
You don't need your task with set_fact anymore.
Here you can see the sample output of the joined addresses.
This task
- name: generate kv dict with customer-address
debug:
msg: "{{ grp_members }}"
vars:
grp_members: "{{ [item.name] | product(item.destination_addresses) | map('join', '-') | map('community.general.dict_kv', 'name') }}"
loop: "{{ customers }}"
produces this output
TASK [generate kv dict with customer-address] *******************************************************************************
ok: [localhost] => (item={'name': 'CompanyX', 'destination_addresses': ['192.168.0.0/24', '192.168.1.0/24']}) => {
"msg": [
{
"name": "CompanyX-192.168.0.0/24"
},
{
"name": "CompanyX-192.168.1.0/24"
}
]
}
ok: [localhost] => (item={'name': 'CompanyY', 'destination_addresses': ['192.168.2.0/24', '192.168.3.0/24']}) => {
"msg": [
{
"name": "CompanyY-192.168.2.0/24"
},
{
"name": "CompanyY-192.168.3.0/24"
}
]
}
old post
I think that's what you're looking for.
- name: create address group
fortinet.fortios.fortios_firewall_addrgrp:
state: present
firewall_addrgrp:
name: "{{ item.name }}"
member: "{{ item.destination_addresses | join(',') }}"
loop: "{{ customers }}"
I guess that member should be all addresses of the respective customer? These are in a list for the respective customer and you can join them to a string via join function.
So the task set_fact for grp_members would not be necessary.
If this is not the result you need, you have to describe it exactly.
Here you can see the sample output of the joined addresses.
This task
- name: Iterate over customers.
debug:
msg: "{{ item.name }}: {{ item.destination_addresses | join(',') }}"
loop: "{{ customers }}"
produces this output
TASK [Iterate over customers.] ***************************************************************************************
ok: [localhost] => (item={'name': 'CompanyX', 'destination_addresses': ['192.168.0.0/24', '192.168.1.0/24']}) => {
"msg": "CompanyX: 192.168.0.0/24,192.168.1.0/24"
}
ok: [localhost] => (item={'name': 'CompanyY', 'destination_addresses': ['192.168.2.0/24', '192.168.3.0/24']}) => {
"msg": "CompanyY: 192.168.2.0/24,192.168.3.0/24"
}

Related

Register return values from tasks_from when looping over include_role

I'd like to build an array of ticket numbers that are created by the create_srq.yaml task in role servicenow. Is it possible to do this when looping over an include_role?
roles/request_signed_certificate/tasks/main.yaml
- name: Create SNOW Records for Certificate Request
include_role:
name: servicenow
tasks_from: create_srq.yaml
register: result
loop: "{{ spreadsheet }}"
loop_control:
loop_var: csr
vars:
short_description: CSR for {{ csr.certname }}-{{ csr.env }}
attachment: "{{ cert_path }}/{{ csr.certname }}-{{ csr.env }}.csr"
- name: debug
debug:
var: result
roles/servicenow/tasks/create_srq.yaml
- name: Create a SRQ
snow_record:
state: present
table: u_request
username: "{{ snow_username }}"
password: "{{ snow_password }}"
instance: "{{ servicenow_instance }}"
data:
short_description: "{{ short_description }}"
attachment: "{{ attachment }}"
register: srq
- name: Attach file to {{ srq.record.number }}
snow_record:
state: present
table: u_request
username: "{{ snow_username }}"
password: "{{ snow_password }}"
instance: "{{ servicenow_instance }}"
number: "{{ srq.record.number }}"
attachment: "{{ attachment }}"
When running the playbook:
---
- hosts: "{{ hosts_list }}"
connection: local
gather_facts: false
vars:
cert_path: "/tmp/certs"
cert_version: "2023"
pre_tasks:
- name: Create facts from csv
csv_to_facts:
src: "file.csv"
delegate_to: localhost
run_once: true
roles:
- role: request_signed_certificate
The result does not include the registered srq variable from create_srq.yaml:
TASK [request_signed_certificate : debug] **************************************************************
ok: [host.example.com] => {
"result": {
"changed": false,
"msg": "All items completed",
"results": [
{
"ansible_loop_var": "csr",
"csr": {
"certname": "example_one",
"common_name": "domain_one.example.com",
"dns1": "domain_two.example.com",
"env": "development",
},
"include_args": {
"name": "servicenow",
"tasks_from": "create_srq.yaml"
}
},
{
"ansible_loop_var": "csr",
"csr": {
"certname": "example_two",
"common_name": "domain_123.example.com",
"dns1": "domain_456.example.com",
"env": "test",
},
"include_args": {
"name": "servicenow",
"tasks_from": "create_srq.yaml"
}
}
]
}
}
I was able to do this by appending results to a list. While this works, it wasn't exactly what I was after as I would have preferred to install a reusable role to create the srqs.
Adding this bit to the bottom of create_srq.yaml gave me what I was looking for:
- name: output csv entry + ticket number
set_fact:
csv: "{{ csv | default({}) | combine ( { item.key : item.value } ) | combine( { 'ticket': srq.record.number } ) }}"
loop: "{{ csr | dict2items }}"
# Create list of dictionaries to track
# also, a list is required for the to_csv plugin
- name: create list of dicts
set_fact:
contents: "{{ contents | default([]) + [ csv ] }}"

ansible create a new dict and add to list

my playbook always show me just last value , looks like the script is overwrite.
From json file I need extract some value, create dictionary and put it to the list.
My json file .
{
"rade": [
{
"apiRawValues": {
"verificationStatus": "signature-verified"
},
"deviceReference": {
"name": "bigip02"
},
"port": "Ir_HTTP_HTTPs"
},
{
"apiRawValues": {
"verificationStatus": "signature-verified"
},
"deviceReference": {
"name": "bigip01"
},
"port": "Ir_HTTP_HTTPs"
}
]
}
and my playbook look like
---
- hosts: localhost
connection: local
gather_facts: false
vars:
cert1: {}
vars_files:
tasks:
- name : deploy json file AS3 to F5
set_fact:
json_file: "{{ lookup('file', 'parse2.json') }}"
- name: create dic and create list
set_fact:
cert1: "{{ cert1 | d({}) | combine({ 'device': item['deviceReference']['name']}, { 'port': item.port}, recursive=True) }}"
loop: "{{ json_file['rade'] }}"
- name: debug4
debug:
msg: "{{ cert1 }}"
the result is
ok: [localhost] => {
"msg": {
"device": "bigip01",
"port": "Ir_HTTP_HTTPs"
}
}
why it just show me last value ?
I need list of device and port.
thank you for help
The filter json_query makes it simpler e.g.
- debug:
msg: "{{ json_file.rade|
json_query('[].{device: deviceReference.name, port: port}') }}"
gives
msg:
- device: bigip02
port: Ir_HTTP_HTTPs
- device: bigip01
port: Ir_HTTP_HTTPs
If the names of the devices are unique you can create a dictionary, e.g.
- debug:
msg: "{{ dict(_keys|zip(_vals)) }}"
vars:
_keys: "{{ json_file.rade|map(attribute='deviceReference.name')|list }}"
_vals: "{{ json_file.rade|map(attribute='port')|list }}"
gives
msg:
bigip01: Ir_HTTP_HTTPs
bigip02: Ir_HTTP_HTTPs
I have found out solution
- name: create dic and create list
set_fact:
cert1: "{{ cert1 | default([]) + [{ 'device' : item['deviceReference']['name'], 'irule' : item.port }] }}"
loop: "{{ json_file['rade'] }}"
- name: debug4
debug:
msg: "{{ cert1 }}"

How parse a list of dictionary and out the values in Ansible?

I've the following json output. It is a list of dictionary. I just want to parse it and output its value.
I've tried the following play but without success...
"output1": {
"changed": false,
"msg": "All items completed",
"results": [
{
"item": [
{
"Device_Name": "SW1"
},
{
"Interface": "GigabitEthernet1/0/7"
}
]
}
]
}
- name: Display output1...2
debug:
msg: "{{ item.1.Device_Name }};{{ item.1.Interface }}"
with_subelements:
- "{{ output1.results }}"
- item
The error was ""msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'int_name'\n\n"
Try below - I have not tested it though.
- name: Display output1...2
debug:
msg: "{{ item|first }} : {{ item[item|first] }}"
with_items:
- "{{ output1.results[0].item }}"
Quick and dirty
The tasks below give quickly what is requested
- set_fact:
Device_Name: "{{ output1.results[0]['item']|
map(attribute='Device_Name')|
select('defined')|
list|first }}"
Interface: "{{ output1.results[0]['item']|
map(attribute='Interface')|
select('defined')|
list|first }}"
Details
This is a typical example of bad data structure. The task below
- debug:
msg: "{{ output1.results|
map('dict2items')|list|flatten|
json_query('[?key==`item`].value')|flatten }}"
gives item, which is a list and must be iterated again,
"msg": [
{
"Device_Name": "SW1"
},
{
"Interface": "GigabitEthernet1/0/7"
}
]
instead of a dictionary, which could be easily referenced.
"msg": [
{
"Device_Name": "SW1",
"Interface": "GigabitEthernet1/0/7"
}
]
As a result of such filtering the tasks below
- set_fact:
Device_Name: "{{ output1.results|
map('dict2items')|list|flatten|
json_query('[?key==`item`].value')|flatten|
map('dict2items')|list|
json_query('[*][?key==`Device_Name`].value')|flatten }}"
- set_fact:
Interface: "{{ output1.results|
map('dict2items')|list|flatten|
json_query('[?key==`item`].value')|flatten|
map('dict2items')|list|
json_query('[*][?key==`Interface`].value')|flatten }}"
- debug:
var: Device_Name
- debug:
var: Interface
give
"Device_Name": [
"SW1"
]
"Interface": [
"GigabitEthernet1/0/7"
]

Is it possible to set an Ansible fact only if a variable has a value?

I have an Ansible playbook that I'm running from AWX. The playing uses the Infoblox nios module to retrieve information about Infoblox host registrations.
I'm using the set_fact module to to take the output of the query and then define a number of new facts to use elsewhere in the playbook.
The problem that I have is that the query can return a differing number of variables depending on the format of the registration and this breaks the playbook.
What I am trying to do is workout if I can set a new fact only if the specific variable is returned in the original query.
I've tried using "if defined" but that doesn't seem to work.
In example 1 the play "fetch host record" returns the following values. host, ipv4addr and mac as the host has a Mac Address in Infoblox
ok: [localhost] => {
"ansible_facts": {
"host": {
"ipv4addrs": [
{
"host": "myhost1.test.com",
"ipv4addr": "192.168.30.1",
"mac": "00:22:33:11:44:55"
}
],
"name": "myhost1.test.com",
"view": "Internal"
}
},
"changed": false
}
In example 2 the same play only returns host and ipv4addr as the host does not have a Mac Address registered.
ok: [localhost] => {
"ansible_facts": {
"host": {
"ipv4addrs": [
{
"host": "myhost2.test.com",
"ipv4addr": "192.168.30.2"
}
],
"name": "myhost2.test.com",
"view": "Internal"
}
},
"changed": false
}
My playbook contains the following and works only if the host contains a Mac Address as the fact host, doesn't contain a value for host.ipv4addrs[0].mac so it crashes out. I'd like to add some logic to only try and set niosmac if host.ipv4addrs[0].mac is defined.
tasks:
- name: fetch host record
set_fact:
host: "{{ lookup('nios', 'record:host', filter={niossearchcatagory: searchcriteria, 'view': 'Internal'}, provider=nios_provider) }}"
- name: Set niosip
set_fact:
niosip: "{{ host.ipv4addrs[0].ipv4addr }}"
nioshostname: "{{ host.name }}"
niosdhcp: "{{ host.ipv4addrs[0].configure_for_dhcp }}"
niosmac: "{{ host.ipv4addrs[0].mac }}"
Here's the version I attempted using is defined
tasks:
- name: fetch host record
set_fact:
host: "{{ lookup('nios', 'record:host', filter={niossearchcatagory: searchcriteria, 'view': 'Internal'}, provider=nios_provider) }}"
- name: Set niosip
set_fact:
niosip: "{{ host.ipv4addrs[0].ipv4addr }}"
nioshostname: "{{ host.name }}"
niosdhcp: "{{ host.ipv4addrs[0].configure_for_dhcp }}"
niosmac: "{{ host.ipv4addrs[0].mac }}"
when: host.ipv4addrs[0].mac is defined
Cheers
Spence
Sorry, I must have typed something wrong before as I've tried again and it now seems to work. Here's the correct code for clarification.
- name: Set niosip
set_fact:
niosip: "{{ host.ipv4addrs[0].ipv4addr }}"
nioshostname: "{{ host.name }}"
niosdhcp: "{{ host.ipv4addrs[0].configure_for_dhcp }}"
niosmac: "{{ host.ipv4addrs[0].mac }}"
when: host != [] and host.ipv4addrs[0].mac is defined
- name: Set niosip
set_fact:
niosip: "{{ host.ipv4addrs[0].ipv4addr }}"
nioshostname: "{{ host.name }}"
niosdhcp: "{{ host.ipv4addrs[0].configure_for_dhcp }}"
when: host != [] and host.ipv4addrs[0].mac is undefined

Variable won't be filtered correctly

what am I doing wrong?
I use below task to get all defined log_dirs of a host. Those information are stored in a fact, which is a dict and this works like a charm.
- name: get all_log_dirs
set_fact:
all_log_dirs="{{ (all_log_dirs|default({})) | combine( { item.key:vars[item.key] } ) }}"
with_dict: "{{ vars }}"
when: item.key is search('^((?!splunk).)*_log_dir')
Here the appropriate output:
"ansible_facts": {
"all_log_dirs": {
"springboot_server_log_dir": "{{ server_deployment_dir }}/logs"}
But the problem is, if I now want to use the new dict for e. g.:
- name: create symlink for splunk if not present
file:
src: "{{ item.value }}"
dest: "{{ splunk_log_dir }}/{{ item.key | regex_replace('_server_log_dir|_log_dir') | regex_replace('eap','jboss-eap') }}"
state: link
with_dict: "{{ all_log_dirs }}"
I only get:
failed: [...] (item={'value': u'{{ server_deployment_dir }}/logs', 'key': u'springboot_server_log_dir'}) => {
"changed": false,
"invocation": {
"module_args": {
"dest": "/somedir/springboot",
"path": "/somedir/springboot",
"src": "{{ server_deployment_dir }}/logs",
"state": "link",
}
},
"msg": "src file does not exist, use \"force=yes\" if you really want to create the link: /somedir/{{ server_deployment_dir }}/logs",
"path": "/somedir/springboot",
"src": "{{ server_deployment_dir }}/logs",
"state": "absent"
}
Why isn't {{ server_deployment_dir }} filtered correctly by Ansible?
Even I change src to dest and the way around, it won't work, because the variable isn't being filtered.
The value of {{ server_deployment_dir }} is of course host specific and is sth like /opt/applicationXY/appDeployDir
Don't use vars object. Period.
It is internal variable storage intended for under-the-hood usage.
When Ansible template engine detects vars access, it stops further templating chain!
Example:
---
- hosts: localhost
connection: local
gather_facts: no
vars:
myvar1: hello
myvar2: world
myvar3: "{{ myvar2 }}"
tasks:
- debug:
msg: "{{ myvar1 }} {{ myvar3 }}"
- debug:
msg: "{{ vars['myvar1'] }} {{ vars['myvar3'] }}"
Result:
TASK [debug] ***************************
ok: [localhost] => {
"msg": "hello world"
}
TASK [debug] ***************************
ok: [localhost] => {
"msg": "hello {{ myvar2 }}"
}
Update: if you utterly need to access variable trough vars object, there's vars lookup available in Ansible 2.5; and it templates values as usual:
E.g.
- debug:
msg: "{{ lookup('vars','myvar1') }} {{ lookup('vars','myvar3') }}"
results to hello world in the context of my previous example.

Resources