Search code examples
amazon-ec2ansibleansible-inventory

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


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

Solution

  • 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