Get private IP from multi-home AWS EC2 dynamic inventory host - amazon-ec2

This is a follow up to my previous question.
I am in the process of moving from a static inventory to a Dynamic AWS-EC2 inventory, but my requirements are the same.
I need to be able to identify both the 'Management' private IP address and the 'Lan' private IP address for the hosts in a particular group.
Here's the content of the group net_platform_dev_linux extracted from the dynamic inventory:
"net_platform_dev_linux": {
"hosts": [
"dev-linux-1",
"dev-linux-2",
"dev-linux-3",
"dev-linux-4"
]
}
Then here's a sanitised and cutdown version of hostvars from a single host (dev-linux-1) in my EC2 inventory:
{
"ami_launch_index":0,
"ansible_host":"10.1.1.1",
"architecture":"x86_64",
"network_interfaces":[
{
"attachment":{
"attach_time":"2022-07-13T13:47:59+00:00",
"attachment_id":"eni-attach-defdefdefdefdef",
"delete_on_termination":false,
"device_index":0,
"network_card_index":0,
"status":"attached"
},
"description":"Lan-Interface",
"groups":[
{
"group_id":"sg-defdefdefdefdef",
"group_name":"sg-defdefdefdefdef"
}
],
"interface_type":"interface",
"ipv6_addresses":[
],
"mac_address":"98:yz:76:wx:54:uv",
"network_interface_id":"eni-defdefdefdefdef",
"owner_id":"0098765432100",
"private_dns_name":"ip-10-1-1-1.eu-west-2.compute.internal",
"private_ip_address":"10.1.1.1",
"private_ip_addresses":[
{
"primary":true,
"private_dns_name":"ip-10-1-1-1.eu-west-2.compute.internal",
"private_ip_address":"10.1.1.1"
}
],
"source_dest_check":true,
"status":"in-use",
"subnet_id":"subnet-defdefdefdefdef",
"vpc_id":"vpc-defdefdefdefdef"
},
{
"attachment":{
"attach_time":"2022-07-13T13:47:59+00:00",
"attachment_id":"eni-attach-abcabcabcabcabc",
"delete_on_termination":false,
"device_index":1,
"network_card_index":0,
"status":"attached"
},
"description":"Management-Interface",
"groups":[
{
"group_id":"sg-abcabcabcabc",
"group_name":"sg-abcabcabcabc"
}
],
"interface_type":"interface",
"ipv6_addresses":[
],
"mac_address":"ab:12:cd:34:ab:12",
"network_interface_id":"eni-abcabcabcabcabc",
"owner_id":"0098765432100",
"private_dns_name":"ip-10-2-0-1.eu-west-2.compute.internal",
"private_ip_address":"10.2.0.1",
"private_ip_addresses":[
{
"primary":true,
"private_dns_name":"ip-10-2-0-1.eu-west-2.compute.internal",
"private_ip_address":"10.2.0.1"
}
],
"source_dest_check":true,
"status":"in-use",
"subnet_id":"subnet-abcabcabcabcabc",
"vpc_id":"vpc-abcabcabcabcabc"
}
],
"owner_id":"abcabcabcabcabc",
"placement":{
"availability_zone":"eu-west-2a",
"group_name":"",
"region":"eu-west-2",
"tenancy":"default"
},
"platform_details":"Linux/UNIX",
"private_dns_name":"ip-10-1-1-1.eu-west-2.compute.internal",
"tags":{
"Platform":"linux",
"Contact":"not defined",
"Creator":"not defined",
"Environment":"Development",
"Name":"dev-linux-1"
}
}
I would like to be able create a comma separate string of private IP addresses for both the 'Management' interfaces and the 'LAN' interfaces.
Working example using static inventory (credit to β.εηοιτ.βε).
In a static inventory I could use the following to create a comma separate list of 'Management' IP addresses for members of the net_platform_dev_linux groups using the ansible_host value:
Hosts.yml
---
all:
hosts:
dev-linux-1:
ansible_host: 10.2.0.1
dev-linux-2:
ansible_host: 10.2.0.2
dev-linux-3:
ansible_host: 10.2.0.3
dev-linux-4:
ansible_host: 10.2.0.4
children:
net_platform_dev_linux:
hosts:
dev-linux-1:
dev-linux-2:
dev-linux-3:
dev-linux-4:
- debug:
msg: >-
{{
hostvars
| dict2items
| selectattr('key', 'in', groups.net_platform_dev_linux)
| map(attribute="value.ansible_host")
| join(',')
}}
TASK [debug] **************************************************************
ok: [localhost] =>
msg: 10.2.0.1,10.2.0.2,10.2.0.3,10.2.0.4
Desired output with the dynamic inventory:
I would like to be able to identify the network interfaces from their description eg: "description": "Lan-Interface" or "description": "Management-Interface" then be able to create a comma separate list of those IP addresses:
- debug:
msg:
- "Management Interfaces: {{ management_ips }}"
- "Lan Interfaces : {{ lan_ips }}"
TASK [debug] **************************************************************
ok: [localhost] =>
msg: Management Interfaces: 10.2.0.1,10.2.0.2,10.2.0.3,10.2.0.4
Lan Interfaces : 10.0.1.1,10.0.1.2,10.0.1.3,10.0.1.4

For those kind of cases, you have to return to a state you can handle and go further from there, to have something to start with:
- debug:
var: >-
hostvars
| dict2items
| selectattr('key', 'in', groups.net_platform_dev_linux)
Should give you a huge JSON you can start to analyse.
Then based on what you gave as one of your hosts variables, we can see that you are interested in all the dictionaries having the description equal to Lan-Interface or Management-Interface in a list of dictionaries. Which is what we did already with the selectattr to filter on keys.
The only difficulty, here, is that you will get a list of lists from this expression, since network_interfaces contains a list of all the interfaces:
- debug:
var: >-
hostvars
| dict2items
| selectattr('key', 'in', groups.net_platform_dev_linux)
| map(attribute='value.network_interfaces')
This said, since you only want to extract values from those network_interfaces, and nothing higher in the JSON hierarchy, you can simplify that list of lists by flatten'ing it.
From here on, you simply fall back to the same matter you resolved already, filter a list of dictionaries based on a property, map one of its attributes, join the list.
So we end up with:
- set_fact:
management_ips: >-
{{
hostvars
| dict2items
| selectattr('key', 'in', groups.net_platform_dev_linux)
| map(attribute='value.network_interfaces')
| flatten
| selectattr('description', '==', 'Management-Interface')
| map(attribute='private_ip_address')
| join(',')
}}
lan_ips: >-
{{
hostvars
| dict2items
| selectattr('key', 'in', groups.net_platform_dev_linux)
| map(attribute='value.network_interfaces')
| flatten
| selectattr('description', '==', 'Lan-Interface')
| map(attribute='private_ip_address')
| join(',')
}}
Which, if run through debug tasks, would give:
management_ips: 10.2.0.1,10.2.0.2,10.2.0.3,10.2.0.4
lan_ips: 10.1.1.1,10.1.1.2,10.1.1.3,10.1.1.4

Related

Multiple conditions in JMESPath query does not give any results

I am trying to query the following Infoblox data with Ansible and JMESPath json_query:
{
"ip_records.json": {
"result": [
{
"_ref": "fixedaddress/blabla",
"ipv4addr": "10.10.10.10",
"network_view": "Bla"
},
{
"_ref": "record:host/blabla",
"ipv4addrs": [
{
"_ref": "record:host_ipv4addr/blabla",
"host": "bla.bla.com",
"ipv4addr": "10.10.10.10"
}
],
"name": "bla.bla.com",
"view": " "
},
{
"_ref": "record:a/blabla",
"ipv4addr": "10.10.10.10",
"name": "bla.bla.com",
"view": "bla"
}
]
}
}
I want to get only the _ref value for the item with fixedaddress in the _ref value.
Forgot to add that there might also be multiple records with fixedaddress but different IP's. So I also want to filter on a specific IP as the same time.
I have created queries to filter
only on IP address given as input
the string fixedaddress
a combination of both
The first two work as expected. But, I want to combine both conditions and would expect to get the single item as output, but I get nothing. I tried using && and | to combine both, as showed below.
- name: "Search IP Record: Task 2.2: Filter Results."
vars:
jmesquery: "[] | [?ipv4addr==`{{ infoblox_ip }}`]._ref"
set_fact:
ip_records_refs: "{{ ip_records.json.result | json_query(jmesquery) }}"
- name: "Search IP Record: Task 2.4: Filter Results."
vars:
jmesquery: "[] | [?_ref.contains(#,`fixedaddress`)]._ref"
set_fact:
ip_records_refs: "{{ ip_records.json.result | to_json | from_json | json_query(jmesquery) }}"
- name: "Search IP Record: Task 2.6: Filter Results."
vars:
# jmesquery: "[] | ([?ipv4addr==`{{ infoblox_ip }}` && _ref.contains(#,`fixedaddress`)])._ref"
jmesquery: "[] | [?ipv4addr==`{{ infoblox_ip }}`].ref | [?_ref.contains(#,`fixedaddress`)]._ref"
set_fact:
ip_records_refs: "{{ ip_records.json.result | to_json | from_json | json_query(jmesquery) }}"
Output:
TASK [Search IP Record: Task 2.3 Dump variable Content] ***********
ok: [localhost] => {
"ip_records_refs": [
"fixedaddress/blabla",
"record:a/blabla"
]
}
TASK [Search IP Record: Task 2.5 Dump variable Content] ***********
ok: [localhost] => {
"ip_records_refs": [
"fixedaddress/blabla"
]
}
TASK [Search IP Record: Task 2.7 Dump variable Content] ***********
ok: [localhost] => {
"ip_records_refs": []
}
You are misusing the pipe expression.
From your trial, it is hard to tell exactly what you think it does, but here is a simple explanation: you might not see it, but a JMESPath filter on an array does not return you a JSON array, rather it returns you a projection.
You cannot chain a filter on top of projection, you need to reset it first, in order to get the resulting JSON array, and this is what the pipe expression is meant for.
In your case, you do not want to have a filter on top of a projection, you want a filter with multiple conditions, so, your last set_fact query should read:
jmesquery: >-
[?
_ref.contains(#,`fixedaddress`)
&& ipv4addr == `{{ infoblox_ip }}`
]._ref
And your two first queries should be simplified to:
jmesquery: "[?_ref.contains(#,`fixedaddress`)]._ref"
and
jmesquery: "[?ipv4addr == `{{ infoblox_ip }}`]._ref"
Q: "Get _ref value for the item with 'fixedaddress' in the _ref key."
A: The query below
jmesquery: "[?_ref.contains(#,`fixedaddress`)]._ref"
ip_records_refs: "{{ ip_records.json.result|json_query(jmesquery) }}"
gives the expected result
ip_records_refs:
- fixedaddress/blabla
Example of a complete playbook for testing
- hosts: localhost
vars:
ip_records:
json:
result:
- _ref: fixedaddress/blabla
ipv4addr: 10.10.10.10
network_view: Bla
- _ref: record:host/blabla
ipv4addrs:
- _ref: record:host_ipv4addr/blabla
host: bla.bla.com
ipv4addr: 10.10.10.10
name: bla.bla.com
view: ' '
- _ref: record:a/blabla
ipv4addr: 10.10.10.10
name: bla.bla.com
view: bla
# Get _ref value for the item with 'fixedaddress' in the _ref key
jmesquery: "[?_ref.contains(#,`fixedaddress`)]._ref"
ip_records_refs: "{{ ip_records.json.result|json_query(jmesquery) }}"
tasks:
- debug:
var: ip_records
- debug:
var: ip_records_refs

Ansible | delete files from a directory if the filename doesn't contains any of the strings from a list

I'm creating vm-s with libvirt, and I would like to do a housekeeping, if I delete a host (in this example a VM) from my inventory, at the next run of the playbook, it should delete that VM's qcow2 disk from the disk pool.
I don't really get, how could I create a nested loop that iterates through the file list of that specific directory and the list of vms in my inventory, checks if the name of the vm is part of any file in the filelist, and deletes the files whose have no connection into the inventory.
Here is an example from the many things I already tried:
- name: "Housekeeping: list qcow2 disks in libvirt-pool"
find:
paths: /mnt/hdd/libvirt-pool
depth: 1
patterns:
- "*.qcow2"
register: qcow_disks
- name: debug
debug:
msg: "{{item[0]}}"
with_nested:
- "{{ qcow_disks.files | map(attribute='path') | list }}"
- "{{ groups.vm }}"
when: item[1] in item[0]
register: valid_disks
- name: debug1
debug:
msg: "invalid disks: {{ valid_disks.results | difference(all_disk) }}"
variable:
all_disk: "{{ qcow_disks.files | map(attribute='path') | list }}"
Hope you can help me out!
Thanks in advance!
I assume you have in groups.vm a list of names of VMs, without the extension .qcow2.
So the list groups.vm could looks like e.g:
['vm1', 'vm5', 'test']
The find command returns files like:
[
"/mnt/hdd/libvirt-pool/bob.qcow2",
"/mnt/hdd/libvirt-pool/daniel.qcow2",
"/mnt/hdd/libvirt-pool/test.qcow2",
"/mnt/hdd/libvirt-pool/vm1.qcow2",
"/mnt/hdd/libvirt-pool/vm5.qcow2"
]
With the following command you can reduce this list to the name without extension, then you can easily compare the lists.
{{ qcow_disks.files | map(attribute='path') | map('basename') | map('splitext') | map('first') }}
basename returns the filename, without preceding path
splitext splits the filename into a list: [name, extension]
first takes the first element from the list, i.e. the name
More on basename and splitext in the Ansible docs.
{{ found_disks | reject('in', current_vms) }}
With the reject filter you can then discard the current elements, so that you contain a list with all old VMs.
The following tasks:
- name: "Housekeeping: list qcow2 disks in libvirt-pool"
find:
paths: /mnt/hdd/libvirt-pool
depth: 1
patterns:
- "*.qcow2"
register: qcow_disks
- debug:
msg: "{{ qcow_disks.files | map(attribute='path') }}"
- debug:
msg: "{{ old_disks }}"
vars:
current_vms: ['vm1', 'vm5', 'test']
found_disks: "{{ qcow_disks.files | map(attribute='path') | map('basename') | map('splitext') | map('first') }}"
old_disks: "{{ found_disks | reject('in', current_vms) }}"
Note: current_vms corresponds to the list you have via groups.vm.
return this result:
TASK [Housekeeping: list qcow2 disks in libvirt-pool] ************************
ok: [localhost]
TASK [debug] *****************************************************************
ok: [localhost] => {
"msg": [
"/mnt/hdd/libvirt-pool/bob.qcow2",
"/mnt/hdd/libvirt-pool/daniel.qcow2",
"/mnt/hdd/libvirt-pool/test.qcow2",
"/mnt/hdd/libvirt-pool/vm1.qcow2",
"/mnt/hdd/libvirt-pool/vm5.qcow2"
]
}
TASK [debug] *****************************************************************
ok: [localhost] => {
"msg": [
"bob",
"daniel"
]
}
I hope this helps you.

Create var based on list in dict

Imagine this dict on 4 different hosts.
# on host 1
my_dict:
ip: 10.0.0.111
roles:
- name: something
observer: false
# on host 2
my_dict:
ip: 10.0.0.112
roles:
- name: something
observer: false
# on host 3
my_dict:
ip: 10.0.0.113
roles:
- name: something
observer: true
# on host 4
my_dict:
ip: 10.0.0.114
roles:
- name: whatever
When Ansible runs for all 4 hosts I want it to build a variable for each host having the roles name 'something'. The desired output is:
10.0.0.111 10.0.0.112 10.0.0.113:observer
There are 2 requirements:
when my_dict.roles.name == 'something' it must add the ip to the var
but when my_dict.roles.observer , it must add the ip + ':observer'
I eventually want to use the var in a Jinja template, so to me, the var can be either set via an Ansible task or as a jinja template.
This doesn't work:
- name: set fact for ip
debug:
msg: >-
{{ ansible_play_hosts |
map('extract', hostvars, ['my_dict', 'ip'] ) |
join(' ') }}
when: ???
You could create two lists:
one with what should be postfixed to the IPs with the condition based on the observer property
the other one with the IPs
And then zip them back together.
Given:
- debug:
msg: >-
{{
_ips | zip(_is_observer) | map('join') | join(' ')
}}
vars:
_hosts: >-
{{
hostvars
| dict2items
| selectattr('key', 'in', ansible_play_hosts)
| selectattr('value.my_dict.roles.0.name', '==', 'something')
}}
_is_observer: >-
{{
_hosts
| map(attribute='value.my_dict.roles.0.observer')
| map('replace', false, '')
| map('replace', true, ':observer')
}}
_ips: >-
{{
_hosts
| map(attribute='value.my_dict.ip')
}}
This yields:
TASK [debug] *************************************************************
ok: [localhost] =>
msg: 10.0.0.111 10.0.0.112 10.0.0.113:observer

Ansible regex replace in variable to cisco interface names

I'm currently working with a script to create interface descriptions based on CDP neighbor info, but it's placing the full names e.g., GigabitEthernet1/1/1, HundredGigabitEthernet1/1/1.
My regex is weak, but I would like to do a regex replace to keep only the first 3 chars of the interface name.
I think a pattern like (dredGigatbitEthernet|abitEthernet|ntyGigabitEthernet|etc) should work, but not sure how to put that into the playbook line below to modify the port value
nxos_config:
lines:
- description {{ item.value[0].port }} ON {{ item.value[0].host }}
E.g, I am looking for GigabitEthernet1/1/1 to end up as Gig1/1/1
Here is an example of input data:
{
"FastEthernet1/1/1": [{
"host": "hostname",
"port": "Port 1"
}]
}
Final play to make it correct using ansible net neighbors as the source
Thank you - I updated my play, adjusted for ansible net neighbors
- name: Set Interface description based on CDP/LLDP discovery
hosts: all
gather_facts: yes
connection: network_cli
tasks:
- debug:
msg: "{{ ansible_facts.net_neighbors }}"
- debug:
msg: >-
description
{{
item[0].port
| regex_search('(.{3}).*([0-9]+\/[0-9]+\/[0-9]+)', '\1', '\2')
| join
}}
ON {{ item.value[0].host }}"
loop: "{{ ansible_facts.net_neighbors | dict2items }}"
loop_control:
label: "{{ item.key }}"
Thanks for the input!
Given that you want the three first characters along with the last 3 digits separated by a slash, then the regex (.{3}).*([0-9]+\/[0-9]+\/[0-9]+) should give you two capturing groups containing those two requirements.
In Ansible, you can use regex_search to extract those groups, then join them back, with the help on the join Jinja filter.
Given the playbook:
- hosts: localhost
gather_facts: no
tasks:
- debug:
msg: >-
description
{{
item.key
| regex_search('(.{3}).*([0-9]+\/[0-9]+\/[0-9]+)', '\1', '\2')
| join
}}
ON {{ item.value[0].host }}"
loop: "{{ interfaces | dict2items }}"
loop_control:
label: "{{ item.key }}"
vars:
interfaces:
GigabitEthernet1/1/1:
- port: Port 1
host: example.org
HundredGigabitEthernet1/1/1:
- port: Port 2
host: example.com
This yields:
TASK [debug] ***************************************************************
ok: [localhost] => (item=eth0) =>
msg: description Gig1/1/1 ON example.org"
ok: [localhost] => (item=eth1) =>
msg: description Hun1/1/1 ON example.com"

Ansible: Convert two lists into key, value dict

I have 2 lists as set_fact and want to create a dict
I am running ansible 2.8
I have list1 as below
"inventory_devices": [
"device0",
"device1"
]
and list2 as below
"inventory_ips": [
"10.1.1.1",
"10.1.1.2"
]
I want to get an output shows like
"inventory_dict": [
"device0": "10.1.1.1",
"device1": "10.1.1.2"
]
Thanks.
You can do it entirely with jinja2 using the zip filter built into ansible.
To get a list combining the elements of other lists use zip
- name: give me list combo of two lists
debug:
msg: "{{ [1,2,3,4,5] | zip(['a','b','c','d','e','f']) | list }}"
...
Similarly to the output of the items2dict filter mentioned above, these filters can be
used to contruct a dict:
{{ dict(keys_list | zip(values_list)) }}
The zip filter sequentially combines items from pairs of lists and the dict construct creates a dictionary from a list of pairs.
inventory_dict: "{{ dict(inventory_devices | zip(inventory_ips)) }}"
here is the task to do it, populate combined var in the PB below:
---
- hosts: localhost
gather_facts: false
vars:
inventory_devices:
- device0
- device1
inventory_ips:
- 10.1.1.1
- 10.1.1.2
tasks:
- name: populate combined var
set_fact:
combined_var: "{{ combined_var|default({}) | combine({ item.0: item.1 }) }}"
loop: "{{ query('together', inventory_devices, inventory_ips) }}"
- name: print combined var
debug:
var: combined_var
result:
TASK [print combined var] **********************************************************************************************************************************************************************************************
ok: [localhost] => {
"combined_var": {
"device0": "10.1.1.1",
"device1": "10.1.1.2"
}
}
hope it helps

Resources