1#!/usr/bin/python
2#
3# This is a free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This Ansible library is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this library.  If not, see <http://www.gnu.org/licenses/>.
15
16ANSIBLE_METADATA = {'metadata_version': '1.1',
17                    'status': ['preview'],
18                    'supported_by': 'community'}
19
20
21DOCUMENTATION = '''
22---
23module: ec2_eni_info
24short_description: Gather information about ec2 ENI interfaces in AWS
25description:
26    - Gather information about ec2 ENI interfaces in AWS
27    - This module was called C(ec2_eni_facts) before Ansible 2.9. The usage did not change.
28version_added: "2.0"
29author: "Rob White (@wimnat)"
30requirements: [ boto3 ]
31options:
32  filters:
33    description:
34      - A dict of filters to apply. Each dict item consists of a filter key and a filter value.
35        See U(https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeNetworkInterfaces.html) for possible filters.
36extends_documentation_fragment:
37    - aws
38    - ec2
39'''
40
41EXAMPLES = '''
42# Note: These examples do not set authentication details, see the AWS Guide for details.
43
44# Gather information about all ENIs
45- ec2_eni_info:
46
47# Gather information about a particular ENI
48- ec2_eni_info:
49    filters:
50      network-interface-id: eni-xxxxxxx
51
52'''
53
54RETURN = '''
55network_interfaces:
56  description: List of matching elastic network interfaces
57  returned: always
58  type: complex
59  contains:
60    association:
61      description: Info of associated elastic IP (EIP)
62      returned: always, empty dict if no association exists
63      type: dict
64      sample: {
65          allocation_id: "eipalloc-5sdf123",
66          association_id: "eipassoc-8sdf123",
67          ip_owner_id: "4415120123456",
68          public_dns_name: "ec2-52-1-0-63.compute-1.amazonaws.com",
69          public_ip: "52.1.0.63"
70        }
71    attachment:
72      description: Info about attached ec2 instance
73      returned: always, empty dict if ENI is not attached
74      type: dict
75      sample: {
76        attach_time: "2017-08-05T15:25:47+00:00",
77        attachment_id: "eni-attach-149d21234",
78        delete_on_termination: false,
79        device_index: 1,
80        instance_id: "i-15b8d3cadbafa1234",
81        instance_owner_id: "4415120123456",
82        status: "attached"
83      }
84    availability_zone:
85      description: Availability zone of ENI
86      returned: always
87      type: str
88      sample: "us-east-1b"
89    description:
90      description: Description text for ENI
91      returned: always
92      type: str
93      sample: "My favourite network interface"
94    groups:
95      description: List of attached security groups
96      returned: always
97      type: list
98      sample: [
99        {
100          group_id: "sg-26d0f1234",
101          group_name: "my_ec2_security_group"
102        }
103      ]
104    id:
105      description: The id of the ENI (alias for network_interface_id)
106      returned: always
107      type: str
108      sample: "eni-392fsdf"
109    interface_type:
110      description: Type of the network interface
111      returned: always
112      type: str
113      sample: "interface"
114    ipv6_addresses:
115      description: List of IPv6 addresses for this interface
116      returned: always
117      type: list
118      sample: []
119    mac_address:
120      description: MAC address of the network interface
121      returned: always
122      type: str
123      sample: "0a:f8:10:2f:ab:a1"
124    network_interface_id:
125      description: The id of the ENI
126      returned: always
127      type: str
128      sample: "eni-392fsdf"
129    owner_id:
130      description: AWS account id of the owner of the ENI
131      returned: always
132      type: str
133      sample: "4415120123456"
134    private_dns_name:
135      description: Private DNS name for the ENI
136      returned: always
137      type: str
138      sample: "ip-172-16-1-180.ec2.internal"
139    private_ip_address:
140      description: Private IP address for the ENI
141      returned: always
142      type: str
143      sample: "172.16.1.180"
144    private_ip_addresses:
145      description: List of private IP addresses attached to the ENI
146      returned: always
147      type: list
148      sample: []
149    requester_id:
150      description: The ID of the entity that launched the ENI
151      returned: always
152      type: str
153      sample: "AIDAIONYVJQNIAZFT3ABC"
154    requester_managed:
155      description:  Indicates whether the network interface is being managed by an AWS service.
156      returned: always
157      type: bool
158      sample: false
159    source_dest_check:
160      description: Indicates whether the network interface performs source/destination checking.
161      returned: always
162      type: bool
163      sample: false
164    status:
165      description: Indicates if the network interface is attached to an instance or not
166      returned: always
167      type: str
168      sample: "in-use"
169    subnet_id:
170      description: Subnet ID the ENI is in
171      returned: always
172      type: str
173      sample: "subnet-7bbf01234"
174    tag_set:
175      description: Dictionary of tags added to the ENI
176      returned: always
177      type: dict
178      sample: {}
179    vpc_id:
180      description: ID of the VPC the network interface it part of
181      returned: always
182      type: str
183      sample: "vpc-b3f1f123"
184'''
185
186try:
187    from botocore.exceptions import ClientError, NoCredentialsError
188    HAS_BOTO3 = True
189except ImportError:
190    HAS_BOTO3 = False
191
192from ansible.module_utils.basic import AnsibleModule
193from ansible.module_utils.ec2 import ansible_dict_to_boto3_filter_list, boto3_conn
194from ansible.module_utils.ec2 import boto3_tag_list_to_ansible_dict, camel_dict_to_snake_dict
195from ansible.module_utils.ec2 import ec2_argument_spec, get_aws_connection_info
196
197
198def list_eni(connection, module):
199
200    if module.params.get("filters") is None:
201        filters = []
202    else:
203        filters = ansible_dict_to_boto3_filter_list(module.params.get("filters"))
204
205    try:
206        network_interfaces_result = connection.describe_network_interfaces(Filters=filters)['NetworkInterfaces']
207    except (ClientError, NoCredentialsError) as e:
208        module.fail_json(msg=e.message)
209
210    # Modify boto3 tags list to be ansible friendly dict and then camel_case
211    camel_network_interfaces = []
212    for network_interface in network_interfaces_result:
213        network_interface['TagSet'] = boto3_tag_list_to_ansible_dict(network_interface['TagSet'])
214        # Added id to interface info to be compatible with return values of ec2_eni module:
215        network_interface['Id'] = network_interface['NetworkInterfaceId']
216        camel_network_interfaces.append(camel_dict_to_snake_dict(network_interface))
217
218    module.exit_json(network_interfaces=camel_network_interfaces)
219
220
221def get_eni_info(interface):
222
223    # Private addresses
224    private_addresses = []
225    for ip in interface.private_ip_addresses:
226        private_addresses.append({'private_ip_address': ip.private_ip_address, 'primary_address': ip.primary})
227
228    interface_info = {'id': interface.id,
229                      'subnet_id': interface.subnet_id,
230                      'vpc_id': interface.vpc_id,
231                      'description': interface.description,
232                      'owner_id': interface.owner_id,
233                      'status': interface.status,
234                      'mac_address': interface.mac_address,
235                      'private_ip_address': interface.private_ip_address,
236                      'source_dest_check': interface.source_dest_check,
237                      'groups': dict((group.id, group.name) for group in interface.groups),
238                      'private_ip_addresses': private_addresses
239                      }
240
241    if hasattr(interface, 'publicDnsName'):
242        interface_info['association'] = {'public_ip_address': interface.publicIp,
243                                         'public_dns_name': interface.publicDnsName,
244                                         'ip_owner_id': interface.ipOwnerId
245                                         }
246
247    if interface.attachment is not None:
248        interface_info['attachment'] = {'attachment_id': interface.attachment.id,
249                                        'instance_id': interface.attachment.instance_id,
250                                        'device_index': interface.attachment.device_index,
251                                        'status': interface.attachment.status,
252                                        'attach_time': interface.attachment.attach_time,
253                                        'delete_on_termination': interface.attachment.delete_on_termination,
254                                        }
255
256    return interface_info
257
258
259def main():
260    argument_spec = ec2_argument_spec()
261    argument_spec.update(
262        dict(
263            filters=dict(default=None, type='dict')
264        )
265    )
266
267    module = AnsibleModule(argument_spec=argument_spec)
268    if module._name == 'ec2_eni_facts':
269        module.deprecate("The 'ec2_eni_facts' module has been renamed to 'ec2_eni_info'", version='2.13')
270
271    if not HAS_BOTO3:
272        module.fail_json(msg='boto3 required for this module')
273
274    region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
275
276    connection = boto3_conn(module, conn_type='client', resource='ec2', region=region, endpoint=ec2_url, **aws_connect_params)
277
278    list_eni(connection, module)
279
280
281if __name__ == '__main__':
282    main()
283