summaryrefslogtreecommitdiff
path: root/test/support/integration/plugins/modules
diff options
context:
space:
mode:
Diffstat (limited to 'test/support/integration/plugins/modules')
-rw-r--r--test/support/integration/plugins/modules/ec2_eni.py633
-rw-r--r--test/support/integration/plugins/modules/ec2_eni_info.py276
-rw-r--r--test/support/integration/plugins/modules/ec2_instance.py1805
-rw-r--r--test/support/integration/plugins/modules/ec2_instance_info.py572
-rw-r--r--test/support/integration/plugins/modules/ec2_key.py271
-rw-r--r--test/support/integration/plugins/modules/ec2_vpc_igw.py283
-rw-r--r--test/support/integration/plugins/modules/ec2_vpc_route_table.py750
7 files changed, 0 insertions, 4590 deletions
diff --git a/test/support/integration/plugins/modules/ec2_eni.py b/test/support/integration/plugins/modules/ec2_eni.py
deleted file mode 100644
index 8b6dbd1c32..0000000000
--- a/test/support/integration/plugins/modules/ec2_eni.py
+++ /dev/null
@@ -1,633 +0,0 @@
-#!/usr/bin/python
-#
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
----
-module: ec2_eni
-short_description: Create and optionally attach an Elastic Network Interface (ENI) to an instance
-description:
- - Create and optionally attach an Elastic Network Interface (ENI) to an instance. If an ENI ID or private_ip is
- provided, the existing ENI (if any) will be modified. The 'attached' parameter controls the attachment status
- of the network interface.
-version_added: "2.0"
-author: "Rob White (@wimnat)"
-options:
- eni_id:
- description:
- - The ID of the ENI (to modify).
- - If I(eni_id=None) and I(state=present), a new eni will be created.
- type: str
- instance_id:
- description:
- - Instance ID that you wish to attach ENI to.
- - Since version 2.2, use the I(attached) parameter to attach or detach an ENI. Prior to 2.2, to detach an ENI from an instance, use C(None).
- type: str
- private_ip_address:
- description:
- - Private IP address.
- type: str
- subnet_id:
- description:
- - ID of subnet in which to create the ENI.
- type: str
- description:
- description:
- - Optional description of the ENI.
- type: str
- security_groups:
- description:
- - List of security groups associated with the interface. Only used when I(state=present).
- - Since version 2.2, you can specify security groups by ID or by name or a combination of both. Prior to 2.2, you can specify only by ID.
- type: list
- elements: str
- state:
- description:
- - Create or delete ENI.
- default: present
- choices: [ 'present', 'absent' ]
- type: str
- device_index:
- description:
- - The index of the device for the network interface attachment on the instance.
- default: 0
- type: int
- attached:
- description:
- - Specifies if network interface should be attached or detached from instance. If omitted, attachment status
- won't change
- version_added: 2.2
- type: bool
- force_detach:
- description:
- - Force detachment of the interface. This applies either when explicitly detaching the interface by setting I(instance_id=None)
- or when deleting an interface with I(state=absent).
- default: false
- type: bool
- delete_on_termination:
- description:
- - Delete the interface when the instance it is attached to is terminated. You can only specify this flag when the
- interface is being modified, not on creation.
- required: false
- type: bool
- source_dest_check:
- description:
- - By default, interfaces perform source/destination checks. NAT instances however need this check to be disabled.
- You can only specify this flag when the interface is being modified, not on creation.
- required: false
- type: bool
- secondary_private_ip_addresses:
- description:
- - A list of IP addresses to assign as secondary IP addresses to the network interface.
- This option is mutually exclusive of I(secondary_private_ip_address_count)
- required: false
- version_added: 2.2
- type: list
- elements: str
- purge_secondary_private_ip_addresses:
- description:
- - To be used with I(secondary_private_ip_addresses) to determine whether or not to remove any secondary IP addresses other than those specified.
- - Set I(secondary_private_ip_addresses=[]) to purge all secondary addresses.
- default: false
- type: bool
- version_added: 2.5
- secondary_private_ip_address_count:
- description:
- - The number of secondary IP addresses to assign to the network interface. This option is mutually exclusive of I(secondary_private_ip_addresses)
- required: false
- version_added: 2.2
- type: int
- allow_reassignment:
- description:
- - Indicates whether to allow an IP address that is already assigned to another network interface or instance
- to be reassigned to the specified network interface.
- required: false
- default: false
- type: bool
- version_added: 2.7
-extends_documentation_fragment:
- - aws
- - ec2
-notes:
- - This module identifies and ENI based on either the I(eni_id), a combination of I(private_ip_address) and I(subnet_id),
- or a combination of I(instance_id) and I(device_id). Any of these options will let you specify a particular ENI.
-'''
-
-EXAMPLES = '''
-# Note: These examples do not set authentication details, see the AWS Guide for details.
-
-# Create an ENI. As no security group is defined, ENI will be created in default security group
-- ec2_eni:
- private_ip_address: 172.31.0.20
- subnet_id: subnet-xxxxxxxx
- state: present
-
-# Create an ENI and attach it to an instance
-- ec2_eni:
- instance_id: i-xxxxxxx
- device_index: 1
- private_ip_address: 172.31.0.20
- subnet_id: subnet-xxxxxxxx
- state: present
-
-# Create an ENI with two secondary addresses
-- ec2_eni:
- subnet_id: subnet-xxxxxxxx
- state: present
- secondary_private_ip_address_count: 2
-
-# Assign a secondary IP address to an existing ENI
-# This will purge any existing IPs
-- ec2_eni:
- subnet_id: subnet-xxxxxxxx
- eni_id: eni-yyyyyyyy
- state: present
- secondary_private_ip_addresses:
- - 172.16.1.1
-
-# Remove any secondary IP addresses from an existing ENI
-- ec2_eni:
- subnet_id: subnet-xxxxxxxx
- eni_id: eni-yyyyyyyy
- state: present
- secondary_private_ip_address_count: 0
-
-# Destroy an ENI, detaching it from any instance if necessary
-- ec2_eni:
- eni_id: eni-xxxxxxx
- force_detach: true
- state: absent
-
-# Update an ENI
-- ec2_eni:
- eni_id: eni-xxxxxxx
- description: "My new description"
- state: present
-
-# Update an ENI identifying it by private_ip_address and subnet_id
-- ec2_eni:
- subnet_id: subnet-xxxxxxx
- private_ip_address: 172.16.1.1
- description: "My new description"
-
-# Detach an ENI from an instance
-- ec2_eni:
- eni_id: eni-xxxxxxx
- instance_id: None
- state: present
-
-### Delete an interface on termination
-# First create the interface
-- ec2_eni:
- instance_id: i-xxxxxxx
- device_index: 1
- private_ip_address: 172.31.0.20
- subnet_id: subnet-xxxxxxxx
- state: present
- register: eni
-
-# Modify the interface to enable the delete_on_terminaton flag
-- ec2_eni:
- eni_id: "{{ eni.interface.id }}"
- delete_on_termination: true
-
-'''
-
-
-RETURN = '''
-interface:
- description: Network interface attributes
- returned: when state != absent
- type: complex
- contains:
- description:
- description: interface description
- type: str
- sample: Firewall network interface
- groups:
- description: list of security groups
- type: list
- elements: dict
- sample: [ { "sg-f8a8a9da": "default" } ]
- id:
- description: network interface id
- type: str
- sample: "eni-1d889198"
- mac_address:
- description: interface's physical address
- type: str
- sample: "00:00:5E:00:53:23"
- owner_id:
- description: aws account id
- type: str
- sample: 812381371
- private_ip_address:
- description: primary ip address of this interface
- type: str
- sample: 10.20.30.40
- private_ip_addresses:
- description: list of all private ip addresses associated to this interface
- type: list
- elements: dict
- sample: [ { "primary_address": true, "private_ip_address": "10.20.30.40" } ]
- source_dest_check:
- description: value of source/dest check flag
- type: bool
- sample: True
- status:
- description: network interface status
- type: str
- sample: "pending"
- subnet_id:
- description: which vpc subnet the interface is bound
- type: str
- sample: subnet-b0a0393c
- vpc_id:
- description: which vpc this network interface is bound
- type: str
- sample: vpc-9a9a9da
-
-'''
-
-import time
-import re
-
-try:
- import boto.ec2
- import boto.vpc
- from boto.exception import BotoServerError
- HAS_BOTO = True
-except ImportError:
- HAS_BOTO = False
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.ec2 import (AnsibleAWSError, connect_to_aws,
- ec2_argument_spec, get_aws_connection_info,
- get_ec2_security_group_ids_from_names)
-
-
-def get_eni_info(interface):
-
- # Private addresses
- private_addresses = []
- for ip in interface.private_ip_addresses:
- private_addresses.append({'private_ip_address': ip.private_ip_address, 'primary_address': ip.primary})
-
- interface_info = {'id': interface.id,
- 'subnet_id': interface.subnet_id,
- 'vpc_id': interface.vpc_id,
- 'description': interface.description,
- 'owner_id': interface.owner_id,
- 'status': interface.status,
- 'mac_address': interface.mac_address,
- 'private_ip_address': interface.private_ip_address,
- 'source_dest_check': interface.source_dest_check,
- 'groups': dict((group.id, group.name) for group in interface.groups),
- 'private_ip_addresses': private_addresses
- }
-
- if interface.attachment is not None:
- interface_info['attachment'] = {'attachment_id': interface.attachment.id,
- 'instance_id': interface.attachment.instance_id,
- 'device_index': interface.attachment.device_index,
- 'status': interface.attachment.status,
- 'attach_time': interface.attachment.attach_time,
- 'delete_on_termination': interface.attachment.delete_on_termination,
- }
-
- return interface_info
-
-
-def wait_for_eni(eni, status):
-
- while True:
- time.sleep(3)
- eni.update()
- # If the status is detached we just need attachment to disappear
- if eni.attachment is None:
- if status == "detached":
- break
- else:
- if status == "attached" and eni.attachment.status == "attached":
- break
-
-
-def create_eni(connection, vpc_id, module):
-
- instance_id = module.params.get("instance_id")
- attached = module.params.get("attached")
- if instance_id == 'None':
- instance_id = None
- device_index = module.params.get("device_index")
- subnet_id = module.params.get('subnet_id')
- private_ip_address = module.params.get('private_ip_address')
- description = module.params.get('description')
- security_groups = get_ec2_security_group_ids_from_names(module.params.get('security_groups'), connection, vpc_id=vpc_id, boto3=False)
- secondary_private_ip_addresses = module.params.get("secondary_private_ip_addresses")
- secondary_private_ip_address_count = module.params.get("secondary_private_ip_address_count")
- changed = False
-
- try:
- eni = connection.create_network_interface(subnet_id, private_ip_address, description, security_groups)
- if attached and instance_id is not None:
- try:
- eni.attach(instance_id, device_index)
- except BotoServerError:
- eni.delete()
- raise
- # Wait to allow creation / attachment to finish
- wait_for_eni(eni, "attached")
- eni.update()
-
- if secondary_private_ip_address_count is not None:
- try:
- connection.assign_private_ip_addresses(network_interface_id=eni.id, secondary_private_ip_address_count=secondary_private_ip_address_count)
- except BotoServerError:
- eni.delete()
- raise
-
- if secondary_private_ip_addresses is not None:
- try:
- connection.assign_private_ip_addresses(network_interface_id=eni.id, private_ip_addresses=secondary_private_ip_addresses)
- except BotoServerError:
- eni.delete()
- raise
-
- changed = True
-
- except BotoServerError as e:
- module.fail_json(msg=e.message)
-
- module.exit_json(changed=changed, interface=get_eni_info(eni))
-
-
-def modify_eni(connection, vpc_id, module, eni):
-
- instance_id = module.params.get("instance_id")
- attached = module.params.get("attached")
- do_detach = module.params.get('state') == 'detached'
- device_index = module.params.get("device_index")
- description = module.params.get('description')
- security_groups = module.params.get('security_groups')
- force_detach = module.params.get("force_detach")
- source_dest_check = module.params.get("source_dest_check")
- delete_on_termination = module.params.get("delete_on_termination")
- secondary_private_ip_addresses = module.params.get("secondary_private_ip_addresses")
- purge_secondary_private_ip_addresses = module.params.get("purge_secondary_private_ip_addresses")
- secondary_private_ip_address_count = module.params.get("secondary_private_ip_address_count")
- allow_reassignment = module.params.get("allow_reassignment")
- changed = False
-
- try:
- if description is not None:
- if eni.description != description:
- connection.modify_network_interface_attribute(eni.id, "description", description)
- changed = True
- if len(security_groups) > 0:
- groups = get_ec2_security_group_ids_from_names(security_groups, connection, vpc_id=vpc_id, boto3=False)
- if sorted(get_sec_group_list(eni.groups)) != sorted(groups):
- connection.modify_network_interface_attribute(eni.id, "groupSet", groups)
- changed = True
- if source_dest_check is not None:
- if eni.source_dest_check != source_dest_check:
- connection.modify_network_interface_attribute(eni.id, "sourceDestCheck", source_dest_check)
- changed = True
- if delete_on_termination is not None and eni.attachment is not None:
- if eni.attachment.delete_on_termination is not delete_on_termination:
- connection.modify_network_interface_attribute(eni.id, "deleteOnTermination", delete_on_termination, eni.attachment.id)
- changed = True
-
- current_secondary_addresses = [i.private_ip_address for i in eni.private_ip_addresses if not i.primary]
- if secondary_private_ip_addresses is not None:
- secondary_addresses_to_remove = list(set(current_secondary_addresses) - set(secondary_private_ip_addresses))
- if secondary_addresses_to_remove and purge_secondary_private_ip_addresses:
- connection.unassign_private_ip_addresses(network_interface_id=eni.id,
- private_ip_addresses=list(set(current_secondary_addresses) -
- set(secondary_private_ip_addresses)),
- dry_run=False)
- changed = True
-
- secondary_addresses_to_add = list(set(secondary_private_ip_addresses) - set(current_secondary_addresses))
- if secondary_addresses_to_add:
- connection.assign_private_ip_addresses(network_interface_id=eni.id,
- private_ip_addresses=secondary_addresses_to_add,
- secondary_private_ip_address_count=None,
- allow_reassignment=allow_reassignment, dry_run=False)
- changed = True
- if secondary_private_ip_address_count is not None:
- current_secondary_address_count = len(current_secondary_addresses)
-
- if secondary_private_ip_address_count > current_secondary_address_count:
- connection.assign_private_ip_addresses(network_interface_id=eni.id,
- private_ip_addresses=None,
- secondary_private_ip_address_count=(secondary_private_ip_address_count -
- current_secondary_address_count),
- allow_reassignment=allow_reassignment, dry_run=False)
- changed = True
- elif secondary_private_ip_address_count < current_secondary_address_count:
- # How many of these addresses do we want to remove
- secondary_addresses_to_remove_count = current_secondary_address_count - secondary_private_ip_address_count
- connection.unassign_private_ip_addresses(network_interface_id=eni.id,
- private_ip_addresses=current_secondary_addresses[:secondary_addresses_to_remove_count],
- dry_run=False)
-
- if attached is True:
- if eni.attachment and eni.attachment.instance_id != instance_id:
- detach_eni(eni, module)
- eni.attach(instance_id, device_index)
- wait_for_eni(eni, "attached")
- changed = True
- if eni.attachment is None:
- eni.attach(instance_id, device_index)
- wait_for_eni(eni, "attached")
- changed = True
- elif attached is False:
- detach_eni(eni, module)
-
- except BotoServerError as e:
- module.fail_json(msg=e.message)
-
- eni.update()
- module.exit_json(changed=changed, interface=get_eni_info(eni))
-
-
-def delete_eni(connection, module):
-
- eni_id = module.params.get("eni_id")
- force_detach = module.params.get("force_detach")
-
- try:
- eni_result_set = connection.get_all_network_interfaces(eni_id)
- eni = eni_result_set[0]
-
- if force_detach is True:
- if eni.attachment is not None:
- eni.detach(force_detach)
- # Wait to allow detachment to finish
- wait_for_eni(eni, "detached")
- eni.update()
- eni.delete()
- changed = True
- else:
- eni.delete()
- changed = True
-
- module.exit_json(changed=changed)
- except BotoServerError as e:
- regex = re.compile('The networkInterface ID \'.*\' does not exist')
- if regex.search(e.message) is not None:
- module.exit_json(changed=False)
- else:
- module.fail_json(msg=e.message)
-
-
-def detach_eni(eni, module):
-
- attached = module.params.get("attached")
-
- force_detach = module.params.get("force_detach")
- if eni.attachment is not None:
- eni.detach(force_detach)
- wait_for_eni(eni, "detached")
- if attached:
- return
- eni.update()
- module.exit_json(changed=True, interface=get_eni_info(eni))
- else:
- module.exit_json(changed=False, interface=get_eni_info(eni))
-
-
-def uniquely_find_eni(connection, module):
-
- eni_id = module.params.get("eni_id")
- private_ip_address = module.params.get('private_ip_address')
- subnet_id = module.params.get('subnet_id')
- instance_id = module.params.get('instance_id')
- device_index = module.params.get('device_index')
- attached = module.params.get('attached')
-
- try:
- filters = {}
-
- # proceed only if we're univocally specifying an ENI
- if eni_id is None and private_ip_address is None and (instance_id is None and device_index is None):
- return None
-
- if private_ip_address and subnet_id:
- filters['private-ip-address'] = private_ip_address
- filters['subnet-id'] = subnet_id
-
- if not attached and instance_id and device_index:
- filters['attachment.instance-id'] = instance_id
- filters['attachment.device-index'] = device_index
-
- if eni_id is None and len(filters) == 0:
- return None
-
- eni_result = connection.get_all_network_interfaces(eni_id, filters=filters)
- if len(eni_result) == 1:
- return eni_result[0]
- else:
- return None
-
- except BotoServerError as e:
- module.fail_json(msg=e.message)
-
- return None
-
-
-def get_sec_group_list(groups):
-
- # Build list of remote security groups
- remote_security_groups = []
- for group in groups:
- remote_security_groups.append(group.id.encode())
-
- return remote_security_groups
-
-
-def _get_vpc_id(connection, module, subnet_id):
-
- try:
- return connection.get_all_subnets(subnet_ids=[subnet_id])[0].vpc_id
- except BotoServerError as e:
- module.fail_json(msg=e.message)
-
-
-def main():
- argument_spec = ec2_argument_spec()
- argument_spec.update(
- dict(
- eni_id=dict(default=None, type='str'),
- instance_id=dict(default=None, type='str'),
- private_ip_address=dict(type='str'),
- subnet_id=dict(type='str'),
- description=dict(type='str'),
- security_groups=dict(default=[], type='list'),
- device_index=dict(default=0, type='int'),
- state=dict(default='present', choices=['present', 'absent']),
- force_detach=dict(default='no', type='bool'),
- source_dest_check=dict(default=None, type='bool'),
- delete_on_termination=dict(default=None, type='bool'),
- secondary_private_ip_addresses=dict(default=None, type='list'),
- purge_secondary_private_ip_addresses=dict(default=False, type='bool'),
- secondary_private_ip_address_count=dict(default=None, type='int'),
- allow_reassignment=dict(default=False, type='bool'),
- attached=dict(default=None, type='bool')
- )
- )
-
- module = AnsibleModule(argument_spec=argument_spec,
- mutually_exclusive=[
- ['secondary_private_ip_addresses', 'secondary_private_ip_address_count']
- ],
- required_if=([
- ('state', 'absent', ['eni_id']),
- ('attached', True, ['instance_id']),
- ('purge_secondary_private_ip_addresses', True, ['secondary_private_ip_addresses'])
- ])
- )
-
- if not HAS_BOTO:
- module.fail_json(msg='boto required for this module')
-
- region, ec2_url, aws_connect_params = get_aws_connection_info(module)
-
- if region:
- try:
- connection = connect_to_aws(boto.ec2, region, **aws_connect_params)
- vpc_connection = connect_to_aws(boto.vpc, region, **aws_connect_params)
- except (boto.exception.NoAuthHandlerFound, AnsibleAWSError) as e:
- module.fail_json(msg=str(e))
- else:
- module.fail_json(msg="region must be specified")
-
- state = module.params.get("state")
-
- if state == 'present':
- eni = uniquely_find_eni(connection, module)
- if eni is None:
- subnet_id = module.params.get("subnet_id")
- if subnet_id is None:
- module.fail_json(msg="subnet_id is required when creating a new ENI")
-
- vpc_id = _get_vpc_id(vpc_connection, module, subnet_id)
- create_eni(connection, vpc_id, module)
- else:
- vpc_id = eni.vpc_id
- modify_eni(connection, vpc_id, module, eni)
-
- elif state == 'absent':
- delete_eni(connection, module)
-
-
-if __name__ == '__main__':
- main()
diff --git a/test/support/integration/plugins/modules/ec2_eni_info.py b/test/support/integration/plugins/modules/ec2_eni_info.py
deleted file mode 100644
index 1e281a4938..0000000000
--- a/test/support/integration/plugins/modules/ec2_eni_info.py
+++ /dev/null
@@ -1,276 +0,0 @@
-#!/usr/bin/python
-#
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
----
-module: ec2_eni_info
-short_description: Gather information about ec2 ENI interfaces in AWS
-description:
- - Gather information about ec2 ENI interfaces in AWS.
- - This module was called C(ec2_eni_facts) before Ansible 2.9. The usage did not change.
-version_added: "2.0"
-author: "Rob White (@wimnat)"
-requirements: [ boto3 ]
-options:
- filters:
- description:
- - A dict of filters to apply. Each dict item consists of a filter key and a filter value.
- See U(https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeNetworkInterfaces.html) for possible filters.
- type: dict
-extends_documentation_fragment:
- - aws
- - ec2
-'''
-
-EXAMPLES = '''
-# Note: These examples do not set authentication details, see the AWS Guide for details.
-
-# Gather information about all ENIs
-- ec2_eni_info:
-
-# Gather information about a particular ENI
-- ec2_eni_info:
- filters:
- network-interface-id: eni-xxxxxxx
-
-'''
-
-RETURN = '''
-network_interfaces:
- description: List of matching elastic network interfaces
- returned: always
- type: complex
- contains:
- association:
- description: Info of associated elastic IP (EIP)
- returned: always, empty dict if no association exists
- type: dict
- sample: {
- allocation_id: "eipalloc-5sdf123",
- association_id: "eipassoc-8sdf123",
- ip_owner_id: "4415120123456",
- public_dns_name: "ec2-52-1-0-63.compute-1.amazonaws.com",
- public_ip: "52.1.0.63"
- }
- attachment:
- description: Info about attached ec2 instance
- returned: always, empty dict if ENI is not attached
- type: dict
- sample: {
- attach_time: "2017-08-05T15:25:47+00:00",
- attachment_id: "eni-attach-149d21234",
- delete_on_termination: false,
- device_index: 1,
- instance_id: "i-15b8d3cadbafa1234",
- instance_owner_id: "4415120123456",
- status: "attached"
- }
- availability_zone:
- description: Availability zone of ENI
- returned: always
- type: str
- sample: "us-east-1b"
- description:
- description: Description text for ENI
- returned: always
- type: str
- sample: "My favourite network interface"
- groups:
- description: List of attached security groups
- returned: always
- type: list
- sample: [
- {
- group_id: "sg-26d0f1234",
- group_name: "my_ec2_security_group"
- }
- ]
- id:
- description: The id of the ENI (alias for network_interface_id)
- returned: always
- type: str
- sample: "eni-392fsdf"
- interface_type:
- description: Type of the network interface
- returned: always
- type: str
- sample: "interface"
- ipv6_addresses:
- description: List of IPv6 addresses for this interface
- returned: always
- type: list
- sample: []
- mac_address:
- description: MAC address of the network interface
- returned: always
- type: str
- sample: "0a:f8:10:2f:ab:a1"
- network_interface_id:
- description: The id of the ENI
- returned: always
- type: str
- sample: "eni-392fsdf"
- owner_id:
- description: AWS account id of the owner of the ENI
- returned: always
- type: str
- sample: "4415120123456"
- private_dns_name:
- description: Private DNS name for the ENI
- returned: always
- type: str
- sample: "ip-172-16-1-180.ec2.internal"
- private_ip_address:
- description: Private IP address for the ENI
- returned: always
- type: str
- sample: "172.16.1.180"
- private_ip_addresses:
- description: List of private IP addresses attached to the ENI
- returned: always
- type: list
- sample: []
- requester_id:
- description: The ID of the entity that launched the ENI
- returned: always
- type: str
- sample: "AIDAIONYVJQNIAZFT3ABC"
- requester_managed:
- description: Indicates whether the network interface is being managed by an AWS service.
- returned: always
- type: bool
- sample: false
- source_dest_check:
- description: Indicates whether the network interface performs source/destination checking.
- returned: always
- type: bool
- sample: false
- status:
- description: Indicates if the network interface is attached to an instance or not
- returned: always
- type: str
- sample: "in-use"
- subnet_id:
- description: Subnet ID the ENI is in
- returned: always
- type: str
- sample: "subnet-7bbf01234"
- tag_set:
- description: Dictionary of tags added to the ENI
- returned: always
- type: dict
- sample: {}
- vpc_id:
- description: ID of the VPC the network interface it part of
- returned: always
- type: str
- sample: "vpc-b3f1f123"
-'''
-
-try:
- from botocore.exceptions import ClientError, NoCredentialsError
- HAS_BOTO3 = True
-except ImportError:
- HAS_BOTO3 = False
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.ec2 import ansible_dict_to_boto3_filter_list, boto3_conn
-from ansible.module_utils.ec2 import boto3_tag_list_to_ansible_dict, camel_dict_to_snake_dict
-from ansible.module_utils.ec2 import ec2_argument_spec, get_aws_connection_info
-
-
-def list_eni(connection, module):
-
- if module.params.get("filters") is None:
- filters = []
- else:
- filters = ansible_dict_to_boto3_filter_list(module.params.get("filters"))
-
- try:
- network_interfaces_result = connection.describe_network_interfaces(Filters=filters)['NetworkInterfaces']
- except (ClientError, NoCredentialsError) as e:
- module.fail_json(msg=e.message)
-
- # Modify boto3 tags list to be ansible friendly dict and then camel_case
- camel_network_interfaces = []
- for network_interface in network_interfaces_result:
- network_interface['TagSet'] = boto3_tag_list_to_ansible_dict(network_interface['TagSet'])
- # Added id to interface info to be compatible with return values of ec2_eni module:
- network_interface['Id'] = network_interface['NetworkInterfaceId']
- camel_network_interfaces.append(camel_dict_to_snake_dict(network_interface))
-
- module.exit_json(network_interfaces=camel_network_interfaces)
-
-
-def get_eni_info(interface):
-
- # Private addresses
- private_addresses = []
- for ip in interface.private_ip_addresses:
- private_addresses.append({'private_ip_address': ip.private_ip_address, 'primary_address': ip.primary})
-
- interface_info = {'id': interface.id,
- 'subnet_id': interface.subnet_id,
- 'vpc_id': interface.vpc_id,
- 'description': interface.description,
- 'owner_id': interface.owner_id,
- 'status': interface.status,
- 'mac_address': interface.mac_address,
- 'private_ip_address': interface.private_ip_address,
- 'source_dest_check': interface.source_dest_check,
- 'groups': dict((group.id, group.name) for group in interface.groups),
- 'private_ip_addresses': private_addresses
- }
-
- if hasattr(interface, 'publicDnsName'):
- interface_info['association'] = {'public_ip_address': interface.publicIp,
- 'public_dns_name': interface.publicDnsName,
- 'ip_owner_id': interface.ipOwnerId
- }
-
- if interface.attachment is not None:
- interface_info['attachment'] = {'attachment_id': interface.attachment.id,
- 'instance_id': interface.attachment.instance_id,
- 'device_index': interface.attachment.device_index,
- 'status': interface.attachment.status,
- 'attach_time': interface.attachment.attach_time,
- 'delete_on_termination': interface.attachment.delete_on_termination,
- }
-
- return interface_info
-
-
-def main():
- argument_spec = ec2_argument_spec()
- argument_spec.update(
- dict(
- filters=dict(default=None, type='dict')
- )
- )
-
- module = AnsibleModule(argument_spec=argument_spec)
- if module._name == 'ec2_eni_facts':
- module.deprecate("The 'ec2_eni_facts' module has been renamed to 'ec2_eni_info'",
- version='2.13', collection_name='ansible.builtin')
-
- if not HAS_BOTO3:
- module.fail_json(msg='boto3 required for this module')
-
- region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
-
- connection = boto3_conn(module, conn_type='client', resource='ec2', region=region, endpoint=ec2_url, **aws_connect_params)
-
- list_eni(connection, module)
-
-
-if __name__ == '__main__':
- main()
diff --git a/test/support/integration/plugins/modules/ec2_instance.py b/test/support/integration/plugins/modules/ec2_instance.py
deleted file mode 100644
index 7a587fb941..0000000000
--- a/test/support/integration/plugins/modules/ec2_instance.py
+++ /dev/null
@@ -1,1805 +0,0 @@
-#!/usr/bin/python
-# Copyright: Ansible Project
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = '''
----
-module: ec2_instance
-short_description: Create & manage EC2 instances
-description:
- - Create and manage AWS EC2 instances.
- - >
- Note: This module does not support creating
- L(EC2 Spot instances,https://aws.amazon.com/ec2/spot/). The M(ec2) module
- can create and manage spot instances.
-version_added: "2.5"
-author:
- - Ryan Scott Brown (@ryansb)
-requirements: [ "boto3", "botocore" ]
-options:
- instance_ids:
- description:
- - If you specify one or more instance IDs, only instances that have the specified IDs are returned.
- type: list
- state:
- description:
- - Goal state for the instances.
- choices: [present, terminated, running, started, stopped, restarted, rebooted, absent]
- default: present
- type: str
- wait:
- description:
- - Whether or not to wait for the desired state (use wait_timeout to customize this).
- default: true
- type: bool
- wait_timeout:
- description:
- - How long to wait (in seconds) for the instance to finish booting/terminating.
- default: 600
- type: int
- instance_type:
- description:
- - Instance type to use for the instance, see U(https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html)
- Only required when instance is not already present.
- default: t2.micro
- type: str
- user_data:
- description:
- - Opaque blob of data which is made available to the ec2 instance
- type: str
- tower_callback:
- description:
- - Preconfigured user-data to enable an instance to perform a Tower callback (Linux only).
- - Mutually exclusive with I(user_data).
- - For Windows instances, to enable remote access via Ansible set I(tower_callback.windows) to true, and optionally set an admin password.
- - If using 'windows' and 'set_password', callback to Tower will not be performed but the instance will be ready to receive winrm connections from Ansible.
- type: dict
- suboptions:
- tower_address:
- description:
- - IP address or DNS name of Tower server. Must be accessible via this address from the VPC that this instance will be launched in.
- type: str
- job_template_id:
- description:
- - Either the integer ID of the Tower Job Template, or the name (name supported only for Tower 3.2+).
- type: str
- host_config_key:
- description:
- - Host configuration secret key generated by the Tower job template.
- type: str
- tags:
- description:
- - A hash/dictionary of tags to add to the new instance or to add/remove from an existing one.
- type: dict
- purge_tags:
- description:
- - Delete any tags not specified in the task that are on the instance.
- This means you have to specify all the desired tags on each task affecting an instance.
- default: false
- type: bool
- image:
- description:
- - An image to use for the instance. The M(ec2_ami_info) module may be used to retrieve images.
- One of I(image) or I(image_id) are required when instance is not already present.
- type: dict
- suboptions:
- id:
- description:
- - The AMI ID.
- type: str
- ramdisk:
- description:
- - Overrides the AMI's default ramdisk ID.
- type: str
- kernel:
- description:
- - a string AKI to override the AMI kernel.
- image_id:
- description:
- - I(ami) ID to use for the instance. One of I(image) or I(image_id) are required when instance is not already present.
- - This is an alias for I(image.id).
- type: str
- security_groups:
- description:
- - A list of security group IDs or names (strings). Mutually exclusive with I(security_group).
- type: list
- security_group:
- description:
- - A security group ID or name. Mutually exclusive with I(security_groups).
- type: str
- name:
- description:
- - The Name tag for the instance.
- type: str
- vpc_subnet_id:
- description:
- - The subnet ID in which to launch the instance (VPC)
- If none is provided, ec2_instance will chose the default zone of the default VPC.
- aliases: ['subnet_id']
- type: str
- network:
- description:
- - Either a dictionary containing the key 'interfaces' corresponding to a list of network interface IDs or
- containing specifications for a single network interface.
- - Use the ec2_eni module to create ENIs with special settings.
- type: dict
- suboptions:
- interfaces:
- description:
- - a list of ENI IDs (strings) or a list of objects containing the key I(id).
- type: list
- assign_public_ip:
- description:
- - when true assigns a public IP address to the interface
- type: bool
- private_ip_address:
- description:
- - an IPv4 address to assign to the interface
- type: str
- ipv6_addresses:
- description:
- - a list of IPv6 addresses to assign to the network interface
- type: list
- source_dest_check:
- description:
- - controls whether source/destination checking is enabled on the interface
- type: bool
- description:
- description:
- - a description for the network interface
- type: str
- private_ip_addresses:
- description:
- - a list of IPv4 addresses to assign to the network interface
- type: list
- subnet_id:
- description:
- - the subnet to connect the network interface to
- type: str
- delete_on_termination:
- description:
- - Delete the interface when the instance it is attached to is
- terminated.
- type: bool
- device_index:
- description:
- - The index of the interface to modify
- type: int
- groups:
- description:
- - a list of security group IDs to attach to the interface
- type: list
- volumes:
- description:
- - A list of block device mappings, by default this will always use the AMI root device so the volumes option is primarily for adding more storage.
- - A mapping contains the (optional) keys device_name, virtual_name, ebs.volume_type, ebs.volume_size, ebs.kms_key_id,
- ebs.iops, and ebs.delete_on_termination.
- - For more information about each parameter, see U(https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_BlockDeviceMapping.html).
- type: list
- launch_template:
- description:
- - The EC2 launch template to base instance configuration on.
- type: dict
- suboptions:
- id:
- description:
- - the ID of the launch template (optional if name is specified).
- type: str
- name:
- description:
- - the pretty name of the launch template (optional if id is specified).
- type: str
- version:
- description:
- - the specific version of the launch template to use. If unspecified, the template default is chosen.
- key_name:
- description:
- - Name of the SSH access key to assign to the instance - must exist in the region the instance is created.
- type: str
- availability_zone:
- description:
- - Specify an availability zone to use the default subnet it. Useful if not specifying the I(vpc_subnet_id) parameter.
- - If no subnet, ENI, or availability zone is provided, the default subnet in the default VPC will be used in the first AZ (alphabetically sorted).
- type: str
- instance_initiated_shutdown_behavior:
- description:
- - Whether to stop or terminate an instance upon shutdown.
- choices: ['stop', 'terminate']
- type: str
- tenancy:
- description:
- - What type of tenancy to allow an instance to use. Default is shared tenancy. Dedicated tenancy will incur additional charges.
- choices: ['dedicated', 'default']
- type: str
- termination_protection:
- description:
- - Whether to enable termination protection.
- This module will not terminate an instance with termination protection active, it must be turned off first.
- type: bool
- cpu_credit_specification:
- description:
- - For T series instances, choose whether to allow increased charges to buy CPU credits if the default pool is depleted.
- - Choose I(unlimited) to enable buying additional CPU credits.
- choices: ['unlimited', 'standard']
- type: str
- cpu_options:
- description:
- - Reduce the number of vCPU exposed to the instance.
- - Those parameters can only be set at instance launch. The two suboptions threads_per_core and core_count are mandatory.
- - See U(https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-optimize-cpu.html) for combinations available.
- - Requires botocore >= 1.10.16
- version_added: 2.7
- type: dict
- suboptions:
- threads_per_core:
- description:
- - Select the number of threads per core to enable. Disable or Enable Intel HT.
- choices: [1, 2]
- required: true
- type: int
- core_count:
- description:
- - Set the number of core to enable.
- required: true
- type: int
- detailed_monitoring:
- description:
- - Whether to allow detailed cloudwatch metrics to be collected, enabling more detailed alerting.
- type: bool
- ebs_optimized:
- description:
- - Whether instance is should use optimized EBS volumes, see U(https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSOptimized.html).
- type: bool
- filters:
- description:
- - A dict of filters to apply when deciding whether existing instances match and should be altered. Each dict item
- consists of a filter key and a filter value. See
- U(https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html).
- for possible filters. Filter names and values are case sensitive.
- - By default, instances are filtered for counting by their "Name" tag, base AMI, state (running, by default), and
- subnet ID. Any queryable filter can be used. Good candidates are specific tags, SSH keys, or security groups.
- type: dict
- instance_role:
- description:
- - The ARN or name of an EC2-enabled instance role to be used. If a name is not provided in arn format
- then the ListInstanceProfiles permission must also be granted.
- U(https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListInstanceProfiles.html) If no full ARN is provided,
- the role with a matching name will be used from the active AWS account.
- type: str
- placement_group:
- description:
- - The placement group that needs to be assigned to the instance
- version_added: 2.8
- type: str
-
-extends_documentation_fragment:
- - aws
- - ec2
-'''
-
-EXAMPLES = '''
-# Note: These examples do not set authentication details, see the AWS Guide for details.
-
-# Terminate every running instance in a region. Use with EXTREME caution.
-- ec2_instance:
- state: absent
- filters:
- instance-state-name: running
-
-# restart a particular instance by its ID
-- ec2_instance:
- state: restarted
- instance_ids:
- - i-12345678
-
-# start an instance with a public IP address
-- ec2_instance:
- name: "public-compute-instance"
- key_name: "prod-ssh-key"
- vpc_subnet_id: subnet-5ca1ab1e
- instance_type: c5.large
- security_group: default
- network:
- assign_public_ip: true
- image_id: ami-123456
- tags:
- Environment: Testing
-
-# start an instance and Add EBS
-- ec2_instance:
- name: "public-withebs-instance"
- vpc_subnet_id: subnet-5ca1ab1e
- instance_type: t2.micro
- key_name: "prod-ssh-key"
- security_group: default
- volumes:
- - device_name: /dev/sda1
- ebs:
- volume_size: 16
- delete_on_termination: true
-
-# start an instance with a cpu_options
-- ec2_instance:
- name: "public-cpuoption-instance"
- vpc_subnet_id: subnet-5ca1ab1e
- tags:
- Environment: Testing
- instance_type: c4.large
- volumes:
- - device_name: /dev/sda1
- ebs:
- delete_on_termination: true
- cpu_options:
- core_count: 1
- threads_per_core: 1
-
-# start an instance and have it begin a Tower callback on boot
-- ec2_instance:
- name: "tower-callback-test"
- key_name: "prod-ssh-key"
- vpc_subnet_id: subnet-5ca1ab1e
- security_group: default
- tower_callback:
- # IP or hostname of tower server
- tower_address: 1.2.3.4
- job_template_id: 876
- host_config_key: '[secret config key goes here]'
- network:
- assign_public_ip: true
- image_id: ami-123456
- cpu_credit_specification: unlimited
- tags:
- SomeThing: "A value"
-
-# start an instance with ENI (An existing ENI ID is required)
-- ec2_instance:
- name: "public-eni-instance"
- key_name: "prod-ssh-key"
- vpc_subnet_id: subnet-5ca1ab1e
- network:
- interfaces:
- - id: "eni-12345"
- tags:
- Env: "eni_on"
- volumes:
- - device_name: /dev/sda1
- ebs:
- delete_on_termination: true
- instance_type: t2.micro
- image_id: ami-123456
-
-# add second ENI interface
-- ec2_instance:
- name: "public-eni-instance"
- network:
- interfaces:
- - id: "eni-12345"
- - id: "eni-67890"
- image_id: ami-123456
- tags:
- Env: "eni_on"
- instance_type: t2.micro
-'''
-
-RETURN = '''
-instances:
- description: a list of ec2 instances
- returned: when wait == true
- type: complex
- contains:
- ami_launch_index:
- description: The AMI launch index, which can be used to find this instance in the launch group.
- returned: always
- type: int
- sample: 0
- architecture:
- description: The architecture of the image
- returned: always
- type: str
- sample: x86_64
- block_device_mappings:
- description: Any block device mapping entries for the instance.
- returned: always
- type: complex
- contains:
- device_name:
- description: The device name exposed to the instance (for example, /dev/sdh or xvdh).
- returned: always
- type: str
- sample: /dev/sdh
- ebs:
- description: Parameters used to automatically set up EBS volumes when the instance is launched.
- returned: always
- type: complex
- contains:
- attach_time:
- description: The time stamp when the attachment initiated.
- returned: always
- type: str
- sample: "2017-03-23T22:51:24+00:00"
- delete_on_termination:
- description: Indicates whether the volume is deleted on instance termination.
- returned: always
- type: bool
- sample: true
- status:
- description: The attachment state.
- returned: always
- type: str
- sample: attached
- volume_id:
- description: The ID of the EBS volume
- returned: always
- type: str
- sample: vol-12345678
- client_token:
- description: The idempotency token you provided when you launched the instance, if applicable.
- returned: always
- type: str
- sample: mytoken
- ebs_optimized:
- description: Indicates whether the instance is optimized for EBS I/O.
- returned: always
- type: bool
- sample: false
- hypervisor:
- description: The hypervisor type of the instance.
- returned: always
- type: str
- sample: xen
- iam_instance_profile:
- description: The IAM instance profile associated with the instance, if applicable.
- returned: always
- type: complex
- contains:
- arn:
- description: The Amazon Resource Name (ARN) of the instance profile.
- returned: always
- type: str
- sample: "arn:aws:iam::000012345678:instance-profile/myprofile"
- id:
- description: The ID of the instance profile
- returned: always
- type: str
- sample: JFJ397FDG400FG9FD1N
- image_id:
- description: The ID of the AMI used to launch the instance.
- returned: always
- type: str
- sample: ami-0011223344
- instance_id:
- description: The ID of the instance.
- returned: always
- type: str
- sample: i-012345678
- instance_type:
- description: The instance type size of the running instance.
- returned: always
- type: str
- sample: t2.micro
- key_name:
- description: The name of the key pair, if this instance was launched with an associated key pair.
- returned: always
- type: str
- sample: my-key
- launch_time:
- description: The time the instance was launched.
- returned: always
- type: str
- sample: "2017-03-23T22:51:24+00:00"
- monitoring:
- description: The monitoring for the instance.
- returned: always
- type: complex
- contains:
- state:
- description: Indicates whether detailed monitoring is enabled. Otherwise, basic monitoring is enabled.
- returned: always
- type: str
- sample: disabled
- network_interfaces:
- description: One or more network interfaces for the instance.
- returned: always
- type: complex
- contains:
- association:
- description: The association information for an Elastic IPv4 associated with the network interface.
- returned: always
- type: complex
- contains:
- ip_owner_id:
- description: The ID of the owner of the Elastic IP address.
- returned: always
- type: str
- sample: amazon
- public_dns_name:
- description: The public DNS name.
- returned: always
- type: str
- sample: ""
- public_ip:
- description: The public IP address or Elastic IP address bound to the network interface.
- returned: always
- type: str
- sample: 1.2.3.4
- attachment:
- description: The network interface attachment.
- returned: always
- type: complex
- contains:
- attach_time:
- description: The time stamp when the attachment initiated.
- returned: always
- type: str
- sample: "2017-03-23T22:51:24+00:00"
- attachment_id:
- description: The ID of the network interface attachment.
- returned: always
- type: str
- sample: eni-attach-3aff3f
- delete_on_termination:
- description: Indicates whether the network interface is deleted when the instance is terminated.
- returned: always
- type: bool
- sample: true
- device_index:
- description: The index of the device on the instance for the network interface attachment.
- returned: always
- type: int
- sample: 0
- status:
- description: The attachment state.
- returned: always
- type: str
- sample: attached
- description:
- description: The description.
- returned: always
- type: str
- sample: My interface
- groups:
- description: One or more security groups.
- returned: always
- type: list
- elements: dict
- contains:
- group_id:
- description: The ID of the security group.
- returned: always
- type: str
- sample: sg-abcdef12
- group_name:
- description: The name of the security group.
- returned: always
- type: str
- sample: mygroup
- ipv6_addresses:
- description: One or more IPv6 addresses associated with the network interface.
- returned: always
- type: list
- elements: dict
- contains:
- ipv6_address:
- description: The IPv6 address.
- returned: always
- type: str
- sample: "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
- mac_address:
- description: The MAC address.
- returned: always
- type: str
- sample: "00:11:22:33:44:55"
- network_interface_id:
- description: The ID of the network interface.
- returned: always
- type: str
- sample: eni-01234567
- owner_id:
- description: The AWS account ID of the owner of the network interface.
- returned: always
- type: str
- sample: 01234567890
- private_ip_address:
- description: The IPv4 address of the network interface within the subnet.
- returned: always
- type: str
- sample: 10.0.0.1
- private_ip_addresses:
- description: The private IPv4 addresses associated with the network interface.
- returned: always
- type: list
- elements: dict
- contains:
- association:
- description: The association information for an Elastic IP address (IPv4) associated with the network interface.
- returned: always
- type: complex
- contains:
- ip_owner_id:
- description: The ID of the owner of the Elastic IP address.
- returned: always
- type: str
- sample: amazon
- public_dns_name:
- description: The public DNS name.
- returned: always
- type: str
- sample: ""
- public_ip:
- description: The public IP address or Elastic IP address bound to the network interface.
- returned: always
- type: str
- sample: 1.2.3.4
- primary:
- description: Indicates whether this IPv4 address is the primary private IP address of the network interface.
- returned: always
- type: bool
- sample: true
- private_ip_address:
- description: The private IPv4 address of the network interface.
- returned: always
- type: str
- sample: 10.0.0.1
- source_dest_check:
- description: Indicates whether source/destination checking is enabled.
- returned: always
- type: bool
- sample: true
- status:
- description: The status of the network interface.
- returned: always
- type: str
- sample: in-use
- subnet_id:
- description: The ID of the subnet for the network interface.
- returned: always
- type: str
- sample: subnet-0123456
- vpc_id:
- description: The ID of the VPC for the network interface.
- returned: always
- type: str
- sample: vpc-0123456
- placement:
- description: The location where the instance launched, if applicable.
- returned: always
- type: complex
- contains:
- availability_zone:
- description: The Availability Zone of the instance.
- returned: always
- type: str
- sample: ap-southeast-2a
- group_name:
- description: The name of the placement group the instance is in (for cluster compute instances).
- returned: always
- type: str
- sample: ""
- tenancy:
- description: The tenancy of the instance (if the instance is running in a VPC).
- returned: always
- type: str
- sample: default
- private_dns_name:
- description: The private DNS name.
- returned: always
- type: str
- sample: ip-10-0-0-1.ap-southeast-2.compute.internal
- private_ip_address:
- description: The IPv4 address of the network interface within the subnet.
- returned: always
- type: str
- sample: 10.0.0.1
- product_codes:
- description: One or more product codes.
- returned: always
- type: list
- elements: dict
- contains:
- product_code_id:
- description: The product code.
- returned: always
- type: str
- sample: aw0evgkw8ef3n2498gndfgasdfsd5cce
- product_code_type:
- description: The type of product code.
- returned: always
- type: str
- sample: marketplace
- public_dns_name:
- description: The public DNS name assigned to the instance.
- returned: always
- type: str
- sample:
- public_ip_address:
- description: The public IPv4 address assigned to the instance
- returned: always
- type: str
- sample: 52.0.0.1
- root_device_name:
- description: The device name of the root device
- returned: always
- type: str
- sample: /dev/sda1
- root_device_type:
- description: The type of root device used by the AMI.
- returned: always
- type: str
- sample: ebs
- security_groups:
- description: One or more security groups for the instance.
- returned: always
- type: list
- elements: dict
- contains:
- group_id:
- description: The ID of the security group.
- returned: always
- type: str
- sample: sg-0123456
- group_name:
- description: The name of the security group.
- returned: always
- type: str
- sample: my-security-group
- network.source_dest_check:
- description: Indicates whether source/destination checking is enabled.
- returned: always
- type: bool
- sample: true
- state:
- description: The current state of the instance.
- returned: always
- type: complex
- contains:
- code:
- description: The low byte represents the state.
- returned: always
- type: int
- sample: 16
- name:
- description: The name of the state.
- returned: always
- type: str
- sample: running
- state_transition_reason:
- description: The reason for the most recent state transition.
- returned: always
- type: str
- sample:
- subnet_id:
- description: The ID of the subnet in which the instance is running.
- returned: always
- type: str
- sample: subnet-00abcdef
- tags:
- description: Any tags assigned to the instance.
- returned: always
- type: dict
- sample:
- virtualization_type:
- description: The type of virtualization of the AMI.
- returned: always
- type: str
- sample: hvm
- vpc_id:
- description: The ID of the VPC the instance is in.
- returned: always
- type: dict
- sample: vpc-0011223344
-'''
-
-import re
-import uuid
-import string
-import textwrap
-import time
-from collections import namedtuple
-
-try:
- import boto3
- import botocore.exceptions
-except ImportError:
- pass # caught by AnsibleAWSModule
-
-from ansible.module_utils.six import text_type, string_types
-from ansible.module_utils.six.moves.urllib import parse as urlparse
-from ansible.module_utils._text import to_bytes, to_native
-import ansible.module_utils.ec2 as ec2_utils
-from ansible.module_utils.ec2 import (AWSRetry,
- ansible_dict_to_boto3_filter_list,
- compare_aws_tags,
- boto3_tag_list_to_ansible_dict,
- ansible_dict_to_boto3_tag_list,
- camel_dict_to_snake_dict)
-
-from ansible.module_utils.aws.core import AnsibleAWSModule
-
-module = None
-
-
-def tower_callback_script(tower_conf, windows=False, passwd=None):
- script_url = 'https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1'
- if windows and passwd is not None:
- script_tpl = """<powershell>
- $admin = [adsi]("WinNT://./administrator, user")
- $admin.PSBase.Invoke("SetPassword", "{PASS}")
- Invoke-Expression ((New-Object System.Net.Webclient).DownloadString('{SCRIPT}'))
- </powershell>
- """
- return to_native(textwrap.dedent(script_tpl).format(PASS=passwd, SCRIPT=script_url))
- elif windows and passwd is None:
- script_tpl = """<powershell>
- $admin = [adsi]("WinNT://./administrator, user")
- Invoke-Expression ((New-Object System.Net.Webclient).DownloadString('{SCRIPT}'))
- </powershell>
- """
- return to_native(textwrap.dedent(script_tpl).format(PASS=passwd, SCRIPT=script_url))
- elif not windows:
- for p in ['tower_address', 'job_template_id', 'host_config_key']:
- if p not in tower_conf:
- module.fail_json(msg="Incomplete tower_callback configuration. tower_callback.{0} not set.".format(p))
-
- if isinstance(tower_conf['job_template_id'], string_types):
- tower_conf['job_template_id'] = urlparse.quote(tower_conf['job_template_id'])
- tpl = string.Template(textwrap.dedent("""#!/bin/bash
- set -x
-
- retry_attempts=10
- attempt=0
- while [[ $attempt -lt $retry_attempts ]]
- do
- status_code=`curl --max-time 10 -v -k -s -i \
- --data "host_config_key=${host_config_key}" \
- 'https://${tower_address}/api/v2/job_templates/${template_id}/callback/' \
- | head -n 1 \
- | awk '{print $2}'`
- if [[ $status_code == 404 ]]
- then
- status_code=`curl --max-time 10 -v -k -s -i \
- --data "host_config_key=${host_config_key}" \
- 'https://${tower_address}/api/v1/job_templates/${template_id}/callback/' \
- | head -n 1 \
- | awk '{print $2}'`
- # fall back to using V1 API for Tower 3.1 and below, since v2 API will always 404
- fi
- if [[ $status_code == 201 ]]
- then
- exit 0
- fi
- attempt=$(( attempt + 1 ))
- echo "$${status_code} received... retrying in 1 minute. (Attempt $${attempt})"
- sleep 60
- done
- exit 1
- """))
- return tpl.safe_substitute(tower_address=tower_conf['tower_address'],
- template_id=tower_conf['job_template_id'],
- host_config_key=tower_conf['host_config_key'])
- raise NotImplementedError("Only windows with remote-prep or non-windows with tower job callback supported so far.")
-
-
-@AWSRetry.jittered_backoff()
-def manage_tags(match, new_tags, purge_tags, ec2):
- changed = False
- old_tags = boto3_tag_list_to_ansible_dict(match['Tags'])
- tags_to_set, tags_to_delete = compare_aws_tags(
- old_tags, new_tags,
- purge_tags=purge_tags,
- )
- if tags_to_set:
- ec2.create_tags(
- Resources=[match['InstanceId']],
- Tags=ansible_dict_to_boto3_tag_list(tags_to_set))
- changed |= True
- if tags_to_delete:
- delete_with_current_values = dict((k, old_tags.get(k)) for k in tags_to_delete)
- ec2.delete_tags(
- Resources=[match['InstanceId']],
- Tags=ansible_dict_to_boto3_tag_list(delete_with_current_values))
- changed |= True
- return changed
-
-
-def build_volume_spec(params):
- volumes = params.get('volumes') or []
- for volume in volumes:
- if 'ebs' in volume:
- for int_value in ['volume_size', 'iops']:
- if int_value in volume['ebs']:
- volume['ebs'][int_value] = int(volume['ebs'][int_value])
- return [ec2_utils.snake_dict_to_camel_dict(v, capitalize_first=True) for v in volumes]
-
-
-def add_or_update_instance_profile(instance, desired_profile_name):
- instance_profile_setting = instance.get('IamInstanceProfile')
- if instance_profile_setting and desired_profile_name:
- if desired_profile_name in (instance_profile_setting.get('Name'), instance_profile_setting.get('Arn')):
- # great, the profile we asked for is what's there
- return False
- else:
- desired_arn = determine_iam_role(desired_profile_name)
- if instance_profile_setting.get('Arn') == desired_arn:
- return False
- # update association
- ec2 = module.client('ec2')
- try:
- association = ec2.describe_iam_instance_profile_associations(Filters=[{'Name': 'instance-id', 'Values': [instance['InstanceId']]}])
- except botocore.exceptions.ClientError as e:
- # check for InvalidAssociationID.NotFound
- module.fail_json_aws(e, "Could not find instance profile association")
- try:
- resp = ec2.replace_iam_instance_profile_association(
- AssociationId=association['IamInstanceProfileAssociations'][0]['AssociationId'],
- IamInstanceProfile={'Arn': determine_iam_role(desired_profile_name)}
- )
- return True
- except botocore.exceptions.ClientError as e:
- module.fail_json_aws(e, "Could not associate instance profile")
-
- if not instance_profile_setting and desired_profile_name:
- # create association
- ec2 = module.client('ec2')
- try:
- resp = ec2.associate_iam_instance_profile(
- IamInstanceProfile={'Arn': determine_iam_role(desired_profile_name)},
- InstanceId=instance['InstanceId']
- )
- return True
- except botocore.exceptions.ClientError as e:
- module.fail_json_aws(e, "Could not associate new instance profile")
-
- return False
-
-
-def build_network_spec(params, ec2=None):
- """
- Returns list of interfaces [complex]
- Interface type: {
- 'AssociatePublicIpAddress': True|False,
- 'DeleteOnTermination': True|False,
- 'Description': 'string',
- 'DeviceIndex': 123,
- 'Groups': [
- 'string',
- ],
- 'Ipv6AddressCount': 123,
- 'Ipv6Addresses': [
- {
- 'Ipv6Address': 'string'
- },
- ],
- 'NetworkInterfaceId': 'string',
- 'PrivateIpAddress': 'string',
- 'PrivateIpAddresses': [
- {
- 'Primary': True|False,
- 'PrivateIpAddress': 'string'
- },
- ],
- 'SecondaryPrivateIpAddressCount': 123,
- 'SubnetId': 'string'
- },
- """
- if ec2 is None:
- ec2 = module.client('ec2')
-
- interfaces = []
- network = params.get('network') or {}
- if not network.get('interfaces'):
- # they only specified one interface
- spec = {
- 'DeviceIndex': 0,
- }
- if network.get('assign_public_ip') is not None:
- spec['AssociatePublicIpAddress'] = network['assign_public_ip']
-
- if params.get('vpc_subnet_id'):
- spec['SubnetId'] = params['vpc_subnet_id']
- else:
- default_vpc = get_default_vpc(ec2)
- if default_vpc is None:
- raise module.fail_json(
- msg="No default subnet could be found - you must include a VPC subnet ID (vpc_subnet_id parameter) to create an instance")
- else:
- sub = get_default_subnet(ec2, default_vpc)
- spec['SubnetId'] = sub['SubnetId']
-
- if network.get('private_ip_address'):
- spec['PrivateIpAddress'] = network['private_ip_address']
-
- if params.get('security_group') or params.get('security_groups'):
- groups = discover_security_groups(
- group=params.get('security_group'),
- groups=params.get('security_groups'),
- subnet_id=spec['SubnetId'],
- ec2=ec2
- )
- spec['Groups'] = [g['GroupId'] for g in groups]
- if network.get('description') is not None:
- spec['Description'] = network['description']
- # TODO more special snowflake network things
-
- return [spec]
-
- # handle list of `network.interfaces` options
- for idx, interface_params in enumerate(network.get('interfaces', [])):
- spec = {
- 'DeviceIndex': idx,
- }
-
- if isinstance(interface_params, string_types):
- # naive case where user gave
- # network_interfaces: [eni-1234, eni-4567, ....]
- # put into normal data structure so we don't dupe code
- interface_params = {'id': interface_params}
-
- if interface_params.get('id') is not None:
- # if an ID is provided, we don't want to set any other parameters.
- spec['NetworkInterfaceId'] = interface_params['id']
- interfaces.append(spec)
- continue
-
- spec['DeleteOnTermination'] = interface_params.get('delete_on_termination', True)
-
- if interface_params.get('ipv6_addresses'):
- spec['Ipv6Addresses'] = [{'Ipv6Address': a} for a in interface_params.get('ipv6_addresses', [])]
-
- if interface_params.get('private_ip_address'):
- spec['PrivateIpAddress'] = interface_params.get('private_ip_address')
-
- if interface_params.get('description'):
- spec['Description'] = interface_params.get('description')
-
- if interface_params.get('subnet_id', params.get('vpc_subnet_id')):
- spec['SubnetId'] = interface_params.get('subnet_id', params.get('vpc_subnet_id'))
- elif not spec.get('SubnetId') and not interface_params['id']:
- # TODO grab a subnet from default VPC
- raise ValueError('Failed to assign subnet to interface {0}'.format(interface_params))
-
- interfaces.append(spec)
- return interfaces
-
-
-def warn_if_public_ip_assignment_changed(instance):
- # This is a non-modifiable attribute.
- assign_public_ip = (module.params.get('network') or {}).get('assign_public_ip')
- if assign_public_ip is None:
- return
-
- # Check that public ip assignment is the same and warn if not
- public_dns_name = instance.get('PublicDnsName')
- if (public_dns_name and not assign_public_ip) or (assign_public_ip and not public_dns_name):
- module.warn(
- "Unable to modify public ip assignment to {0} for instance {1}. "
- "Whether or not to assign a public IP is determined during instance creation.".format(
- assign_public_ip, instance['InstanceId']))
-
-
-def warn_if_cpu_options_changed(instance):
- # This is a non-modifiable attribute.
- cpu_options = module.params.get('cpu_options')
- if cpu_options is None:
- return
-
- # Check that the CpuOptions set are the same and warn if not
- core_count_curr = instance['CpuOptions'].get('CoreCount')
- core_count = cpu_options.get('core_count')
- threads_per_core_curr = instance['CpuOptions'].get('ThreadsPerCore')
- threads_per_core = cpu_options.get('threads_per_core')
- if core_count_curr != core_count:
- module.warn(
- "Unable to modify core_count from {0} to {1}. "
- "Assigning a number of core is determinted during instance creation".format(
- core_count_curr, core_count))
-
- if threads_per_core_curr != threads_per_core:
- module.warn(
- "Unable to modify threads_per_core from {0} to {1}. "
- "Assigning a number of threads per core is determined during instance creation.".format(
- threads_per_core_curr, threads_per_core))
-
-
-def discover_security_groups(group, groups, parent_vpc_id=None, subnet_id=None, ec2=None):
- if ec2 is None:
- ec2 = module.client('ec2')
-
- if subnet_id is not None:
- try:
- sub = ec2.describe_subnets(SubnetIds=[subnet_id])
- except botocore.exceptions.ClientError as e:
- if e.response['Error']['Code'] == 'InvalidGroup.NotFound':
- module.fail_json(
- "Could not find subnet {0} to associate security groups. Please check the vpc_subnet_id and security_groups parameters.".format(
- subnet_id
- )
- )
- module.fail_json_aws(e, msg="Error while searching for subnet {0} parent VPC.".format(subnet_id))
- except botocore.exceptions.BotoCoreError as e:
- module.fail_json_aws(e, msg="Error while searching for subnet {0} parent VPC.".format(subnet_id))
- parent_vpc_id = sub['Subnets'][0]['VpcId']
-
- vpc = {
- 'Name': 'vpc-id',
- 'Values': [parent_vpc_id]
- }
-
- # because filter lists are AND in the security groups API,
- # make two separate requests for groups by ID and by name
- id_filters = [vpc]
- name_filters = [vpc]
-
- if group:
- name_filters.append(
- dict(
- Name='group-name',
- Values=[group]
- )
- )
- if group.startswith('sg-'):
- id_filters.append(
- dict(
- Name='group-id',
- Values=[group]
- )
- )
- if groups:
- name_filters.append(
- dict(
- Name='group-name',
- Values=groups
- )
- )
- if [g for g in groups if g.startswith('sg-')]:
- id_filters.append(
- dict(
- Name='group-id',
- Values=[g for g in groups if g.startswith('sg-')]
- )
- )
-
- found_groups = []
- for f_set in (id_filters, name_filters):
- if len(f_set) > 1:
- found_groups.extend(ec2.get_paginator(
- 'describe_security_groups'
- ).paginate(
- Filters=f_set
- ).search('SecurityGroups[]'))
- return list(dict((g['GroupId'], g) for g in found_groups).values())
-
-
-def build_top_level_options(params):
- spec = {}
- if params.get('image_id'):
- spec['ImageId'] = params['image_id']
- elif isinstance(params.get('image'), dict):
- image = params.get('image', {})
- spec['ImageId'] = image.get('id')
- if 'ramdisk' in image:
- spec['RamdiskId'] = image['ramdisk']
- if 'kernel' in image:
- spec['KernelId'] = image['kernel']
- if not spec.get('ImageId') and not params.get('launch_template'):
- module.fail_json(msg="You must include an image_id or image.id parameter to create an instance, or use a launch_template.")
-
- if params.get('key_name') is not None:
- spec['KeyName'] = params.get('key_name')
- if params.get('user_data') is not None:
- spec['UserData'] = to_native(params.get('user_data'))
- elif params.get('tower_callback') is not None:
- spec['UserData'] = tower_callback_script(
- tower_conf=params.get('tower_callback'),
- windows=params.get('tower_callback').get('windows', False),
- passwd=params.get('tower_callback').get('set_password'),
- )
-
- if params.get('launch_template') is not None:
- spec['LaunchTemplate'] = {}
- if not params.get('launch_template').get('id') or params.get('launch_template').get('name'):
- module.fail_json(msg="Could not create instance with launch template. Either launch_template.name or launch_template.id parameters are required")
-
- if params.get('launch_template').get('id') is not None:
- spec['LaunchTemplate']['LaunchTemplateId'] = params.get('launch_template').get('id')
- if params.get('launch_template').get('name') is not None:
- spec['LaunchTemplate']['LaunchTemplateName'] = params.get('launch_template').get('name')
- if params.get('launch_template').get('version') is not None:
- spec['LaunchTemplate']['Version'] = to_native(params.get('launch_template').get('version'))
-
- if params.get('detailed_monitoring', False):
- spec['Monitoring'] = {'Enabled': True}
- if params.get('cpu_credit_specification') is not None:
- spec['CreditSpecification'] = {'CpuCredits': params.get('cpu_credit_specification')}
- if params.get('tenancy') is not None:
- spec['Placement'] = {'Tenancy': params.get('tenancy')}
- if params.get('placement_group'):
- if 'Placement' in spec:
- spec['Placement']['GroupName'] = str(params.get('placement_group'))
- else:
- spec.setdefault('Placement', {'GroupName': str(params.get('placement_group'))})
- if params.get('ebs_optimized') is not None:
- spec['EbsOptimized'] = params.get('ebs_optimized')
- if params.get('instance_initiated_shutdown_behavior'):
- spec['InstanceInitiatedShutdownBehavior'] = params.get('instance_initiated_shutdown_behavior')
- if params.get('termination_protection') is not None:
- spec['DisableApiTermination'] = params.get('termination_protection')
- if params.get('cpu_options') is not None:
- spec['CpuOptions'] = {}
- spec['CpuOptions']['ThreadsPerCore'] = params.get('cpu_options').get('threads_per_core')
- spec['CpuOptions']['CoreCount'] = params.get('cpu_options').get('core_count')
- return spec
-
-
-def build_instance_tags(params, propagate_tags_to_volumes=True):
- tags = params.get('tags', {})
- if params.get('name') is not None:
- if tags is None:
- tags = {}
- tags['Name'] = params.get('name')
- return [
- {
- 'ResourceType': 'volume',
- 'Tags': ansible_dict_to_boto3_tag_list(tags),
- },
- {
- 'ResourceType': 'instance',
- 'Tags': ansible_dict_to_boto3_tag_list(tags),
- },
- ]
-
-
-def build_run_instance_spec(params, ec2=None):
- if ec2 is None:
- ec2 = module.client('ec2')
-
- spec = dict(
- ClientToken=uuid.uuid4().hex,
- MaxCount=1,
- MinCount=1,
- )
- # network parameters
- spec['NetworkInterfaces'] = build_network_spec(params, ec2)
- spec['BlockDeviceMappings'] = build_volume_spec(params)
- spec.update(**build_top_level_options(params))
- spec['TagSpecifications'] = build_instance_tags(params)
-
- # IAM profile
- if params.get('instance_role'):
- spec['IamInstanceProfile'] = dict(Arn=determine_iam_role(params.get('instance_role')))
-
- spec['InstanceType'] = params['instance_type']
- return spec
-
-
-def await_instances(ids, state='OK'):
- if not module.params.get('wait', True):
- # the user asked not to wait for anything
- return
-
- if module.check_mode:
- # In check mode, there is no change even if you wait.
- return
-
- state_opts = {
- 'OK': 'instance_status_ok',
- 'STOPPED': 'instance_stopped',
- 'TERMINATED': 'instance_terminated',
- 'EXISTS': 'instance_exists',
- 'RUNNING': 'instance_running',
- }
- if state not in state_opts:
- module.fail_json(msg="Cannot wait for state {0}, invalid state".format(state))
- waiter = module.client('ec2').get_waiter(state_opts[state])
- try:
- waiter.wait(
- InstanceIds=ids,
- WaiterConfig={
- 'Delay': 15,
- 'MaxAttempts': module.params.get('wait_timeout', 600) // 15,
- }
- )
- except botocore.exceptions.WaiterConfigError as e:
- module.fail_json(msg="{0}. Error waiting for instances {1} to reach state {2}".format(
- to_native(e), ', '.join(ids), state))
- except botocore.exceptions.WaiterError as e:
- module.warn("Instances {0} took too long to reach state {1}. {2}".format(
- ', '.join(ids), state, to_native(e)))
-
-
-def diff_instance_and_params(instance, params, ec2=None, skip=None):
- """boto3 instance obj, module params"""
- if ec2 is None:
- ec2 = module.client('ec2')
-
- if skip is None:
- skip = []
-
- changes_to_apply = []
- id_ = instance['InstanceId']
-
- ParamMapper = namedtuple('ParamMapper', ['param_key', 'instance_key', 'attribute_name', 'add_value'])
-
- def value_wrapper(v):
- return {'Value': v}
-
- param_mappings = [
- ParamMapper('ebs_optimized', 'EbsOptimized', 'ebsOptimized', value_wrapper),
- ParamMapper('termination_protection', 'DisableApiTermination', 'disableApiTermination', value_wrapper),
- # user data is an immutable property
- # ParamMapper('user_data', 'UserData', 'userData', value_wrapper),
- ]
-
- for mapping in param_mappings:
- if params.get(mapping.param_key) is not None and mapping.instance_key not in skip:
- value = AWSRetry.jittered_backoff()(ec2.describe_instance_attribute)(Attribute=mapping.attribute_name, InstanceId=id_)
- if params.get(mapping.param_key) is not None and value[mapping.instance_key]['Value'] != params.get(mapping.param_key):
- arguments = dict(
- InstanceId=instance['InstanceId'],
- # Attribute=mapping.attribute_name,
- )
- arguments[mapping.instance_key] = mapping.add_value(params.get(mapping.param_key))
- changes_to_apply.append(arguments)
-
- if (params.get('network') or {}).get('source_dest_check') is not None:
- # network.source_dest_check is nested, so needs to be treated separately
- check = bool(params.get('network').get('source_dest_check'))
- if instance['SourceDestCheck'] != check:
- changes_to_apply.append(dict(
- InstanceId=instance['InstanceId'],
- SourceDestCheck={'Value': check},
- ))
-
- return changes_to_apply
-
-
-def change_network_attachments(instance, params, ec2):
- if (params.get('network') or {}).get('interfaces') is not None:
- new_ids = []
- for inty in params.get('network').get('interfaces'):
- if isinstance(inty, dict) and 'id' in inty:
- new_ids.append(inty['id'])
- elif isinstance(inty, string_types):
- new_ids.append(inty)
- # network.interfaces can create the need to attach new interfaces
- old_ids = [inty['NetworkInterfaceId'] for inty in instance['NetworkInterfaces']]
- to_attach = set(new_ids) - set(old_ids)
- for eni_id in to_attach:
- ec2.attach_network_interface(
- DeviceIndex=new_ids.index(eni_id),
- InstanceId=instance['InstanceId'],
- NetworkInterfaceId=eni_id,
- )
- return bool(len(to_attach))
- return False
-
-
-def find_instances(ec2, ids=None, filters=None):
- paginator = ec2.get_paginator('describe_instances')
- if ids:
- return list(paginator.paginate(
- InstanceIds=ids,
- ).search('Reservations[].Instances[]'))
- elif filters is None:
- module.fail_json(msg="No filters provided when they were required")
- elif filters is not None:
- for key in list(filters.keys()):
- if not key.startswith("tag:"):
- filters[key.replace("_", "-")] = filters.pop(key)
- return list(paginator.paginate(
- Filters=ansible_dict_to_boto3_filter_list(filters)
- ).search('Reservations[].Instances[]'))
- return []
-
-
-@AWSRetry.jittered_backoff()
-def get_default_vpc(ec2):
- vpcs = ec2.describe_vpcs(Filters=ansible_dict_to_boto3_filter_list({'isDefault': 'true'}))
- if len(vpcs.get('Vpcs', [])):
- return vpcs.get('Vpcs')[0]
- return None
-
-
-@AWSRetry.jittered_backoff()
-def get_default_subnet(ec2, vpc, availability_zone=None):
- subnets = ec2.describe_subnets(
- Filters=ansible_dict_to_boto3_filter_list({
- 'vpc-id': vpc['VpcId'],
- 'state': 'available',
- 'default-for-az': 'true',
- })
- )
- if len(subnets.get('Subnets', [])):
- if availability_zone is not None:
- subs_by_az = dict((subnet['AvailabilityZone'], subnet) for subnet in subnets.get('Subnets'))
- if availability_zone in subs_by_az:
- return subs_by_az[availability_zone]
-
- # to have a deterministic sorting order, we sort by AZ so we'll always pick the `a` subnet first
- # there can only be one default-for-az subnet per AZ, so the AZ key is always unique in this list
- by_az = sorted(subnets.get('Subnets'), key=lambda s: s['AvailabilityZone'])
- return by_az[0]
- return None
-
-
-def ensure_instance_state(state, ec2=None):
- if ec2 is None:
- module.client('ec2')
- if state in ('running', 'started'):
- changed, failed, instances, failure_reason = change_instance_state(filters=module.params.get('filters'), desired_state='RUNNING')
-
- if failed:
- module.fail_json(
- msg="Unable to start instances: {0}".format(failure_reason),
- reboot_success=list(changed),
- reboot_failed=failed)
-
- module.exit_json(
- msg='Instances started',
- reboot_success=list(changed),
- changed=bool(len(changed)),
- reboot_failed=[],
- instances=[pretty_instance(i) for i in instances],
- )
- elif state in ('restarted', 'rebooted'):
- changed, failed, instances, failure_reason = change_instance_state(
- filters=module.params.get('filters'),
- desired_state='STOPPED')
- changed, failed, instances, failure_reason = change_instance_state(
- filters=module.params.get('filters'),
- desired_state='RUNNING')
-
- if failed:
- module.fail_json(
- msg="Unable to restart instances: {0}".format(failure_reason),
- reboot_success=list(changed),
- reboot_failed=failed)
-
- module.exit_json(
- msg='Instances restarted',
- reboot_success=list(changed),
- changed=bool(len(changed)),
- reboot_failed=[],
- instances=[pretty_instance(i) for i in instances],
- )
- elif state in ('stopped',):
- changed, failed, instances, failure_reason = change_instance_state(
- filters=module.params.get('filters'),
- desired_state='STOPPED')
-
- if failed:
- module.fail_json(
- msg="Unable to stop instances: {0}".format(failure_reason),
- stop_success=list(changed),
- stop_failed=failed)
-
- module.exit_json(
- msg='Instances stopped',
- stop_success=list(changed),
- changed=bool(len(changed)),
- stop_failed=[],
- instances=[pretty_instance(i) for i in instances],
- )
- elif state in ('absent', 'terminated'):
- terminated, terminate_failed, instances, failure_reason = change_instance_state(
- filters=module.params.get('filters'),
- desired_state='TERMINATED')
-
- if terminate_failed:
- module.fail_json(
- msg="Unable to terminate instances: {0}".format(failure_reason),
- terminate_success=list(terminated),
- terminate_failed=terminate_failed)
- module.exit_json(
- msg='Instances terminated',
- terminate_success=list(terminated),
- changed=bool(len(terminated)),
- terminate_failed=[],
- instances=[pretty_instance(i) for i in instances],
- )
-
-
-@AWSRetry.jittered_backoff()
-def change_instance_state(filters, desired_state, ec2=None):
- """Takes STOPPED/RUNNING/TERMINATED"""
- if ec2 is None:
- ec2 = module.client('ec2')
-
- changed = set()
- instances = find_instances(ec2, filters=filters)
- to_change = set(i['InstanceId'] for i in instances if i['State']['Name'].upper() != desired_state)
- unchanged = set()
- failure_reason = ""
-
- for inst in instances:
- try:
- if desired_state == 'TERMINATED':
- if module.check_mode:
- changed.add(inst['InstanceId'])
- continue
-
- # TODO use a client-token to prevent double-sends of these start/stop/terminate commands
- # https://docs.aws.amazon.com/AWSEC2/latest/APIReference/Run_Instance_Idempotency.html
- resp = ec2.terminate_instances(InstanceIds=[inst['InstanceId']])
- [changed.add(i['InstanceId']) for i in resp['TerminatingInstances']]
- if desired_state == 'STOPPED':
- if inst['State']['Name'] in ('stopping', 'stopped'):
- unchanged.add(inst['InstanceId'])
- continue
-
- if module.check_mode:
- changed.add(inst['InstanceId'])
- continue
-
- resp = ec2.stop_instances(InstanceIds=[inst['InstanceId']])
- [changed.add(i['InstanceId']) for i in resp['StoppingInstances']]
- if desired_state == 'RUNNING':
- if module.check_mode:
- changed.add(inst['InstanceId'])
- continue
-
- resp = ec2.start_instances(InstanceIds=[inst['InstanceId']])
- [changed.add(i['InstanceId']) for i in resp['StartingInstances']]
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- try:
- failure_reason = to_native(e.message)
- except AttributeError:
- failure_reason = to_native(e)
-
- if changed:
- await_instances(ids=list(changed) + list(unchanged), state=desired_state)
-
- change_failed = list(to_change - changed)
- instances = find_instances(ec2, ids=list(i['InstanceId'] for i in instances))
- return changed, change_failed, instances, failure_reason
-
-
-def pretty_instance(i):
- instance = camel_dict_to_snake_dict(i, ignore_list=['Tags'])
- instance['tags'] = boto3_tag_list_to_ansible_dict(i['Tags'])
- return instance
-
-
-def determine_iam_role(name_or_arn):
- if re.match(r'^arn:aws:iam::\d+:instance-profile/[\w+=/,.@-]+$', name_or_arn):
- return name_or_arn
- iam = module.client('iam', retry_decorator=AWSRetry.jittered_backoff())
- try:
- role = iam.get_instance_profile(InstanceProfileName=name_or_arn, aws_retry=True)
- return role['InstanceProfile']['Arn']
- except botocore.exceptions.ClientError as e:
- if e.response['Error']['Code'] == 'NoSuchEntity':
- module.fail_json_aws(e, msg="Could not find instance_role {0}".format(name_or_arn))
- module.fail_json_aws(e, msg="An error occurred while searching for instance_role {0}. Please try supplying the full ARN.".format(name_or_arn))
-
-
-def handle_existing(existing_matches, changed, ec2, state):
- if state in ('running', 'started') and [i for i in existing_matches if i['State']['Name'] != 'running']:
- ins_changed, failed, instances, failure_reason = change_instance_state(filters=module.params.get('filters'), desired_state='RUNNING')
- if failed:
- module.fail_json(msg="Couldn't start instances: {0}. Failure reason: {1}".format(instances, failure_reason))
- module.exit_json(
- changed=bool(len(ins_changed)) or changed,
- instances=[pretty_instance(i) for i in instances],
- instance_ids=[i['InstanceId'] for i in instances],
- )
- changes = diff_instance_and_params(existing_matches[0], module.params)
- for c in changes:
- AWSRetry.jittered_backoff()(ec2.modify_instance_attribute)(**c)
- changed |= bool(changes)
- changed |= add_or_update_instance_profile(existing_matches[0], module.params.get('instance_role'))
- changed |= change_network_attachments(existing_matches[0], module.params, ec2)
- altered = find_instances(ec2, ids=[i['InstanceId'] for i in existing_matches])
- module.exit_json(
- changed=bool(len(changes)) or changed,
- instances=[pretty_instance(i) for i in altered],
- instance_ids=[i['InstanceId'] for i in altered],
- changes=changes,
- )
-
-
-def ensure_present(existing_matches, changed, ec2, state):
- if len(existing_matches):
- try:
- handle_existing(existing_matches, changed, ec2, state)
- except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
- module.fail_json_aws(
- e, msg="Failed to handle existing instances {0}".format(', '.join([i['InstanceId'] for i in existing_matches])),
- # instances=[pretty_instance(i) for i in existing_matches],
- # instance_ids=[i['InstanceId'] for i in existing_matches],
- )
- try:
- instance_spec = build_run_instance_spec(module.params)
- # If check mode is enabled,suspend 'ensure function'.
- if module.check_mode:
- module.exit_json(
- changed=True,
- spec=instance_spec,
- )
- instance_response = run_instances(ec2, **instance_spec)
- instances = instance_response['Instances']
- instance_ids = [i['InstanceId'] for i in instances]
-
- for ins in instances:
- changes = diff_instance_and_params(ins, module.params, skip=['UserData', 'EbsOptimized'])
- for c in changes:
- try:
- AWSRetry.jittered_backoff()(ec2.modify_instance_attribute)(**c)
- except botocore.exceptions.ClientError as e:
- module.fail_json_aws(e, msg="Could not apply change {0} to new instance.".format(str(c)))
-
- if not module.params.get('wait'):
- module.exit_json(
- changed=True,
- instance_ids=instance_ids,
- spec=instance_spec,
- )
- await_instances(instance_ids)
- instances = ec2.get_paginator('describe_instances').paginate(
- InstanceIds=instance_ids
- ).search('Reservations[].Instances[]')
-
- module.exit_json(
- changed=True,
- instances=[pretty_instance(i) for i in instances],
- instance_ids=instance_ids,
- spec=instance_spec,
- )
- except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
- module.fail_json_aws(e, msg="Failed to create new EC2 instance")
-
-
-@AWSRetry.jittered_backoff()
-def run_instances(ec2, **instance_spec):
- try:
- return ec2.run_instances(**instance_spec)
- except botocore.exceptions.ClientError as e:
- if e.response['Error']['Code'] == 'InvalidParameterValue' and "Invalid IAM Instance Profile ARN" in e.response['Error']['Message']:
- # If the instance profile has just been created, it takes some time to be visible by ec2
- # So we wait 10 second and retry the run_instances
- time.sleep(10)
- return ec2.run_instances(**instance_spec)
- else:
- raise e
-
-
-def main():
- global module
- argument_spec = dict(
- state=dict(default='present', choices=['present', 'started', 'running', 'stopped', 'restarted', 'rebooted', 'terminated', 'absent']),
- wait=dict(default=True, type='bool'),
- wait_timeout=dict(default=600, type='int'),
- # count=dict(default=1, type='int'),
- image=dict(type='dict'),
- image_id=dict(type='str'),
- instance_type=dict(default='t2.micro', type='str'),
- user_data=dict(type='str'),
- tower_callback=dict(type='dict'),
- ebs_optimized=dict(type='bool'),
- vpc_subnet_id=dict(type='str', aliases=['subnet_id']),
- availability_zone=dict(type='str'),
- security_groups=dict(default=[], type='list'),
- security_group=dict(type='str'),
- instance_role=dict(type='str'),
- name=dict(type='str'),
- tags=dict(type='dict'),
- purge_tags=dict(type='bool', default=False),
- filters=dict(type='dict', default=None),
- launch_template=dict(type='dict'),
- key_name=dict(type='str'),
- cpu_credit_specification=dict(type='str', choices=['standard', 'unlimited']),
- cpu_options=dict(type='dict', options=dict(
- core_count=dict(type='int', required=True),
- threads_per_core=dict(type='int', choices=[1, 2], required=True)
- )),
- tenancy=dict(type='str', choices=['dedicated', 'default']),
- placement_group=dict(type='str'),
- instance_initiated_shutdown_behavior=dict(type='str', choices=['stop', 'terminate']),
- termination_protection=dict(type='bool'),
- detailed_monitoring=dict(type='bool'),
- instance_ids=dict(default=[], type='list'),
- network=dict(default=None, type='dict'),
- volumes=dict(default=None, type='list'),
- )
- # running/present are synonyms
- # as are terminated/absent
- module = AnsibleAWSModule(
- argument_spec=argument_spec,
- mutually_exclusive=[
- ['security_groups', 'security_group'],
- ['availability_zone', 'vpc_subnet_id'],
- ['tower_callback', 'user_data'],
- ['image_id', 'image'],
- ],
- supports_check_mode=True
- )
-
- if module.params.get('network'):
- if module.params.get('network').get('interfaces'):
- if module.params.get('security_group'):
- module.fail_json(msg="Parameter network.interfaces can't be used with security_group")
- if module.params.get('security_groups'):
- module.fail_json(msg="Parameter network.interfaces can't be used with security_groups")
-
- state = module.params.get('state')
- ec2 = module.client('ec2')
- if module.params.get('filters') is None:
- filters = {
- # all states except shutting-down and terminated
- 'instance-state-name': ['pending', 'running', 'stopping', 'stopped']
- }
- if state == 'stopped':
- # only need to change instances that aren't already stopped
- filters['instance-state-name'] = ['stopping', 'pending', 'running']
-
- if isinstance(module.params.get('instance_ids'), string_types):
- filters['instance-id'] = [module.params.get('instance_ids')]
- elif isinstance(module.params.get('instance_ids'), list) and len(module.params.get('instance_ids')):
- filters['instance-id'] = module.params.get('instance_ids')
- else:
- if not module.params.get('vpc_subnet_id'):
- if module.params.get('network'):
- # grab AZ from one of the ENIs
- ints = module.params.get('network').get('interfaces')
- if ints:
- filters['network-interface.network-interface-id'] = []
- for i in ints:
- if isinstance(i, dict):
- i = i['id']
- filters['network-interface.network-interface-id'].append(i)
- else:
- sub = get_default_subnet(ec2, get_default_vpc(ec2), availability_zone=module.params.get('availability_zone'))
- filters['subnet-id'] = sub['SubnetId']
- else:
- filters['subnet-id'] = [module.params.get('vpc_subnet_id')]
-
- if module.params.get('name'):
- filters['tag:Name'] = [module.params.get('name')]
-
- if module.params.get('image_id'):
- filters['image-id'] = [module.params.get('image_id')]
- elif (module.params.get('image') or {}).get('id'):
- filters['image-id'] = [module.params.get('image', {}).get('id')]
-
- module.params['filters'] = filters
-
- if module.params.get('cpu_options') and not module.botocore_at_least('1.10.16'):
- module.fail_json(msg="cpu_options is only supported with botocore >= 1.10.16")
-
- existing_matches = find_instances(ec2, filters=module.params.get('filters'))
- changed = False
-
- if state not in ('terminated', 'absent') and existing_matches:
- for match in existing_matches:
- warn_if_public_ip_assignment_changed(match)
- warn_if_cpu_options_changed(match)
- tags = module.params.get('tags') or {}
- name = module.params.get('name')
- if name:
- tags['Name'] = name
- changed |= manage_tags(match, tags, module.params.get('purge_tags', False), ec2)
-
- if state in ('present', 'running', 'started'):
- ensure_present(existing_matches=existing_matches, changed=changed, ec2=ec2, state=state)
- elif state in ('restarted', 'rebooted', 'stopped', 'absent', 'terminated'):
- if existing_matches:
- ensure_instance_state(state, ec2)
- else:
- module.exit_json(
- msg='No matching instances found',
- changed=False,
- instances=[],
- )
- else:
- module.fail_json(msg="We don't handle the state {0}".format(state))
-
-
-if __name__ == '__main__':
- main()
diff --git a/test/support/integration/plugins/modules/ec2_instance_info.py b/test/support/integration/plugins/modules/ec2_instance_info.py
deleted file mode 100644
index 0836ef3b80..0000000000
--- a/test/support/integration/plugins/modules/ec2_instance_info.py
+++ /dev/null
@@ -1,572 +0,0 @@
-#!/usr/bin/python
-# Copyright: Ansible Project
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = '''
----
-module: ec2_instance_info
-short_description: Gather information about ec2 instances in AWS
-description:
- - Gather information about ec2 instances in AWS
- - This module was called C(ec2_instance_facts) before Ansible 2.9. The usage did not change.
-version_added: "2.4"
-author:
- - Michael Schuett (@michaeljs1990)
- - Rob White (@wimnat)
-requirements: [ "boto3", "botocore" ]
-options:
- instance_ids:
- description:
- - If you specify one or more instance IDs, only instances that have the specified IDs are returned.
- required: false
- version_added: 2.4
- type: list
- filters:
- description:
- - A dict of filters to apply. Each dict item consists of a filter key and a filter value. See
- U(https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html) for possible filters. Filter
- names and values are case sensitive.
- required: false
- default: {}
- type: dict
-
-extends_documentation_fragment:
- - aws
- - ec2
-'''
-
-EXAMPLES = '''
-# Note: These examples do not set authentication details, see the AWS Guide for details.
-
-# Gather information about all instances
-- ec2_instance_info:
-
-# Gather information about all instances in AZ ap-southeast-2a
-- ec2_instance_info:
- filters:
- availability-zone: ap-southeast-2a
-
-# Gather information about a particular instance using ID
-- ec2_instance_info:
- instance_ids:
- - i-12345678
-
-# Gather information about any instance with a tag key Name and value Example
-- ec2_instance_info:
- filters:
- "tag:Name": Example
-
-# Gather information about any instance in states "shutting-down", "stopping", "stopped"
-- ec2_instance_info:
- filters:
- instance-state-name: [ "shutting-down", "stopping", "stopped" ]
-
-'''
-
-RETURN = '''
-instances:
- description: a list of ec2 instances
- returned: always
- type: complex
- contains:
- ami_launch_index:
- description: The AMI launch index, which can be used to find this instance in the launch group.
- returned: always
- type: int
- sample: 0
- architecture:
- description: The architecture of the image
- returned: always
- type: str
- sample: x86_64
- block_device_mappings:
- description: Any block device mapping entries for the instance.
- returned: always
- type: complex
- contains:
- device_name:
- description: The device name exposed to the instance (for example, /dev/sdh or xvdh).
- returned: always
- type: str
- sample: /dev/sdh
- ebs:
- description: Parameters used to automatically set up EBS volumes when the instance is launched.
- returned: always
- type: complex
- contains:
- attach_time:
- description: The time stamp when the attachment initiated.
- returned: always
- type: str
- sample: "2017-03-23T22:51:24+00:00"
- delete_on_termination:
- description: Indicates whether the volume is deleted on instance termination.
- returned: always
- type: bool
- sample: true
- status:
- description: The attachment state.
- returned: always
- type: str
- sample: attached
- volume_id:
- description: The ID of the EBS volume
- returned: always
- type: str
- sample: vol-12345678
- cpu_options:
- description: The CPU options set for the instance.
- returned: always if botocore version >= 1.10.16
- type: complex
- contains:
- core_count:
- description: The number of CPU cores for the instance.
- returned: always
- type: int
- sample: 1
- threads_per_core:
- description: The number of threads per CPU core. On supported instance, a value of 1 means Intel Hyper-Threading Technology is disabled.
- returned: always
- type: int
- sample: 1
- client_token:
- description: The idempotency token you provided when you launched the instance, if applicable.
- returned: always
- type: str
- sample: mytoken
- ebs_optimized:
- description: Indicates whether the instance is optimized for EBS I/O.
- returned: always
- type: bool
- sample: false
- hypervisor:
- description: The hypervisor type of the instance.
- returned: always
- type: str
- sample: xen
- iam_instance_profile:
- description: The IAM instance profile associated with the instance, if applicable.
- returned: always
- type: complex
- contains:
- arn:
- description: The Amazon Resource Name (ARN) of the instance profile.
- returned: always
- type: str
- sample: "arn:aws:iam::000012345678:instance-profile/myprofile"
- id:
- description: The ID of the instance profile
- returned: always
- type: str
- sample: JFJ397FDG400FG9FD1N
- image_id:
- description: The ID of the AMI used to launch the instance.
- returned: always
- type: str
- sample: ami-0011223344
- instance_id:
- description: The ID of the instance.
- returned: always
- type: str
- sample: i-012345678
- instance_type:
- description: The instance type size of the running instance.
- returned: always
- type: str
- sample: t2.micro
- key_name:
- description: The name of the key pair, if this instance was launched with an associated key pair.
- returned: always
- type: str
- sample: my-key
- launch_time:
- description: The time the instance was launched.
- returned: always
- type: str
- sample: "2017-03-23T22:51:24+00:00"
- monitoring:
- description: The monitoring for the instance.
- returned: always
- type: complex
- contains:
- state:
- description: Indicates whether detailed monitoring is enabled. Otherwise, basic monitoring is enabled.
- returned: always
- type: str
- sample: disabled
- network_interfaces:
- description: One or more network interfaces for the instance.
- returned: always
- type: complex
- contains:
- association:
- description: The association information for an Elastic IPv4 associated with the network interface.
- returned: always
- type: complex
- contains:
- ip_owner_id:
- description: The ID of the owner of the Elastic IP address.
- returned: always
- type: str
- sample: amazon
- public_dns_name:
- description: The public DNS name.
- returned: always
- type: str
- sample: ""
- public_ip:
- description: The public IP address or Elastic IP address bound to the network interface.
- returned: always
- type: str
- sample: 1.2.3.4
- attachment:
- description: The network interface attachment.
- returned: always
- type: complex
- contains:
- attach_time:
- description: The time stamp when the attachment initiated.
- returned: always
- type: str
- sample: "2017-03-23T22:51:24+00:00"
- attachment_id:
- description: The ID of the network interface attachment.
- returned: always
- type: str
- sample: eni-attach-3aff3f
- delete_on_termination:
- description: Indicates whether the network interface is deleted when the instance is terminated.
- returned: always
- type: bool
- sample: true
- device_index:
- description: The index of the device on the instance for the network interface attachment.
- returned: always
- type: int
- sample: 0
- status:
- description: The attachment state.
- returned: always
- type: str
- sample: attached
- description:
- description: The description.
- returned: always
- type: str
- sample: My interface
- groups:
- description: One or more security groups.
- returned: always
- type: list
- elements: dict
- contains:
- group_id:
- description: The ID of the security group.
- returned: always
- type: str
- sample: sg-abcdef12
- group_name:
- description: The name of the security group.
- returned: always
- type: str
- sample: mygroup
- ipv6_addresses:
- description: One or more IPv6 addresses associated with the network interface.
- returned: always
- type: list
- elements: dict
- contains:
- ipv6_address:
- description: The IPv6 address.
- returned: always
- type: str
- sample: "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
- mac_address:
- description: The MAC address.
- returned: always
- type: str
- sample: "00:11:22:33:44:55"
- network_interface_id:
- description: The ID of the network interface.
- returned: always
- type: str
- sample: eni-01234567
- owner_id:
- description: The AWS account ID of the owner of the network interface.
- returned: always
- type: str
- sample: 01234567890
- private_ip_address:
- description: The IPv4 address of the network interface within the subnet.
- returned: always
- type: str
- sample: 10.0.0.1
- private_ip_addresses:
- description: The private IPv4 addresses associated with the network interface.
- returned: always
- type: list
- elements: dict
- contains:
- association:
- description: The association information for an Elastic IP address (IPv4) associated with the network interface.
- returned: always
- type: complex
- contains:
- ip_owner_id:
- description: The ID of the owner of the Elastic IP address.
- returned: always
- type: str
- sample: amazon
- public_dns_name:
- description: The public DNS name.
- returned: always
- type: str
- sample: ""
- public_ip:
- description: The public IP address or Elastic IP address bound to the network interface.
- returned: always
- type: str
- sample: 1.2.3.4
- primary:
- description: Indicates whether this IPv4 address is the primary private IP address of the network interface.
- returned: always
- type: bool
- sample: true
- private_ip_address:
- description: The private IPv4 address of the network interface.
- returned: always
- type: str
- sample: 10.0.0.1
- source_dest_check:
- description: Indicates whether source/destination checking is enabled.
- returned: always
- type: bool
- sample: true
- status:
- description: The status of the network interface.
- returned: always
- type: str
- sample: in-use
- subnet_id:
- description: The ID of the subnet for the network interface.
- returned: always
- type: str
- sample: subnet-0123456
- vpc_id:
- description: The ID of the VPC for the network interface.
- returned: always
- type: str
- sample: vpc-0123456
- placement:
- description: The location where the instance launched, if applicable.
- returned: always
- type: complex
- contains:
- availability_zone:
- description: The Availability Zone of the instance.
- returned: always
- type: str
- sample: ap-southeast-2a
- group_name:
- description: The name of the placement group the instance is in (for cluster compute instances).
- returned: always
- type: str
- sample: ""
- tenancy:
- description: The tenancy of the instance (if the instance is running in a VPC).
- returned: always
- type: str
- sample: default
- private_dns_name:
- description: The private DNS name.
- returned: always
- type: str
- sample: ip-10-0-0-1.ap-southeast-2.compute.internal
- private_ip_address:
- description: The IPv4 address of the network interface within the subnet.
- returned: always
- type: str
- sample: 10.0.0.1
- product_codes:
- description: One or more product codes.
- returned: always
- type: list
- elements: dict
- contains:
- product_code_id:
- description: The product code.
- returned: always
- type: str
- sample: aw0evgkw8ef3n2498gndfgasdfsd5cce
- product_code_type:
- description: The type of product code.
- returned: always
- type: str
- sample: marketplace
- public_dns_name:
- description: The public DNS name assigned to the instance.
- returned: always
- type: str
- sample:
- public_ip_address:
- description: The public IPv4 address assigned to the instance
- returned: always
- type: str
- sample: 52.0.0.1
- root_device_name:
- description: The device name of the root device
- returned: always
- type: str
- sample: /dev/sda1
- root_device_type:
- description: The type of root device used by the AMI.
- returned: always
- type: str
- sample: ebs
- security_groups:
- description: One or more security groups for the instance.
- returned: always
- type: list
- elements: dict
- contains:
- group_id:
- description: The ID of the security group.
- returned: always
- type: str
- sample: sg-0123456
- group_name:
- description: The name of the security group.
- returned: always
- type: str
- sample: my-security-group
- source_dest_check:
- description: Indicates whether source/destination checking is enabled.
- returned: always
- type: bool
- sample: true
- state:
- description: The current state of the instance.
- returned: always
- type: complex
- contains:
- code:
- description: The low byte represents the state.
- returned: always
- type: int
- sample: 16
- name:
- description: The name of the state.
- returned: always
- type: str
- sample: running
- state_transition_reason:
- description: The reason for the most recent state transition.
- returned: always
- type: str
- sample:
- subnet_id:
- description: The ID of the subnet in which the instance is running.
- returned: always
- type: str
- sample: subnet-00abcdef
- tags:
- description: Any tags assigned to the instance.
- returned: always
- type: dict
- sample:
- virtualization_type:
- description: The type of virtualization of the AMI.
- returned: always
- type: str
- sample: hvm
- vpc_id:
- description: The ID of the VPC the instance is in.
- returned: always
- type: dict
- sample: vpc-0011223344
-'''
-
-import traceback
-
-try:
- import boto3
- from botocore.exceptions import ClientError
- HAS_BOTO3 = True
-except ImportError:
- HAS_BOTO3 = False
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.ec2 import (ansible_dict_to_boto3_filter_list,
- boto3_conn, boto3_tag_list_to_ansible_dict, camel_dict_to_snake_dict,
- ec2_argument_spec, get_aws_connection_info)
-
-
-def list_ec2_instances(connection, module):
-
- instance_ids = module.params.get("instance_ids")
- filters = ansible_dict_to_boto3_filter_list(module.params.get("filters"))
-
- try:
- reservations_paginator = connection.get_paginator('describe_instances')
- reservations = reservations_paginator.paginate(InstanceIds=instance_ids, Filters=filters).build_full_result()
- except ClientError as e:
- module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
-
- # Get instances from reservations
- instances = []
- for reservation in reservations['Reservations']:
- instances = instances + reservation['Instances']
-
- # Turn the boto3 result in to ansible_friendly_snaked_names
- snaked_instances = [camel_dict_to_snake_dict(instance) for instance in instances]
-
- # Turn the boto3 result in to ansible friendly tag dictionary
- for instance in snaked_instances:
- instance['tags'] = boto3_tag_list_to_ansible_dict(instance.get('tags', []), 'key', 'value')
-
- module.exit_json(instances=snaked_instances)
-
-
-def main():
-
- argument_spec = ec2_argument_spec()
- argument_spec.update(
- dict(
- instance_ids=dict(default=[], type='list'),
- filters=dict(default={}, type='dict')
- )
- )
-
- module = AnsibleModule(argument_spec=argument_spec,
- mutually_exclusive=[
- ['instance_ids', 'filters']
- ],
- supports_check_mode=True
- )
- if module._name == 'ec2_instance_facts':
- module.deprecate("The 'ec2_instance_facts' module has been renamed to 'ec2_instance_info'",
- version='2.13', collection_name='ansible.builtin')
-
- if not HAS_BOTO3:
- module.fail_json(msg='boto3 required for this module')
-
- region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
-
- if region:
- connection = boto3_conn(module, conn_type='client', resource='ec2', region=region, endpoint=ec2_url, **aws_connect_params)
- else:
- module.fail_json(msg="region must be specified")
-
- list_ec2_instances(connection, module)
-
-
-if __name__ == '__main__':
- main()
diff --git a/test/support/integration/plugins/modules/ec2_key.py b/test/support/integration/plugins/modules/ec2_key.py
deleted file mode 100644
index de67af8bc0..0000000000
--- a/test/support/integration/plugins/modules/ec2_key.py
+++ /dev/null
@@ -1,271 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-# Copyright: Ansible Project
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['stableinterface'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
----
-module: ec2_key
-version_added: "1.5"
-short_description: create or delete an ec2 key pair
-description:
- - create or delete an ec2 key pair.
-options:
- name:
- description:
- - Name of the key pair.
- required: true
- type: str
- key_material:
- description:
- - Public key material.
- required: false
- type: str
- force:
- description:
- - Force overwrite of already existing key pair if key has changed.
- required: false
- default: true
- type: bool
- version_added: "2.3"
- state:
- description:
- - create or delete keypair
- required: false
- choices: [ present, absent ]
- default: 'present'
- type: str
- wait:
- description:
- - This option has no effect since version 2.5 and will be removed in 2.14.
- version_added: "1.6"
- type: bool
- wait_timeout:
- description:
- - This option has no effect since version 2.5 and will be removed in 2.14.
- version_added: "1.6"
- type: int
- required: false
-
-extends_documentation_fragment:
- - aws
- - ec2
-requirements: [ boto3 ]
-author:
- - "Vincent Viallet (@zbal)"
- - "Prasad Katti (@prasadkatti)"
-'''
-
-EXAMPLES = '''
-# Note: These examples do not set authentication details, see the AWS Guide for details.
-
-- name: create a new ec2 key pair, returns generated private key
- ec2_key:
- name: my_keypair
-
-- name: create key pair using provided key_material
- ec2_key:
- name: my_keypair
- key_material: 'ssh-rsa AAAAxyz...== me@example.com'
-
-- name: create key pair using key_material obtained using 'file' lookup plugin
- ec2_key:
- name: my_keypair
- key_material: "{{ lookup('file', '/path/to/public_key/id_rsa.pub') }}"
-
-# try creating a key pair with the name of an already existing keypair
-# but don't overwrite it even if the key is different (force=false)
-- name: try creating a key pair with name of an already existing keypair
- ec2_key:
- name: my_existing_keypair
- key_material: 'ssh-rsa AAAAxyz...== me@example.com'
- force: false
-
-- name: remove key pair by name
- ec2_key:
- name: my_keypair
- state: absent
-'''
-
-RETURN = '''
-changed:
- description: whether a keypair was created/deleted
- returned: always
- type: bool
- sample: true
-msg:
- description: short message describing the action taken
- returned: always
- type: str
- sample: key pair created
-key:
- description: details of the keypair (this is set to null when state is absent)
- returned: always
- type: complex
- contains:
- fingerprint:
- description: fingerprint of the key
- returned: when state is present
- type: str
- sample: 'b0:22:49:61:d9:44:9d:0c:7e:ac:8a:32:93:21:6c:e8:fb:59:62:43'
- name:
- description: name of the keypair
- returned: when state is present
- type: str
- sample: my_keypair
- private_key:
- description: private key of a newly created keypair
- returned: when a new keypair is created by AWS (key_material is not provided)
- type: str
- sample: '-----BEGIN RSA PRIVATE KEY-----
- MIIEowIBAAKC...
- -----END RSA PRIVATE KEY-----'
-'''
-
-import uuid
-
-from ansible.module_utils.aws.core import AnsibleAWSModule
-from ansible.module_utils._text import to_bytes
-
-try:
- from botocore.exceptions import ClientError
-except ImportError:
- pass # caught by AnsibleAWSModule
-
-
-def extract_key_data(key):
-
- data = {
- 'name': key['KeyName'],
- 'fingerprint': key['KeyFingerprint']
- }
- if 'KeyMaterial' in key:
- data['private_key'] = key['KeyMaterial']
- return data
-
-
-def get_key_fingerprint(module, ec2_client, key_material):
- '''
- EC2's fingerprints are non-trivial to generate, so push this key
- to a temporary name and make ec2 calculate the fingerprint for us.
- http://blog.jbrowne.com/?p=23
- https://forums.aws.amazon.com/thread.jspa?messageID=352828
- '''
-
- # find an unused name
- name_in_use = True
- while name_in_use:
- random_name = "ansible-" + str(uuid.uuid4())
- name_in_use = find_key_pair(module, ec2_client, random_name)
-
- temp_key = import_key_pair(module, ec2_client, random_name, key_material)
- delete_key_pair(module, ec2_client, random_name, finish_task=False)
- return temp_key['KeyFingerprint']
-
-
-def find_key_pair(module, ec2_client, name):
-
- try:
- key = ec2_client.describe_key_pairs(KeyNames=[name])['KeyPairs'][0]
- except ClientError as err:
- if err.response['Error']['Code'] == "InvalidKeyPair.NotFound":
- return None
- module.fail_json_aws(err, msg="error finding keypair")
- except IndexError:
- key = None
- return key
-
-
-def create_key_pair(module, ec2_client, name, key_material, force):
-
- key = find_key_pair(module, ec2_client, name)
- if key:
- if key_material and force:
- if not module.check_mode:
- new_fingerprint = get_key_fingerprint(module, ec2_client, key_material)
- if key['KeyFingerprint'] != new_fingerprint:
- delete_key_pair(module, ec2_client, name, finish_task=False)
- key = import_key_pair(module, ec2_client, name, key_material)
- key_data = extract_key_data(key)
- module.exit_json(changed=True, key=key_data, msg="key pair updated")
- else:
- # Assume a change will be made in check mode since a comparison can't be done
- module.exit_json(changed=True, key=extract_key_data(key), msg="key pair updated")
- key_data = extract_key_data(key)
- module.exit_json(changed=False, key=key_data, msg="key pair already exists")
- else:
- # key doesn't exist, create it now
- key_data = None
- if not module.check_mode:
- if key_material:
- key = import_key_pair(module, ec2_client, name, key_material)
- else:
- try:
- key = ec2_client.create_key_pair(KeyName=name)
- except ClientError as err:
- module.fail_json_aws(err, msg="error creating key")
- key_data = extract_key_data(key)
- module.exit_json(changed=True, key=key_data, msg="key pair created")
-
-
-def import_key_pair(module, ec2_client, name, key_material):
-
- try:
- key = ec2_client.import_key_pair(KeyName=name, PublicKeyMaterial=to_bytes(key_material))
- except ClientError as err:
- module.fail_json_aws(err, msg="error importing key")
- return key
-
-
-def delete_key_pair(module, ec2_client, name, finish_task=True):
-
- key = find_key_pair(module, ec2_client, name)
- if key:
- if not module.check_mode:
- try:
- ec2_client.delete_key_pair(KeyName=name)
- except ClientError as err:
- module.fail_json_aws(err, msg="error deleting key")
- if not finish_task:
- return
- module.exit_json(changed=True, key=None, msg="key deleted")
- module.exit_json(key=None, msg="key did not exist")
-
-
-def main():
-
- argument_spec = dict(
- name=dict(required=True),
- key_material=dict(),
- force=dict(type='bool', default=True),
- state=dict(default='present', choices=['present', 'absent']),
- wait=dict(type='bool', removed_in_version='2.14'),
- wait_timeout=dict(type='int', removed_in_version='2.14')
- )
-
- module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True)
-
- ec2_client = module.client('ec2')
-
- name = module.params['name']
- state = module.params.get('state')
- key_material = module.params.get('key_material')
- force = module.params.get('force')
-
- if state == 'absent':
- delete_key_pair(module, ec2_client, name)
- elif state == 'present':
- create_key_pair(module, ec2_client, name, key_material, force)
-
-
-if __name__ == '__main__':
- main()
diff --git a/test/support/integration/plugins/modules/ec2_vpc_igw.py b/test/support/integration/plugins/modules/ec2_vpc_igw.py
deleted file mode 100644
index 5198527af7..0000000000
--- a/test/support/integration/plugins/modules/ec2_vpc_igw.py
+++ /dev/null
@@ -1,283 +0,0 @@
-#!/usr/bin/python
-# Copyright: Ansible Project
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['stableinterface'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
----
-module: ec2_vpc_igw
-short_description: Manage an AWS VPC Internet gateway
-description:
- - Manage an AWS VPC Internet gateway
-version_added: "2.0"
-author: Robert Estelle (@erydo)
-options:
- vpc_id:
- description:
- - The VPC ID for the VPC in which to manage the Internet Gateway.
- required: true
- type: str
- tags:
- description:
- - "A dict of tags to apply to the internet gateway. Any tags currently applied to the internet gateway and not present here will be removed."
- aliases: [ 'resource_tags' ]
- version_added: "2.4"
- type: dict
- state:
- description:
- - Create or terminate the IGW
- default: present
- choices: [ 'present', 'absent' ]
- type: str
-extends_documentation_fragment:
- - aws
- - ec2
-requirements:
- - botocore
- - boto3
-'''
-
-EXAMPLES = '''
-# Note: These examples do not set authentication details, see the AWS Guide for details.
-
-# Ensure that the VPC has an Internet Gateway.
-# The Internet Gateway ID is can be accessed via {{igw.gateway_id}} for use in setting up NATs etc.
-ec2_vpc_igw:
- vpc_id: vpc-abcdefgh
- state: present
-register: igw
-
-'''
-
-RETURN = '''
-changed:
- description: If any changes have been made to the Internet Gateway.
- type: bool
- returned: always
- sample:
- changed: false
-gateway_id:
- description: The unique identifier for the Internet Gateway.
- type: str
- returned: I(state=present)
- sample:
- gateway_id: "igw-XXXXXXXX"
-tags:
- description: The tags associated the Internet Gateway.
- type: dict
- returned: I(state=present)
- sample:
- tags:
- "Ansible": "Test"
-vpc_id:
- description: The VPC ID associated with the Internet Gateway.
- type: str
- returned: I(state=present)
- sample:
- vpc_id: "vpc-XXXXXXXX"
-'''
-
-try:
- import botocore
-except ImportError:
- pass # caught by AnsibleAWSModule
-
-from ansible.module_utils.aws.core import AnsibleAWSModule
-from ansible.module_utils.aws.waiters import get_waiter
-from ansible.module_utils.ec2 import (
- AWSRetry,
- camel_dict_to_snake_dict,
- boto3_tag_list_to_ansible_dict,
- ansible_dict_to_boto3_filter_list,
- ansible_dict_to_boto3_tag_list,
- compare_aws_tags
-)
-from ansible.module_utils.six import string_types
-
-
-class AnsibleEc2Igw(object):
-
- def __init__(self, module, results):
- self._module = module
- self._results = results
- self._connection = self._module.client('ec2')
- self._check_mode = self._module.check_mode
-
- def process(self):
- vpc_id = self._module.params.get('vpc_id')
- state = self._module.params.get('state', 'present')
- tags = self._module.params.get('tags')
-
- if state == 'present':
- self.ensure_igw_present(vpc_id, tags)
- elif state == 'absent':
- self.ensure_igw_absent(vpc_id)
-
- def get_matching_igw(self, vpc_id):
- filters = ansible_dict_to_boto3_filter_list({'attachment.vpc-id': vpc_id})
- igws = []
- try:
- response = self._connection.describe_internet_gateways(Filters=filters)
- igws = response.get('InternetGateways', [])
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- self._module.fail_json_aws(e)
-
- igw = None
- if len(igws) > 1:
- self._module.fail_json(
- msg='EC2 returned more than one Internet Gateway for VPC {0}, aborting'.format(vpc_id))
- elif igws:
- igw = camel_dict_to_snake_dict(igws[0])
-
- return igw
-
- def check_input_tags(self, tags):
- nonstring_tags = [k for k, v in tags.items() if not isinstance(v, string_types)]
- if nonstring_tags:
- self._module.fail_json(msg='One or more tags contain non-string values: {0}'.format(nonstring_tags))
-
- def ensure_tags(self, igw_id, tags, add_only):
- final_tags = []
-
- filters = ansible_dict_to_boto3_filter_list({'resource-id': igw_id, 'resource-type': 'internet-gateway'})
- cur_tags = None
- try:
- cur_tags = self._connection.describe_tags(Filters=filters)
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- self._module.fail_json_aws(e, msg="Couldn't describe tags")
-
- purge_tags = bool(not add_only)
- to_update, to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(cur_tags.get('Tags')), tags, purge_tags)
- final_tags = boto3_tag_list_to_ansible_dict(cur_tags.get('Tags'))
-
- if to_update:
- try:
- if self._check_mode:
- # update tags
- final_tags.update(to_update)
- else:
- AWSRetry.exponential_backoff()(self._connection.create_tags)(
- Resources=[igw_id],
- Tags=ansible_dict_to_boto3_tag_list(to_update)
- )
-
- self._results['changed'] = True
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- self._module.fail_json_aws(e, msg="Couldn't create tags")
-
- if to_delete:
- try:
- if self._check_mode:
- # update tags
- for key in to_delete:
- del final_tags[key]
- else:
- tags_list = []
- for key in to_delete:
- tags_list.append({'Key': key})
-
- AWSRetry.exponential_backoff()(self._connection.delete_tags)(Resources=[igw_id], Tags=tags_list)
-
- self._results['changed'] = True
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- self._module.fail_json_aws(e, msg="Couldn't delete tags")
-
- if not self._check_mode and (to_update or to_delete):
- try:
- response = self._connection.describe_tags(Filters=filters)
- final_tags = boto3_tag_list_to_ansible_dict(response.get('Tags'))
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- self._module.fail_json_aws(e, msg="Couldn't describe tags")
-
- return final_tags
-
- @staticmethod
- def get_igw_info(igw):
- return {
- 'gateway_id': igw['internet_gateway_id'],
- 'tags': igw['tags'],
- 'vpc_id': igw['vpc_id']
- }
-
- def ensure_igw_absent(self, vpc_id):
- igw = self.get_matching_igw(vpc_id)
- if igw is None:
- return self._results
-
- if self._check_mode:
- self._results['changed'] = True
- return self._results
-
- try:
- self._results['changed'] = True
- self._connection.detach_internet_gateway(InternetGatewayId=igw['internet_gateway_id'], VpcId=vpc_id)
- self._connection.delete_internet_gateway(InternetGatewayId=igw['internet_gateway_id'])
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- self._module.fail_json_aws(e, msg="Unable to delete Internet Gateway")
-
- return self._results
-
- def ensure_igw_present(self, vpc_id, tags):
- self.check_input_tags(tags)
-
- igw = self.get_matching_igw(vpc_id)
-
- if igw is None:
- if self._check_mode:
- self._results['changed'] = True
- self._results['gateway_id'] = None
- return self._results
-
- try:
- response = self._connection.create_internet_gateway()
-
- # Ensure the gateway exists before trying to attach it or add tags
- waiter = get_waiter(self._connection, 'internet_gateway_exists')
- waiter.wait(InternetGatewayIds=[response['InternetGateway']['InternetGatewayId']])
-
- igw = camel_dict_to_snake_dict(response['InternetGateway'])
- self._connection.attach_internet_gateway(InternetGatewayId=igw['internet_gateway_id'], VpcId=vpc_id)
- self._results['changed'] = True
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- self._module.fail_json_aws(e, msg='Unable to create Internet Gateway')
-
- igw['vpc_id'] = vpc_id
-
- igw['tags'] = self.ensure_tags(igw_id=igw['internet_gateway_id'], tags=tags, add_only=False)
-
- igw_info = self.get_igw_info(igw)
- self._results.update(igw_info)
-
- return self._results
-
-
-def main():
- argument_spec = dict(
- vpc_id=dict(required=True),
- state=dict(default='present', choices=['present', 'absent']),
- tags=dict(default=dict(), required=False, type='dict', aliases=['resource_tags'])
- )
-
- module = AnsibleAWSModule(
- argument_spec=argument_spec,
- supports_check_mode=True,
- )
- results = dict(
- changed=False
- )
- igw_manager = AnsibleEc2Igw(module=module, results=results)
- igw_manager.process()
-
- module.exit_json(**results)
-
-
-if __name__ == '__main__':
- main()
diff --git a/test/support/integration/plugins/modules/ec2_vpc_route_table.py b/test/support/integration/plugins/modules/ec2_vpc_route_table.py
deleted file mode 100644
index 96c9b2d04d..0000000000
--- a/test/support/integration/plugins/modules/ec2_vpc_route_table.py
+++ /dev/null
@@ -1,750 +0,0 @@
-#!/usr/bin/python
-#
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['stableinterface'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
----
-module: ec2_vpc_route_table
-short_description: Manage route tables for AWS virtual private clouds
-description:
- - Manage route tables for AWS virtual private clouds
-version_added: "2.0"
-author:
-- Robert Estelle (@erydo)
-- Rob White (@wimnat)
-- Will Thames (@willthames)
-options:
- lookup:
- description: Look up route table by either tags or by route table ID. Non-unique tag lookup will fail.
- If no tags are specified then no lookup for an existing route table is performed and a new
- route table will be created. To change tags of a route table you must look up by id.
- default: tag
- choices: [ 'tag', 'id' ]
- type: str
- propagating_vgw_ids:
- description: Enable route propagation from virtual gateways specified by ID.
- type: list
- elements: str
- purge_routes:
- version_added: "2.3"
- description: Purge existing routes that are not found in routes.
- type: bool
- default: 'yes'
- purge_subnets:
- version_added: "2.3"
- description: Purge existing subnets that are not found in subnets. Ignored unless the subnets option is supplied.
- default: 'true'
- type: bool
- purge_tags:
- version_added: "2.5"
- description: Purge existing tags that are not found in route table.
- type: bool
- default: 'no'
- route_table_id:
- description:
- - The ID of the route table to update or delete.
- - Required when I(lookup=id).
- type: str
- routes:
- description: List of routes in the route table.
- Routes are specified as dicts containing the keys 'dest' and one of 'gateway_id',
- 'instance_id', 'network_interface_id', or 'vpc_peering_connection_id'.
- If 'gateway_id' is specified, you can refer to the VPC's IGW by using the value 'igw'.
- Routes are required for present states.
- type: list
- elements: dict
- state:
- description: Create or destroy the VPC route table.
- default: present
- choices: [ 'present', 'absent' ]
- type: str
- subnets:
- description: An array of subnets to add to this route table. Subnets may be specified
- by either subnet ID, Name tag, or by a CIDR such as '10.0.0.0/24'.
- type: list
- elements: str
- tags:
- description: >
- A dictionary of resource tags of the form: C({ tag1: value1, tag2: value2 }). Tags are
- used to uniquely identify route tables within a VPC when the route_table_id is not supplied.
- aliases: [ "resource_tags" ]
- type: dict
- vpc_id:
- description:
- - VPC ID of the VPC in which to create the route table.
- - Required when I(state=present) or I(lookup=tag).
- type: str
-extends_documentation_fragment:
- - aws
- - ec2
-'''
-
-EXAMPLES = '''
-# Note: These examples do not set authentication details, see the AWS Guide for details.
-
-# Basic creation example:
-- name: Set up public subnet route table
- ec2_vpc_route_table:
- vpc_id: vpc-1245678
- region: us-west-1
- tags:
- Name: Public
- subnets:
- - "{{ jumpbox_subnet.subnet.id }}"
- - "{{ frontend_subnet.subnet.id }}"
- - "{{ vpn_subnet.subnet_id }}"
- routes:
- - dest: 0.0.0.0/0
- gateway_id: "{{ igw.gateway_id }}"
- register: public_route_table
-
-- name: Set up NAT-protected route table
- ec2_vpc_route_table:
- vpc_id: vpc-1245678
- region: us-west-1
- tags:
- Name: Internal
- subnets:
- - "{{ application_subnet.subnet.id }}"
- - 'Database Subnet'
- - '10.0.0.0/8'
- routes:
- - dest: 0.0.0.0/0
- instance_id: "{{ nat.instance_id }}"
- register: nat_route_table
-
-- name: delete route table
- ec2_vpc_route_table:
- vpc_id: vpc-1245678
- region: us-west-1
- route_table_id: "{{ route_table.id }}"
- lookup: id
- state: absent
-'''
-
-RETURN = '''
-route_table:
- description: Route Table result
- returned: always
- type: complex
- contains:
- associations:
- description: List of subnets associated with the route table
- returned: always
- type: complex
- contains:
- main:
- description: Whether this is the main route table
- returned: always
- type: bool
- sample: false
- route_table_association_id:
- description: ID of association between route table and subnet
- returned: always
- type: str
- sample: rtbassoc-ab47cfc3
- route_table_id:
- description: ID of the route table
- returned: always
- type: str
- sample: rtb-bf779ed7
- subnet_id:
- description: ID of the subnet
- returned: always
- type: str
- sample: subnet-82055af9
- id:
- description: ID of the route table (same as route_table_id for backwards compatibility)
- returned: always
- type: str
- sample: rtb-bf779ed7
- propagating_vgws:
- description: List of Virtual Private Gateways propagating routes
- returned: always
- type: list
- sample: []
- route_table_id:
- description: ID of the route table
- returned: always
- type: str
- sample: rtb-bf779ed7
- routes:
- description: List of routes in the route table
- returned: always
- type: complex
- contains:
- destination_cidr_block:
- description: CIDR block of destination
- returned: always
- type: str
- sample: 10.228.228.0/22
- gateway_id:
- description: ID of the gateway
- returned: when gateway is local or internet gateway
- type: str
- sample: local
- instance_id:
- description: ID of a NAT instance
- returned: when the route is via an EC2 instance
- type: str
- sample: i-abcd123456789
- instance_owner_id:
- description: AWS account owning the NAT instance
- returned: when the route is via an EC2 instance
- type: str
- sample: 123456789012
- nat_gateway_id:
- description: ID of the NAT gateway
- returned: when the route is via a NAT gateway
- type: str
- sample: local
- origin:
- description: mechanism through which the route is in the table
- returned: always
- type: str
- sample: CreateRouteTable
- state:
- description: state of the route
- returned: always
- type: str
- sample: active
- tags:
- description: Tags applied to the route table
- returned: always
- type: dict
- sample:
- Name: Public route table
- Public: 'true'
- vpc_id:
- description: ID for the VPC in which the route lives
- returned: always
- type: str
- sample: vpc-6e2d2407
-'''
-
-import re
-from time import sleep
-from ansible.module_utils.aws.core import AnsibleAWSModule
-from ansible.module_utils.aws.waiters import get_waiter
-from ansible.module_utils.ec2 import ansible_dict_to_boto3_filter_list
-from ansible.module_utils.ec2 import camel_dict_to_snake_dict, snake_dict_to_camel_dict
-from ansible.module_utils.ec2 import ansible_dict_to_boto3_tag_list, boto3_tag_list_to_ansible_dict
-from ansible.module_utils.ec2 import compare_aws_tags, AWSRetry
-
-
-try:
- import botocore
-except ImportError:
- pass # caught by AnsibleAWSModule
-
-
-CIDR_RE = re.compile(r'^(\d{1,3}\.){3}\d{1,3}/\d{1,2}$')
-SUBNET_RE = re.compile(r'^subnet-[A-z0-9]+$')
-ROUTE_TABLE_RE = re.compile(r'^rtb-[A-z0-9]+$')
-
-
-@AWSRetry.exponential_backoff()
-def describe_subnets_with_backoff(connection, **params):
- return connection.describe_subnets(**params)['Subnets']
-
-
-def find_subnets(connection, module, vpc_id, identified_subnets):
- """
- Finds a list of subnets, each identified either by a raw ID, a unique
- 'Name' tag, or a CIDR such as 10.0.0.0/8.
-
- Note that this function is duplicated in other ec2 modules, and should
- potentially be moved into a shared module_utils
- """
- subnet_ids = []
- subnet_names = []
- subnet_cidrs = []
- for subnet in (identified_subnets or []):
- if re.match(SUBNET_RE, subnet):
- subnet_ids.append(subnet)
- elif re.match(CIDR_RE, subnet):
- subnet_cidrs.append(subnet)
- else:
- subnet_names.append(subnet)
-
- subnets_by_id = []
- if subnet_ids:
- filters = ansible_dict_to_boto3_filter_list({'vpc-id': vpc_id})
- try:
- subnets_by_id = describe_subnets_with_backoff(connection, SubnetIds=subnet_ids, Filters=filters)
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Couldn't find subnet with id %s" % subnet_ids)
-
- subnets_by_cidr = []
- if subnet_cidrs:
- filters = ansible_dict_to_boto3_filter_list({'vpc-id': vpc_id, 'cidr': subnet_cidrs})
- try:
- subnets_by_cidr = describe_subnets_with_backoff(connection, Filters=filters)
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Couldn't find subnet with cidr %s" % subnet_cidrs)
-
- subnets_by_name = []
- if subnet_names:
- filters = ansible_dict_to_boto3_filter_list({'vpc-id': vpc_id, 'tag:Name': subnet_names})
- try:
- subnets_by_name = describe_subnets_with_backoff(connection, Filters=filters)
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Couldn't find subnet with names %s" % subnet_names)
-
- for name in subnet_names:
- matching_count = len([1 for s in subnets_by_name for t in s.get('Tags', []) if t['Key'] == 'Name' and t['Value'] == name])
- if matching_count == 0:
- module.fail_json(msg='Subnet named "{0}" does not exist'.format(name))
- elif matching_count > 1:
- module.fail_json(msg='Multiple subnets named "{0}"'.format(name))
-
- return subnets_by_id + subnets_by_cidr + subnets_by_name
-
-
-def find_igw(connection, module, vpc_id):
- """
- Finds the Internet gateway for the given VPC ID.
- """
- filters = ansible_dict_to_boto3_filter_list({'attachment.vpc-id': vpc_id})
- try:
- igw = connection.describe_internet_gateways(Filters=filters)['InternetGateways']
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg='No IGW found for VPC {0}'.format(vpc_id))
- if len(igw) == 1:
- return igw[0]['InternetGatewayId']
- elif len(igw) == 0:
- module.fail_json(msg='No IGWs found for VPC {0}'.format(vpc_id))
- else:
- module.fail_json(msg='Multiple IGWs found for VPC {0}'.format(vpc_id))
-
-
-@AWSRetry.exponential_backoff()
-def describe_tags_with_backoff(connection, resource_id):
- filters = ansible_dict_to_boto3_filter_list({'resource-id': resource_id})
- paginator = connection.get_paginator('describe_tags')
- tags = paginator.paginate(Filters=filters).build_full_result()['Tags']
- return boto3_tag_list_to_ansible_dict(tags)
-
-
-def tags_match(match_tags, candidate_tags):
- return all((k in candidate_tags and candidate_tags[k] == v
- for k, v in match_tags.items()))
-
-
-def ensure_tags(connection=None, module=None, resource_id=None, tags=None, purge_tags=None, check_mode=None):
- try:
- cur_tags = describe_tags_with_backoff(connection, resource_id)
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg='Unable to list tags for VPC')
-
- to_add, to_delete = compare_aws_tags(cur_tags, tags, purge_tags)
-
- if not to_add and not to_delete:
- return {'changed': False, 'tags': cur_tags}
- if check_mode:
- if not purge_tags:
- tags = cur_tags.update(tags)
- return {'changed': True, 'tags': tags}
-
- if to_delete:
- try:
- connection.delete_tags(Resources=[resource_id], Tags=[{'Key': k} for k in to_delete])
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Couldn't delete tags")
- if to_add:
- try:
- connection.create_tags(Resources=[resource_id], Tags=ansible_dict_to_boto3_tag_list(to_add))
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Couldn't create tags")
-
- try:
- latest_tags = describe_tags_with_backoff(connection, resource_id)
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg='Unable to list tags for VPC')
- return {'changed': True, 'tags': latest_tags}
-
-
-@AWSRetry.exponential_backoff()
-def describe_route_tables_with_backoff(connection, **params):
- try:
- return connection.describe_route_tables(**params)['RouteTables']
- except botocore.exceptions.ClientError as e:
- if e.response['Error']['Code'] == 'InvalidRouteTableID.NotFound':
- return None
- else:
- raise
-
-
-def get_route_table_by_id(connection, module, route_table_id):
-
- route_table = None
- try:
- route_tables = describe_route_tables_with_backoff(connection, RouteTableIds=[route_table_id])
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Couldn't get route table")
- if route_tables:
- route_table = route_tables[0]
-
- return route_table
-
-
-def get_route_table_by_tags(connection, module, vpc_id, tags):
- count = 0
- route_table = None
- filters = ansible_dict_to_boto3_filter_list({'vpc-id': vpc_id})
- try:
- route_tables = describe_route_tables_with_backoff(connection, Filters=filters)
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Couldn't get route table")
- for table in route_tables:
- this_tags = describe_tags_with_backoff(connection, table['RouteTableId'])
- if tags_match(tags, this_tags):
- route_table = table
- count += 1
-
- if count > 1:
- module.fail_json(msg="Tags provided do not identify a unique route table")
- else:
- return route_table
-
-
-def route_spec_matches_route(route_spec, route):
- if route_spec.get('GatewayId') and 'nat-' in route_spec['GatewayId']:
- route_spec['NatGatewayId'] = route_spec.pop('GatewayId')
- if route_spec.get('GatewayId') and 'vpce-' in route_spec['GatewayId']:
- if route_spec.get('DestinationCidrBlock', '').startswith('pl-'):
- route_spec['DestinationPrefixListId'] = route_spec.pop('DestinationCidrBlock')
-
- return set(route_spec.items()).issubset(route.items())
-
-
-def route_spec_matches_route_cidr(route_spec, route):
- return route_spec['DestinationCidrBlock'] == route.get('DestinationCidrBlock')
-
-
-def rename_key(d, old_key, new_key):
- d[new_key] = d.pop(old_key)
-
-
-def index_of_matching_route(route_spec, routes_to_match):
- for i, route in enumerate(routes_to_match):
- if route_spec_matches_route(route_spec, route):
- return "exact", i
- elif 'Origin' in route_spec and route_spec['Origin'] != 'EnableVgwRoutePropagation':
- if route_spec_matches_route_cidr(route_spec, route):
- return "replace", i
-
-
-def ensure_routes(connection=None, module=None, route_table=None, route_specs=None,
- propagating_vgw_ids=None, check_mode=None, purge_routes=None):
- routes_to_match = [route for route in route_table['Routes']]
- route_specs_to_create = []
- route_specs_to_recreate = []
- for route_spec in route_specs:
- match = index_of_matching_route(route_spec, routes_to_match)
- if match is None:
- if route_spec.get('DestinationCidrBlock'):
- route_specs_to_create.append(route_spec)
- else:
- module.warn("Skipping creating {0} because it has no destination cidr block. "
- "To add VPC endpoints to route tables use the ec2_vpc_endpoint module.".format(route_spec))
- else:
- if match[0] == "replace":
- if route_spec.get('DestinationCidrBlock'):
- route_specs_to_recreate.append(route_spec)
- else:
- module.warn("Skipping recreating route {0} because it has no destination cidr block.".format(route_spec))
- del routes_to_match[match[1]]
-
- routes_to_delete = []
- if purge_routes:
- for r in routes_to_match:
- if not r.get('DestinationCidrBlock'):
- module.warn("Skipping purging route {0} because it has no destination cidr block. "
- "To remove VPC endpoints from route tables use the ec2_vpc_endpoint module.".format(r))
- continue
- if r['Origin'] == 'CreateRoute':
- routes_to_delete.append(r)
-
- changed = bool(routes_to_delete or route_specs_to_create or route_specs_to_recreate)
- if changed and not check_mode:
- for route in routes_to_delete:
- try:
- connection.delete_route(RouteTableId=route_table['RouteTableId'], DestinationCidrBlock=route['DestinationCidrBlock'])
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Couldn't delete route")
-
- for route_spec in route_specs_to_recreate:
- try:
- connection.replace_route(RouteTableId=route_table['RouteTableId'],
- **route_spec)
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Couldn't recreate route")
-
- for route_spec in route_specs_to_create:
- try:
- connection.create_route(RouteTableId=route_table['RouteTableId'],
- **route_spec)
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Couldn't create route")
-
- return {'changed': bool(changed)}
-
-
-def ensure_subnet_association(connection=None, module=None, vpc_id=None, route_table_id=None, subnet_id=None,
- check_mode=None):
- filters = ansible_dict_to_boto3_filter_list({'association.subnet-id': subnet_id, 'vpc-id': vpc_id})
- try:
- route_tables = describe_route_tables_with_backoff(connection, Filters=filters)
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Couldn't get route tables")
- for route_table in route_tables:
- if route_table['RouteTableId'] is None:
- continue
- for a in route_table['Associations']:
- if a['Main']:
- continue
- if a['SubnetId'] == subnet_id:
- if route_table['RouteTableId'] == route_table_id:
- return {'changed': False, 'association_id': a['RouteTableAssociationId']}
- else:
- if check_mode:
- return {'changed': True}
- try:
- connection.disassociate_route_table(AssociationId=a['RouteTableAssociationId'])
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Couldn't disassociate subnet from route table")
-
- try:
- association_id = connection.associate_route_table(RouteTableId=route_table_id, SubnetId=subnet_id)
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Couldn't associate subnet with route table")
- return {'changed': True, 'association_id': association_id}
-
-
-def ensure_subnet_associations(connection=None, module=None, route_table=None, subnets=None,
- check_mode=None, purge_subnets=None):
- current_association_ids = [a['RouteTableAssociationId'] for a in route_table['Associations'] if not a['Main']]
- new_association_ids = []
- changed = False
- for subnet in subnets:
- result = ensure_subnet_association(connection=connection, module=module, vpc_id=route_table['VpcId'],
- route_table_id=route_table['RouteTableId'], subnet_id=subnet['SubnetId'], check_mode=check_mode)
- changed = changed or result['changed']
- if changed and check_mode:
- return {'changed': True}
- new_association_ids.append(result['association_id'])
-
- if purge_subnets:
- to_delete = [a_id for a_id in current_association_ids
- if a_id not in new_association_ids]
-
- for a_id in to_delete:
- changed = True
- if not check_mode:
- try:
- connection.disassociate_route_table(AssociationId=a_id)
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Couldn't disassociate subnet from route table")
-
- return {'changed': changed}
-
-
-def ensure_propagation(connection=None, module=None, route_table=None, propagating_vgw_ids=None,
- check_mode=None):
- changed = False
- gateways = [gateway['GatewayId'] for gateway in route_table['PropagatingVgws']]
- to_add = set(propagating_vgw_ids) - set(gateways)
- if to_add:
- changed = True
- if not check_mode:
- for vgw_id in to_add:
- try:
- connection.enable_vgw_route_propagation(RouteTableId=route_table['RouteTableId'],
- GatewayId=vgw_id)
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Couldn't enable route propagation")
-
- return {'changed': changed}
-
-
-def ensure_route_table_absent(connection, module):
-
- lookup = module.params.get('lookup')
- route_table_id = module.params.get('route_table_id')
- tags = module.params.get('tags')
- vpc_id = module.params.get('vpc_id')
- purge_subnets = module.params.get('purge_subnets')
-
- if lookup == 'tag':
- if tags is not None:
- route_table = get_route_table_by_tags(connection, module, vpc_id, tags)
- else:
- route_table = None
- elif lookup == 'id':
- route_table = get_route_table_by_id(connection, module, route_table_id)
-
- if route_table is None:
- return {'changed': False}
-
- # disassociate subnets before deleting route table
- if not module.check_mode:
- ensure_subnet_associations(connection=connection, module=module, route_table=route_table,
- subnets=[], check_mode=False, purge_subnets=purge_subnets)
- try:
- connection.delete_route_table(RouteTableId=route_table['RouteTableId'])
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Error deleting route table")
-
- return {'changed': True}
-
-
-def get_route_table_info(connection, module, route_table):
- result = get_route_table_by_id(connection, module, route_table['RouteTableId'])
- try:
- result['Tags'] = describe_tags_with_backoff(connection, route_table['RouteTableId'])
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Couldn't get tags for route table")
- result = camel_dict_to_snake_dict(result, ignore_list=['Tags'])
- # backwards compatibility
- result['id'] = result['route_table_id']
- return result
-
-
-def create_route_spec(connection, module, vpc_id):
- routes = module.params.get('routes')
-
- for route_spec in routes:
- rename_key(route_spec, 'dest', 'destination_cidr_block')
-
- if route_spec.get('gateway_id') and route_spec['gateway_id'].lower() == 'igw':
- igw = find_igw(connection, module, vpc_id)
- route_spec['gateway_id'] = igw
- if route_spec.get('gateway_id') and route_spec['gateway_id'].startswith('nat-'):
- rename_key(route_spec, 'gateway_id', 'nat_gateway_id')
-
- return snake_dict_to_camel_dict(routes, capitalize_first=True)
-
-
-def ensure_route_table_present(connection, module):
-
- lookup = module.params.get('lookup')
- propagating_vgw_ids = module.params.get('propagating_vgw_ids')
- purge_routes = module.params.get('purge_routes')
- purge_subnets = module.params.get('purge_subnets')
- purge_tags = module.params.get('purge_tags')
- route_table_id = module.params.get('route_table_id')
- subnets = module.params.get('subnets')
- tags = module.params.get('tags')
- vpc_id = module.params.get('vpc_id')
- routes = create_route_spec(connection, module, vpc_id)
-
- changed = False
- tags_valid = False
-
- if lookup == 'tag':
- if tags is not None:
- try:
- route_table = get_route_table_by_tags(connection, module, vpc_id, tags)
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Error finding route table with lookup 'tag'")
- else:
- route_table = None
- elif lookup == 'id':
- try:
- route_table = get_route_table_by_id(connection, module, route_table_id)
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Error finding route table with lookup 'id'")
-
- # If no route table returned then create new route table
- if route_table is None:
- changed = True
- if not module.check_mode:
- try:
- route_table = connection.create_route_table(VpcId=vpc_id)['RouteTable']
- # try to wait for route table to be present before moving on
- get_waiter(
- connection, 'route_table_exists'
- ).wait(
- RouteTableIds=[route_table['RouteTableId']],
- )
- except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
- module.fail_json_aws(e, msg="Error creating route table")
- else:
- route_table = {"id": "rtb-xxxxxxxx", "route_table_id": "rtb-xxxxxxxx", "vpc_id": vpc_id}
- module.exit_json(changed=changed, route_table=route_table)
-
- if routes is not None:
- result = ensure_routes(connection=connection, module=module, route_table=route_table,
- route_specs=routes, propagating_vgw_ids=propagating_vgw_ids,
- check_mode=module.check_mode, purge_routes=purge_routes)
- changed = changed or result['changed']
-
- if propagating_vgw_ids is not None:
- result = ensure_propagation(connection=connection, module=module, route_table=route_table,
- propagating_vgw_ids=propagating_vgw_ids, check_mode=module.check_mode)
- changed = changed or result['changed']
-
- if not tags_valid and tags is not None:
- result = ensure_tags(connection=connection, module=module, resource_id=route_table['RouteTableId'], tags=tags,
- purge_tags=purge_tags, check_mode=module.check_mode)
- route_table['Tags'] = result['tags']
- changed = changed or result['changed']
-
- if subnets is not None:
- associated_subnets = find_subnets(connection, module, vpc_id, subnets)
-
- result = ensure_subnet_associations(connection=connection, module=module, route_table=route_table,
- subnets=associated_subnets, check_mode=module.check_mode,
- purge_subnets=purge_subnets)
- changed = changed or result['changed']
-
- if changed:
- # pause to allow route table routes/subnets/associations to be updated before exiting with final state
- sleep(5)
- module.exit_json(changed=changed, route_table=get_route_table_info(connection, module, route_table))
-
-
-def main():
- argument_spec = dict(
- lookup=dict(default='tag', choices=['tag', 'id']),
- propagating_vgw_ids=dict(type='list'),
- purge_routes=dict(default=True, type='bool'),
- purge_subnets=dict(default=True, type='bool'),
- purge_tags=dict(default=False, type='bool'),
- route_table_id=dict(),
- routes=dict(default=[], type='list'),
- state=dict(default='present', choices=['present', 'absent']),
- subnets=dict(type='list'),
- tags=dict(type='dict', aliases=['resource_tags']),
- vpc_id=dict()
- )
-
- module = AnsibleAWSModule(argument_spec=argument_spec,
- required_if=[['lookup', 'id', ['route_table_id']],
- ['lookup', 'tag', ['vpc_id']],
- ['state', 'present', ['vpc_id']]],
- supports_check_mode=True)
-
- connection = module.client('ec2')
-
- state = module.params.get('state')
-
- if state == 'present':
- result = ensure_route_table_present(connection, module)
- elif state == 'absent':
- result = ensure_route_table_absent(connection, module)
-
- module.exit_json(**result)
-
-
-if __name__ == '__main__':
- main()