Extract hostvar list - ansible

I'm trying to get secondary ip addresses from hosts in a group and set them to a fact.
my hostvars contain
{
"network_interfaces": [
{
"private_ip_address": "10.224.1.48",
"private_ip_addresses": [
{
"primary": true,
"private_dns_name": "ip-10-224-1-48.us-east-2.compute.internal",
"private_ip_address": "10.224.1.48"
},
{
"primary": false,
"private_dns_name": "ip-10-224-1-66.us-east-2.compute.internal",
"private_ip_address": "10.224.1.66"
},
{
"primary": false,
"private_dns_name": "ip-10-224-1-135.us-east-2.compute.internal",
"private_ip_address": "10.224.1.135"
}
],
"source_dest_check": true,
"status": "in-use",
"subnet_id": "subnet-0cfc6e2da31b9cf50",
"vpc_id": "vpc-123456"
}
],
}
Something like
set_fact:
private_ips: "{{ groups['database'] | map('extract', hostvars, ['network_interfaces[0]','private_ip_addresses[1]','private_ip_address']) | join(',') }}"
which doesn't work.
set_fact:
private_ips: "{{ groups['database'] | map('extract', hostvars, ['network_interfaces']) | map(attribute='private_ip_addresses') }}"
ends up with "private_ips": "[AnsibleUndefined, AnsibleUndefined]"
I'm looking for the result to be just a single IP out of private_ip_addresses from each host in the group

Create the list of IPs at each host
- set_fact:
my_ip: "{{ network_interfaces|json_query(_query) }}"
vars:
_query: '[].private_ip_addresses[].private_ip_address'
gives for the data from the example
my_ip:
- 10.224.1.48
- 10.224.1.66
- 10.224.1.135
Then, select the second item from the lists of all hosts in the group database
- set_fact:
private_ips: "{{ groups.database|
map('extract', hostvars, 'my_ip')|
map(attribute=1)|list }}"
run_once: true

Related

Loop with subelements filter

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"
}

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 }}"

Filter debug msg

I am using Ansible 2.9.13 and I have this playbook:
---
- hosts: localhost
connection: local
vars:
ansible_python_interpreter: /usr/bin/env python3
vars_files:
- vars.yml
tasks:
- name: Get Tags from given VM Name
vmware_vm_info:
validate_certs: no
hostname: '{{ vcenter_server }}'
username: '{{ vcenter_user }}'
password: '{{ vcenter_pass }}'
folder: '{{ provision_folder }}'
delegate_to: localhost
register: vm_info
- debug:
msg: "{{ vm_info.virtual_machines | json_query(query) }}"
vars:
query: "[?guest_name=='C97A1612171478']"
When I run it I am getting this output:
ok: [localhost] => {
"msg": [
{
"attributes": {},
"cluster": "xxx01",
"esxi_hostname": "xxxx",
"guest_fullname": "Microsoft Windows 10 (64-bit)",
"guest_name": "C97A1612171478",
"ip_address": "10.x.x.x",
"mac_address": [
"0x:x:x:x:xd:x"
],
"power_state": "poweredOn",
"tags": [],
"uuid": "420xxaf-xxx-xe2-9xe-a5xxxxxa3c",
"vm_network": {
"0x:x:x:xa:x:x": {
"ipv4": [
"169.x.x.x"
],
"ipv6": [
"x::x:x:x:xc"
]
},
"x:x:x:x:x0:x1": {
"ipv4": [
"169.x.x.x"
],
"ipv6": [
"x::x7:xf:x:x"
]
},
"0x:5x:x:x:ax:x": {
"ipv4": [
"10.x.x.x"
],
"ipv6": [
"x::1xx:x:8xx:x"
]
}
}
}
]
}
How can I filter the output to make it show only the "ip_address": "10.x.x.x".
In the end only the 10.x.x.x.
I have tried some ways adding the key ip_address in the message code but all of them gave me an error.
I can filter the msg using Python but if there's a way to get it using Ansible I would like to know how.
If you want to get this information without a loop:
If you need an object as a result:
- debug:
msg: "{{ vm_info.virtual_machines | json_query(query) }}"
vars:
query: "[?guest_name=='C97A1612171478'] | [0].{ip_address: ip_address}"
will yield
{
"ip_address": "10.x.x.x"
}
If you need a string as a result:
- debug:
msg: "{{ vm_info.virtual_machines | json_query(query) }}"
vars:
query: "[?guest_name=='C97A1612171478'] | [0].ip_address"
will yield
"10.x.x.x"
I can't test this properly, but try to fiddle around with the following code:
- debug:
msg: "{{ item.ip_address | json_query(query) }}"
loop: "{{ vm_info.virtual_machines }}"
vars:
query: "[?guest_name=='C97A1612171478']"

Ansible set_fact is overwriting the items

Here is my main.yml
---
- name: Gathering VCenter facts
vmware_vm_info:
hostname: "{{ vcenter_server }}"
username: "{{ vcenter_user }}"
password: "{{ vcenter_pass }}"
validate_certs: false
register: vcenter_facts
delegate_to: localhost
- debug:
var: vcenter_facts.virtual_machines
- name: Find all test-vms to run IO
set_fact:
vm_ip: "{{ item.ip_address }}"
loop: "{{ vcenter_facts.virtual_machines }}"
when: item.guest_name is regex("test_vm*")
- name: print vm_ip variable value
debug:
var: vm_ip
- name: Mount 16TB dropbox in each test vm
shell: mount-16tb-dropbox.sh
args:
chdir: /usr/local/bin/
with_items: "{{ vm_ip }}"
And here is the recap:
ok: [localhost] => {
"vcenter_facts.virtual_machines": [
{
"attributes": {},
"cluster": "Compute Cluster",
"esxi_hostname": "100.80.90.179",
"guest_fullname": "CentOS 7 (64-bit)",
"guest_name": "test_vm4",
"ip_address": "192.168.202.13",
"mac_address": [
"00:50:56:9d:d2:99"
],
"power_state": "poweredOn",
"tags": [],
"uuid": "421d7b54-1359-14e8-3ec4-74b568cb96d2",
"vm_network": {
"00:50:56:9d:d2:99": {
"ipv4": [
"192.168.202.13"
],
"ipv6": [
"fe80::44f6:a395:cde3:4dd1",
"fe80::a357:a163:e44f:2086",
"fe80::cd0c:e7d7:1356:2830"
]
}
}
},
{
"attributes": {},
"cluster": "Compute Cluster",
"esxi_hostname": "100.80.90.178",
"guest_fullname": "CentOS 7 (64-bit)",
"guest_name": "test_vm3",
"ip_address": "192.168.202.12",
"mac_address": [
"00:50:56:9d:a9:e8"
],
"power_state": "poweredOn",
"tags": [],
"uuid": "421d9239-0980-80c1-bca4-540efd726452",
"vm_network": {
"00:50:56:9d:a9:e8": {
"ipv4": [
"192.168.202.12"
],
"ipv6": [
"fe80::cd0c:e7d7:1356:2830"
]
}
}
},
{
"attributes": {},
"cluster": "Compute Cluster",
"esxi_hostname": "100.80.90.178",
"guest_fullname": "CentOS 7 (64-bit)",
"guest_name": "Test_Automation_CentOS8_Linux_VM",
"ip_address": "192.168.202.6",
"mac_address": [
"00:50:56:9d:13:14"
],
"power_state": "poweredOn",
"tags": [],
"uuid": "421d53ba-4824-57e4-06fd-fba0f2b1dbea",
"vm_network": {
"00:50:56:9d:13:14": {
"ipv4": [
"192.168.202.6"
],
"ipv6": [
"fe80::cd0c:e7d7:1356:2830",
"fe80::44f6:a395:cde3:4dd1"
]
}
}
},
{
"attributes": {},
"cluster": "Compute Cluster",
"esxi_hostname": "100.80.90.180",
"guest_fullname": "CentOS 7 (64-bit)",
"guest_name": "test_vm5",
"ip_address": "192.168.202.14",
"mac_address": [
"00:50:56:9d:85:b6"
],
"power_state": "poweredOn",
"tags": [],
"uuid": "421d6855-e60e-cd80-f113-39f11927d63b",
"vm_network": {
"00:50:56:9d:85:b6": {
"ipv4": [
"192.168.202.14"
],
"ipv6": [
"fe80::44f6:a395:cde3:4dd1",
"fe80::cd0c:e7d7:1356:2830",
"fe80::a357:a163:e44f:2086"
]
}
}
}
]
}
I am not able to loop through all the ip_address variable (i.e. 192.168.202.12, 192.168.202.13, 192.168.202.14).
It just reads the last item (i.e. 192.168.202.14).
What am I possibly doing wrong with set_fact that it is not reading all the variable and performing the set of tasks that follows?
An alternate solution using json_query
---
- name: Gathering VCenter facts
vmware_vm_info:
hostname: "{{ vcenter_server }}"
username: "{{ vcenter_user }}"
password: "{{ vcenter_pass }}"
validate_certs: false
register: vcenter_facts
delegate_to: localhost
- name: Mount 16TB dropbox in each test vm
shell: mount-16tb-dropbox.sh
args:
chdir: /usr/local/bin/
vars:
query: >-
[?contains("guest_name", 'test_vm')].ip_address[]
with_items: "{{ vcenter_facts.virtual_machines | to_json | from_json | json_query(query) | list }}"
Note: to_json | from_json is a workaround for a bug between ansible and jmespath so that all values can be converted to real strings and can be used with the jmespath contains function.
This should give all the IP. You correctly assumed where the code may need correction. In the code, vm_ip variable was overwritten by each loop and the last IP remained. What you need is a list and then append each IP to the list.
- set_fact:
vm_ip: "{{ vm_ip | default([]) + [item.ip_address] }}"
loop: "{{ vcenter_facts.virtual_machines | flatten }}"
when: item.guest_name is regex("test_vm*")
- debug:
var: vm_ip
Alternative solution using Jinja2 filters.
- set_fact:
vm_ip: >-
{{ vcenter_facts.virtual_machines | flatten
| rejectattr('guest_name', 'match', '^(?!test_vm).*')
| map(attribute='ip_address') | list }}

Resources