|
|
|
#!/usr/bin/python
|
|
|
|
# This file is part of Ansible
|
|
|
|
#
|
|
|
|
# Ansible is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# Ansible is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
DOCUMENTATION = '''
|
|
|
|
---
|
|
|
|
module: ec2_vpc_nat_gateway
|
|
|
|
short_description: Manage AWS VPC NAT Gateways.
|
|
|
|
description:
|
|
|
|
- Ensure the state of AWS VPC NAT Gateways based on their id, allocation and subnet ids.
|
|
|
|
version_added: "2.2"
|
|
|
|
requirements: [boto3, botocore]
|
|
|
|
options:
|
|
|
|
state:
|
|
|
|
description:
|
|
|
|
- Ensure NAT Gateway is present or absent.
|
|
|
|
required: false
|
|
|
|
default: "present"
|
|
|
|
choices: ["present", "absent"]
|
|
|
|
nat_gateway_id:
|
|
|
|
description:
|
|
|
|
- The id AWS dynamically allocates to the NAT Gateway on creation.
|
|
|
|
This is required when the absent option is present.
|
|
|
|
required: false
|
|
|
|
default: None
|
|
|
|
subnet_id:
|
|
|
|
description:
|
|
|
|
- The id of the subnet to create the NAT Gateway in. This is required
|
|
|
|
with the present option.
|
|
|
|
required: false
|
|
|
|
default: None
|
|
|
|
allocation_id:
|
|
|
|
description:
|
|
|
|
- The id of the elastic IP allocation. If this is not passed and the
|
|
|
|
eip_address is not passed. An EIP is generated for this NAT Gateway.
|
|
|
|
required: false
|
|
|
|
default: None
|
|
|
|
eip_address:
|
|
|
|
description:
|
|
|
|
- The elastic IP address of the EIP you want attached to this NAT Gateway.
|
|
|
|
If this is not passed and the allocation_id is not passed,
|
|
|
|
an EIP is generated for this NAT Gateway.
|
|
|
|
required: false
|
|
|
|
if_exist_do_not_create:
|
|
|
|
description:
|
|
|
|
- if a NAT Gateway exists already in the subnet_id, then do not create a new one.
|
|
|
|
required: false
|
|
|
|
default: false
|
|
|
|
release_eip:
|
|
|
|
description:
|
|
|
|
- Deallocate the EIP from the VPC.
|
|
|
|
- Option is only valid with the absent state.
|
|
|
|
- You should use this with the wait option. Since you can not release an address while a delete operation is happening.
|
|
|
|
required: false
|
|
|
|
default: true
|
|
|
|
wait:
|
|
|
|
description:
|
|
|
|
- Wait for operation to complete before returning.
|
|
|
|
required: false
|
|
|
|
default: false
|
|
|
|
wait_timeout:
|
|
|
|
description:
|
|
|
|
- How many seconds to wait for an operation to complete before timing out.
|
|
|
|
required: false
|
|
|
|
default: 300
|
|
|
|
client_token:
|
|
|
|
description:
|
|
|
|
- Optional unique token to be used during create to ensure idempotency.
|
|
|
|
When specifying this option, ensure you specify the eip_address parameter
|
|
|
|
as well otherwise any subsequent runs will fail.
|
|
|
|
required: false
|
|
|
|
|
|
|
|
author:
|
|
|
|
- "Allen Sanabria (@linuxdynasty)"
|
|
|
|
- "Jon Hadfield (@jonhadfield)"
|
|
|
|
- "Karen Cheng(@Etherdaemon)"
|
|
|
|
extends_documentation_fragment:
|
|
|
|
- aws
|
|
|
|
- ec2
|
|
|
|
'''
|
|
|
|
|
|
|
|
EXAMPLES = '''
|
|
|
|
# Note: These examples do not set authentication details, see the AWS Guide for details.
|
|
|
|
|
|
|
|
- name: Create new nat gateway with client token.
|
|
|
|
ec2_vpc_nat_gateway:
|
|
|
|
state: present
|
|
|
|
subnet_id: subnet-12345678
|
|
|
|
eip_address: 52.1.1.1
|
|
|
|
region: ap-southeast-2
|
|
|
|
client_token: abcd-12345678
|
|
|
|
register: new_nat_gateway
|
|
|
|
|
|
|
|
- name: Create new nat gateway using an allocation-id.
|
|
|
|
ec2_vpc_nat_gateway:
|
|
|
|
state: present
|
|
|
|
subnet_id: subnet-12345678
|
|
|
|
allocation_id: eipalloc-12345678
|
|
|
|
region: ap-southeast-2
|
|
|
|
register: new_nat_gateway
|
|
|
|
|
|
|
|
- name: Create new nat gateway, using an EIP address and wait for available status.
|
|
|
|
ec2_vpc_nat_gateway:
|
|
|
|
state: present
|
|
|
|
subnet_id: subnet-12345678
|
|
|
|
eip_address: 52.1.1.1
|
|
|
|
wait: yes
|
|
|
|
region: ap-southeast-2
|
|
|
|
register: new_nat_gateway
|
|
|
|
|
|
|
|
- name: Create new nat gateway and allocate new EIP.
|
|
|
|
ec2_vpc_nat_gateway:
|
|
|
|
state: present
|
|
|
|
subnet_id: subnet-12345678
|
|
|
|
wait: yes
|
|
|
|
region: ap-southeast-2
|
|
|
|
register: new_nat_gateway
|
|
|
|
|
|
|
|
- name: Create new nat gateway and allocate new EIP if a nat gateway does not yet exist in the subnet.
|
|
|
|
ec2_vpc_nat_gateway:
|
|
|
|
state: present
|
|
|
|
subnet_id: subnet-12345678
|
|
|
|
wait: yes
|
|
|
|
region: ap-southeast-2
|
|
|
|
if_exist_do_not_create: true
|
|
|
|
register: new_nat_gateway
|
|
|
|
|
|
|
|
- name: Delete nat gateway using discovered nat gateways from facts module.
|
|
|
|
ec2_vpc_nat_gateway:
|
|
|
|
state: absent
|
|
|
|
region: ap-southeast-2
|
|
|
|
wait: yes
|
|
|
|
nat_gateway_id: "{{ item.NatGatewayId }}"
|
|
|
|
release_eip: yes
|
|
|
|
register: delete_nat_gateway_result
|
|
|
|
with_items: "{{ gateways_to_remove.result }}"
|
|
|
|
|
|
|
|
- name: Delete nat gateway and wait for deleted status.
|
|
|
|
ec2_vpc_nat_gateway:
|
|
|
|
state: absent
|
|
|
|
nat_gateway_id: nat-12345678
|
|
|
|
wait: yes
|
|
|
|
wait_timeout: 500
|
|
|
|
region: ap-southeast-2
|
|
|
|
|
|
|
|
- name: Delete nat gateway and release EIP.
|
|
|
|
ec2_vpc_nat_gateway:
|
|
|
|
state: absent
|
|
|
|
nat_gateway_id: nat-12345678
|
|
|
|
release_eip: yes
|
|
|
|
wait: yes
|
|
|
|
wait_timeout: 300
|
|
|
|
region: ap-southeast-2
|
|
|
|
'''
|
|
|
|
|
|
|
|
RETURN = '''
|
|
|
|
create_time:
|
|
|
|
description: The ISO 8601 date time formatin UTC.
|
|
|
|
returned: In all cases.
|
|
|
|
type: string
|
|
|
|
sample: "2016-03-05T05:19:20.282000+00:00'"
|
|
|
|
nat_gateway_id:
|
|
|
|
description: id of the VPC NAT Gateway
|
|
|
|
returned: In all cases.
|
|
|
|
type: string
|
|
|
|
sample: "nat-0d1e3a878585988f8"
|
|
|
|
subnet_id:
|
|
|
|
description: id of the Subnet
|
|
|
|
returned: In all cases.
|
|
|
|
type: string
|
|
|
|
sample: "subnet-12345"
|
|
|
|
state:
|
|
|
|
description: The current state of the NAT Gateway.
|
|
|
|
returned: In all cases.
|
|
|
|
type: string
|
|
|
|
sample: "available"
|
|
|
|
vpc_id:
|
|
|
|
description: id of the VPC.
|
|
|
|
returned: In all cases.
|
|
|
|
type: string
|
|
|
|
sample: "vpc-12345"
|
|
|
|
nat_gateway_addresses:
|
|
|
|
description: List of dictionairies containing the public_ip, network_interface_id, private_ip, and allocation_id.
|
|
|
|
returned: In all cases.
|
|
|
|
type: string
|
|
|
|
sample: [
|
|
|
|
{
|
|
|
|
'public_ip': '52.52.52.52',
|
|
|
|
'network_interface_id': 'eni-12345',
|
|
|
|
'private_ip': '10.0.0.100',
|
|
|
|
'allocation_id': 'eipalloc-12345'
|
|
|
|
}
|
|
|
|
]
|
|
|
|
'''
|
|
|
|
|
|
|
|
try:
|
|
|
|
import botocore
|
|
|
|
import boto3
|
|
|
|
HAS_BOTO3 = True
|
|
|
|
except ImportError:
|
|
|
|
HAS_BOTO3 = False
|
|
|
|
|
|
|
|
import datetime
|
|
|
|
import random
|
|
|
|
import re
|
|
|
|
import time
|
|
|
|
|
|
|
|
from dateutil.tz import tzutc
|
|
|
|
|
|
|
|
DRY_RUN_GATEWAYS = [
|
|
|
|
{
|
|
|
|
"nat_gateway_id": "nat-123456789",
|
|
|
|
"subnet_id": "subnet-123456789",
|
|
|
|
"nat_gateway_addresses": [
|
|
|
|
{
|
|
|
|
"public_ip": "55.55.55.55",
|
|
|
|
"network_interface_id": "eni-1234567",
|
|
|
|
"private_ip": "10.0.0.102",
|
|
|
|
"allocation_id": "eipalloc-1234567"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"state": "available",
|
|
|
|
"create_time": "2016-03-05T05:19:20.282000+00:00",
|
|
|
|
"vpc_id": "vpc-12345678"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
DRY_RUN_GATEWAY_UNCONVERTED = [
|
|
|
|
{
|
|
|
|
'VpcId': 'vpc-12345678',
|
|
|
|
'State': 'available',
|
|
|
|
'NatGatewayId': 'nat-123456789',
|
|
|
|
'SubnetId': 'subnet-123456789',
|
|
|
|
'NatGatewayAddresses': [
|
|
|
|
{
|
|
|
|
'PublicIp': '55.55.55.55',
|
|
|
|
'NetworkInterfaceId': 'eni-1234567',
|
|
|
|
'AllocationId': 'eipalloc-1234567',
|
|
|
|
'PrivateIp': '10.0.0.102'
|
|
|
|
}
|
|
|
|
],
|
|
|
|
'CreateTime': datetime.datetime(2016, 3, 5, 5, 19, 20, 282000, tzinfo=tzutc())
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
DRY_RUN_ALLOCATION_UNCONVERTED = {
|
|
|
|
'Addresses': [
|
|
|
|
{
|
|
|
|
'PublicIp': '55.55.55.55',
|
|
|
|
'Domain': 'vpc',
|
|
|
|
'AllocationId': 'eipalloc-1234567'
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
DRY_RUN_MSGS = 'DryRun Mode:'
|
|
|
|
|
|
|
|
|
|
|
|
def convert_to_lower(data):
|
|
|
|
"""Convert all uppercase keys in dict with lowercase_
|
|
|
|
|
|
|
|
Args:
|
|
|
|
data (dict): Dictionary with keys that have upper cases in them
|
|
|
|
Example.. FooBar == foo_bar
|
|
|
|
if a val is of type datetime.datetime, it will be converted to
|
|
|
|
the ISO 8601
|
|
|
|
|
|
|
|
Basic Usage:
|
|
|
|
>>> test = {'FooBar': []}
|
|
|
|
>>> test = convert_to_lower(test)
|
|
|
|
{
|
|
|
|
'foo_bar': []
|
|
|
|
}
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Dictionary
|
|
|
|
"""
|
|
|
|
results = dict()
|
|
|
|
if isinstance(data, dict):
|
|
|
|
for key, val in data.items():
|
|
|
|
key = re.sub(r'(([A-Z]{1,3}){1})', r'_\1', key).lower()
|
|
|
|
if key[0] == '_':
|
|
|
|
key = key[1:]
|
|
|
|
if isinstance(val, datetime.datetime):
|
|
|
|
results[key] = val.isoformat()
|
|
|
|
elif isinstance(val, dict):
|
|
|
|
results[key] = convert_to_lower(val)
|
|
|
|
elif isinstance(val, list):
|
|
|
|
converted = list()
|
|
|
|
for item in val:
|
|
|
|
converted.append(convert_to_lower(item))
|
|
|
|
results[key] = converted
|
|
|
|
else:
|
|
|
|
results[key] = val
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
|
|
def get_nat_gateways(client, subnet_id=None, nat_gateway_id=None,
|
|
|
|
states=None, check_mode=False):
|
|
|
|
"""Retrieve a list of NAT Gateways
|
|
|
|
Args:
|
|
|
|
client (botocore.client.EC2): Boto3 client
|
|
|
|
|
|
|
|
Kwargs:
|
|
|
|
subnet_id (str): The subnet_id the nat resides in.
|
|
|
|
nat_gateway_id (str): The Amazon nat id.
|
|
|
|
states (list): States available (pending, failed, available, deleting, and deleted)
|
|
|
|
default=None
|
|
|
|
|
|
|
|
Basic Usage:
|
|
|
|
>>> client = boto3.client('ec2')
|
|
|
|
>>> subnet_id = 'subnet-12345678'
|
|
|
|
>>> get_nat_gateways(client, subnet_id)
|
|
|
|
[
|
|
|
|
true,
|
|
|
|
"",
|
|
|
|
{
|
|
|
|
"nat_gateway_id": "nat-123456789",
|
|
|
|
"subnet_id": "subnet-123456789",
|
|
|
|
"nat_gateway_addresses": [
|
|
|
|
{
|
|
|
|
"public_ip": "55.55.55.55",
|
|
|
|
"network_interface_id": "eni-1234567",
|
|
|
|
"private_ip": "10.0.0.102",
|
|
|
|
"allocation_id": "eipalloc-1234567"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"state": "deleted",
|
|
|
|
"create_time": "2016-03-05T00:33:21.209000+00:00",
|
|
|
|
"delete_time": "2016-03-05T00:36:37.329000+00:00",
|
|
|
|
"vpc_id": "vpc-12345678"
|
|
|
|
}
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Tuple (bool, str, list)
|
|
|
|
"""
|
|
|
|
params = dict()
|
|
|
|
err_msg = ""
|
|
|
|
gateways_retrieved = False
|
|
|
|
existing_gateways = list()
|
|
|
|
if not states:
|
|
|
|
states = ['available', 'pending']
|
|
|
|
if nat_gateway_id:
|
|
|
|
params['NatGatewayIds'] = [nat_gateway_id]
|
|
|
|
else:
|
|
|
|
params['Filter'] = [
|
|
|
|
{
|
|
|
|
'Name': 'subnet-id',
|
|
|
|
'Values': [subnet_id]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
'Name': 'state',
|
|
|
|
'Values': states
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
try:
|
|
|
|
if not check_mode:
|
|
|
|
gateways = client.describe_nat_gateways(**params)['NatGateways']
|
|
|
|
if gateways:
|
|
|
|
for gw in gateways:
|
|
|
|
existing_gateways.append(convert_to_lower(gw))
|
|
|
|
gateways_retrieved = True
|
|
|
|
else:
|
|
|
|
gateways_retrieved = True
|
|
|
|
if nat_gateway_id:
|
|
|
|
if DRY_RUN_GATEWAYS[0]['nat_gateway_id'] == nat_gateway_id:
|
|
|
|
existing_gateways = DRY_RUN_GATEWAYS
|
|
|
|
elif subnet_id:
|
|
|
|
if DRY_RUN_GATEWAYS[0]['subnet_id'] == subnet_id:
|
|
|
|
existing_gateways = DRY_RUN_GATEWAYS
|
|
|
|
err_msg = '{0} Retrieving gateways'.format(DRY_RUN_MSGS)
|
|
|
|
|
|
|
|
except botocore.exceptions.ClientError as e:
|
|
|
|
err_msg = str(e)
|
|
|
|
|
|
|
|
return gateways_retrieved, err_msg, existing_gateways
|
|
|
|
|
|
|
|
|
|
|
|
def wait_for_status(client, wait_timeout, nat_gateway_id, status,
|
|
|
|
check_mode=False):
|
|
|
|
"""Wait for the NAT Gateway to reach a status
|
|
|
|
Args:
|
|
|
|
client (botocore.client.EC2): Boto3 client
|
|
|
|
wait_timeout (int): Number of seconds to wait, until this timeout is reached.
|
|
|
|
nat_gateway_id (str): The Amazon nat id.
|
|
|
|
status (str): The status to wait for.
|
|
|
|
examples. status=available, status=deleted
|
|
|
|
|
|
|
|
Basic Usage:
|
|
|
|
>>> client = boto3.client('ec2')
|
|
|
|
>>> subnet_id = 'subnet-12345678'
|
|
|
|
>>> allocation_id = 'eipalloc-12345678'
|
|
|
|
>>> wait_for_status(client, subnet_id, allocation_id)
|
|
|
|
[
|
|
|
|
true,
|
|
|
|
"",
|
|
|
|
{
|
|
|
|
"nat_gateway_id": "nat-123456789",
|
|
|
|
"subnet_id": "subnet-1234567",
|
|
|
|
"nat_gateway_addresses": [
|
|
|
|
{
|
|
|
|
"public_ip": "55.55.55.55",
|
|
|
|
"network_interface_id": "eni-1234567",
|
|
|
|
"private_ip": "10.0.0.102",
|
|
|
|
"allocation_id": "eipalloc-12345678"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"state": "deleted",
|
|
|
|
"create_time": "2016-03-05T00:33:21.209000+00:00",
|
|
|
|
"delete_time": "2016-03-05T00:36:37.329000+00:00",
|
|
|
|
"vpc_id": "vpc-12345677"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Tuple (bool, str, dict)
|
|
|
|
"""
|
|
|
|
polling_increment_secs = 5
|
|
|
|
wait_timeout = time.time() + wait_timeout
|
|
|
|
status_achieved = False
|
|
|
|
nat_gateway = dict()
|
|
|
|
states = ['pending', 'failed', 'available', 'deleting', 'deleted']
|
|
|
|
err_msg = ""
|
|
|
|
|
|
|
|
while wait_timeout > time.time():
|
|
|
|
try:
|
|
|
|
gws_retrieved, err_msg, nat_gateways = (
|
|
|
|
get_nat_gateways(
|
|
|
|
client, nat_gateway_id=nat_gateway_id,
|
|
|
|
states=states, check_mode=check_mode
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if gws_retrieved and nat_gateways:
|
|
|
|
nat_gateway = nat_gateways[0]
|
|
|
|
if check_mode:
|
|
|
|
nat_gateway['state'] = status
|
|
|
|
|
|
|
|
if nat_gateway.get('state') == status:
|
|
|
|
status_achieved = True
|
|
|
|
break
|
|
|
|
|
|
|
|
elif nat_gateway.get('state') == 'failed':
|
|
|
|
err_msg = nat_gateway.get('failure_message')
|
|
|
|
break
|
|
|
|
|
|
|
|
elif nat_gateway.get('state') == 'pending':
|
|
|
|
if 'failure_message' in nat_gateway:
|
|
|
|
err_msg = nat_gateway.get('failure_message')
|
|
|
|
status_achieved = False
|
|
|
|
break
|
|
|
|
|
|
|
|
else:
|
|
|
|
time.sleep(polling_increment_secs)
|
|
|
|
|
|
|
|
except botocore.exceptions.ClientError as e:
|
|
|
|
err_msg = str(e)
|
|
|
|
|
|
|
|
if not status_achieved:
|
|
|
|
err_msg = "Wait time out reached, while waiting for results"
|
|
|
|
|
|
|
|
return status_achieved, err_msg, nat_gateway
|
|
|
|
|
|
|
|
|
|
|
|
def gateway_in_subnet_exists(client, subnet_id, allocation_id=None,
|
|
|
|
check_mode=False):
|
|
|
|
"""Retrieve all NAT Gateways for a subnet.
|
|
|
|
Args:
|
|
|
|
subnet_id (str): The subnet_id the nat resides in.
|
|
|
|
|
|
|
|
Kwargs:
|
|
|
|
allocation_id (str): The EIP Amazon identifier.
|
|
|
|
default = None
|
|
|
|
|
|
|
|
Basic Usage:
|
|
|
|
>>> client = boto3.client('ec2')
|
|
|
|
>>> subnet_id = 'subnet-1234567'
|
|
|
|
>>> allocation_id = 'eipalloc-1234567'
|
|
|
|
>>> gateway_in_subnet_exists(client, subnet_id, allocation_id)
|
|
|
|
(
|
|
|
|
[
|
|
|
|
{
|
|
|
|
"nat_gateway_id": "nat-123456789",
|
|
|
|
"subnet_id": "subnet-123456789",
|
|
|
|
"nat_gateway_addresses": [
|
|
|
|
{
|
|
|
|
"public_ip": "55.55.55.55",
|
|
|
|
"network_interface_id": "eni-1234567",
|
|
|
|
"private_ip": "10.0.0.102",
|
|
|
|
"allocation_id": "eipalloc-1234567"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"state": "deleted",
|
|
|
|
"create_time": "2016-03-05T00:33:21.209000+00:00",
|
|
|
|
"delete_time": "2016-03-05T00:36:37.329000+00:00",
|
|
|
|
"vpc_id": "vpc-1234567"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
False
|
|
|
|
)
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Tuple (list, bool)
|
|
|
|
"""
|
|
|
|
allocation_id_exists = False
|
|
|
|
gateways = []
|
|
|
|
states = ['available', 'pending']
|
|
|
|
gws_retrieved, _, gws = (
|
|
|
|
get_nat_gateways(
|
|
|
|
client, subnet_id, states=states, check_mode=check_mode
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if not gws_retrieved:
|
|
|
|
return gateways, allocation_id_exists
|
|
|
|
for gw in gws:
|
|
|
|
for address in gw['nat_gateway_addresses']:
|
|
|
|
if allocation_id:
|
|
|
|
if address.get('allocation_id') == allocation_id:
|
|
|
|
allocation_id_exists = True
|
|
|
|
gateways.append(gw)
|
|
|
|
else:
|
|
|
|
gateways.append(gw)
|
|
|
|
|
|
|
|
return gateways, allocation_id_exists
|
|
|
|
|
|
|
|
|
|
|
|
def get_eip_allocation_id_by_address(client, eip_address, check_mode=False):
|
|
|
|
"""Release an EIP from your EIP Pool
|
|
|
|
Args:
|
|
|
|
client (botocore.client.EC2): Boto3 client
|
|
|
|
eip_address (str): The Elastic IP Address of the EIP.
|
|
|
|
|
|
|
|
Kwargs:
|
|
|
|
check_mode (bool): if set to true, do not run anything and
|
|
|
|
falsify the results.
|
|
|
|
|
|
|
|
Basic Usage:
|
|
|
|
>>> client = boto3.client('ec2')
|
|
|
|
>>> eip_address = '52.87.29.36'
|
|
|
|
>>> get_eip_allocation_id_by_address(client, eip_address)
|
|
|
|
'eipalloc-36014da3'
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Tuple (str, str)
|
|
|
|
"""
|
|
|
|
params = {
|
|
|
|
'PublicIps': [eip_address],
|
|
|
|
}
|
|
|
|
allocation_id = None
|
|
|
|
err_msg = ""
|
|
|
|
try:
|
|
|
|
if not check_mode:
|
|
|
|
allocations = client.describe_addresses(**params)['Addresses']
|
|
|
|
if len(allocations) == 1:
|
|
|
|
allocation = allocations[0]
|
|
|
|
else:
|
|
|
|
allocation = None
|
|
|
|
else:
|
|
|
|
dry_run_eip = (
|
|
|
|
DRY_RUN_ALLOCATION_UNCONVERTED['Addresses'][0]['PublicIp']
|
|
|
|
)
|
|
|
|
if dry_run_eip == eip_address:
|
|
|
|
allocation = DRY_RUN_ALLOCATION_UNCONVERTED['Addresses'][0]
|
|
|
|
else:
|
|
|
|
allocation = None
|
|
|
|
if allocation:
|
|
|
|
if allocation.get('Domain') != 'vpc':
|
|
|
|
err_msg = (
|
|
|
|
"EIP {0} is a non-VPC EIP, please allocate a VPC scoped EIP"
|
|
|
|
.format(eip_address)
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
allocation_id = allocation.get('AllocationId')
|
|
|
|
else:
|
|
|
|
err_msg = (
|
|
|
|
"EIP {0} does not exist".format(eip_address)
|
|
|
|
)
|
|
|
|
|
|
|
|
except botocore.exceptions.ClientError as e:
|
|
|
|
err_msg = str(e)
|
|
|
|
|
|
|
|
return allocation_id, err_msg
|
|
|
|
|
|
|
|
|
|
|
|
def allocate_eip_address(client, check_mode=False):
|
|
|
|
"""Release an EIP from your EIP Pool
|
|
|
|
Args:
|
|
|
|
client (botocore.client.EC2): Boto3 client
|
|
|
|
|
|
|
|
Kwargs:
|
|
|
|
check_mode (bool): if set to true, do not run anything and
|
|
|
|
falsify the results.
|
|
|
|
|
|
|
|
Basic Usage:
|
|
|
|
>>> client = boto3.client('ec2')
|
|
|
|
>>> allocate_eip_address(client)
|
|
|
|
True
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Tuple (bool, str)
|
|
|
|
"""
|
|
|
|
ip_allocated = False
|
|
|
|
new_eip = None
|
|
|
|
err_msg = ''
|
|
|
|
params = {
|
|
|
|
'Domain': 'vpc',
|
|
|
|
}
|
|
|
|
try:
|
|
|
|
if check_mode:
|
|
|
|
ip_allocated = True
|
|
|
|
random_numbers = (
|
|
|
|
''.join(str(x) for x in random.sample(range(0, 9), 7))
|
|
|
|
)
|
|
|
|
new_eip = 'eipalloc-{0}'.format(random_numbers)
|
|
|
|
else:
|
|
|
|
new_eip = client.allocate_address(**params)['AllocationId']
|
|
|
|
ip_allocated = True
|
|
|
|
err_msg = 'eipalloc id {0} created'.format(new_eip)
|
|
|
|
|
|
|
|
except botocore.exceptions.ClientError as e:
|
|
|
|
err_msg = str(e)
|
|
|
|
|
|
|
|
return ip_allocated, err_msg, new_eip
|
|
|
|
|
|
|
|
|
|
|
|
def release_address(client, allocation_id, check_mode=False):
|
|
|
|
"""Release an EIP from your EIP Pool
|
|
|
|
Args:
|
|
|
|
client (botocore.client.EC2): Boto3 client
|
|
|
|
allocation_id (str): The eip Amazon identifier.
|
|
|
|
|
|
|
|
Kwargs:
|
|
|
|
check_mode (bool): if set to true, do not run anything and
|
|
|
|
falsify the results.
|
|
|
|
|
|
|
|
Basic Usage:
|
|
|
|
>>> client = boto3.client('ec2')
|
|
|
|
>>> allocation_id = "eipalloc-123456"
|
|
|
|
>>> release_address(client, allocation_id)
|
|
|
|
True
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Boolean, string
|
|
|
|
"""
|
|
|
|
err_msg = ''
|
|
|
|
if check_mode:
|
|
|
|
return True, ''
|
|
|
|
|
|
|
|
ip_released = False
|
|
|
|
params = {
|
|
|
|
'AllocationId': allocation_id,
|
|
|
|
}
|
|
|
|
try:
|
|
|
|
client.release_address(**params)
|
|
|
|
ip_released = True
|
|
|
|
except botocore.exceptions.ClientError as e:
|
|
|
|
err_msg = str(e)
|
|
|
|
|
|
|
|
return ip_released, err_msg
|
|
|
|
|
|
|
|
|
|
|
|
def create(client, subnet_id, allocation_id, client_token=None,
|
|
|
|
wait=False, wait_timeout=0, if_exist_do_not_create=False,
|
|
|
|
check_mode=False):
|
|
|
|
"""Create an Amazon NAT Gateway.
|
|
|
|
Args:
|
|
|
|
client (botocore.client.EC2): Boto3 client
|
|
|
|
subnet_id (str): The subnet_id the nat resides in.
|
|
|
|
allocation_id (str): The eip Amazon identifier.
|
|
|
|
|
|
|
|
Kwargs:
|
|
|
|
if_exist_do_not_create (bool): if a nat gateway already exists in this
|
|
|
|
subnet, than do not create another one.
|
|
|
|
default = False
|
|
|
|
wait (bool): Wait for the nat to be in the deleted state before returning.
|
|
|
|
default = False
|
|
|
|
wait_timeout (int): Number of seconds to wait, until this timeout is reached.
|
|
|
|
default = 0
|
|
|
|
client_token (str):
|
|
|
|
default = None
|
|
|
|
|
|
|
|
Basic Usage:
|
|
|
|
>>> client = boto3.client('ec2')
|
|
|
|
>>> subnet_id = 'subnet-1234567'
|
|
|
|
>>> allocation_id = 'eipalloc-1234567'
|
|
|
|
>>> create(client, subnet_id, allocation_id, if_exist_do_not_create=True, wait=True, wait_timeout=500)
|
|
|
|
[
|
|
|
|
true,
|
|
|
|
"",
|
|
|
|
{
|
|
|
|
"nat_gateway_id": "nat-123456789",
|
|
|
|
"subnet_id": "subnet-1234567",
|
|
|
|
"nat_gateway_addresses": [
|
|
|
|
{
|
|
|
|
"public_ip": "55.55.55.55",
|
|
|
|
"network_interface_id": "eni-1234567",
|
|
|
|
"private_ip": "10.0.0.102",
|
|
|
|
"allocation_id": "eipalloc-1234567"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"state": "deleted",
|
|
|
|
"create_time": "2016-03-05T00:33:21.209000+00:00",
|
|
|
|
"delete_time": "2016-03-05T00:36:37.329000+00:00",
|
|
|
|
"vpc_id": "vpc-1234567"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Tuple (bool, str, list)
|
|
|
|
"""
|
|
|
|
params = {
|
|
|
|
'SubnetId': subnet_id,
|
|
|
|
'AllocationId': allocation_id
|
|
|
|
}
|
|
|
|
request_time = datetime.datetime.utcnow()
|
|
|
|
changed = False
|
|
|
|
success = False
|
|
|
|
token_provided = False
|
|
|
|
err_msg = ""
|
|
|
|
|
|
|
|
if client_token:
|
|
|
|
token_provided = True
|
|
|
|
params['ClientToken'] = client_token
|
|
|
|
|
|
|
|
try:
|
|
|
|
if not check_mode:
|
|
|
|
result = client.create_nat_gateway(**params)["NatGateway"]
|
|
|
|
else:
|
|
|
|
result = DRY_RUN_GATEWAY_UNCONVERTED[0]
|
|
|
|
result['CreateTime'] = datetime.datetime.utcnow()
|
|
|
|
result['NatGatewayAddresses'][0]['AllocationId'] = allocation_id
|
|
|
|
result['SubnetId'] = subnet_id
|
|
|
|
|
|
|
|
success = True
|
|
|
|
changed = True
|
|
|
|
create_time = result['CreateTime'].replace(tzinfo=None)
|
|
|
|
if token_provided and (request_time > create_time):
|
|
|
|
changed = False
|
|
|
|
elif wait:
|
|
|
|
success, err_msg, result = (
|
|
|
|
wait_for_status(
|
|
|
|
client, wait_timeout, result['NatGatewayId'], 'available',
|
|
|
|
check_mode=check_mode
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if success:
|
|
|
|
err_msg = (
|
|
|
|
'NAT gateway {0} created'.format(result['nat_gateway_id'])
|
|
|
|
)
|
|
|
|
|
|
|
|
except botocore.exceptions.ClientError as e:
|
|
|
|
if "IdempotentParameterMismatch" in e.message:
|
|
|
|
err_msg = (
|
|
|
|
'NAT Gateway does not support update and token has already been provided'
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
err_msg = str(e)
|
|
|
|
success = False
|
|
|
|
changed = False
|
|
|
|
result = None
|
|
|
|
|
|
|
|
return success, changed, err_msg, result
|
|
|
|
|
|
|
|
|
|
|
|
def pre_create(client, subnet_id, allocation_id=None, eip_address=None,
|
|
|
|
if_exist_do_not_create=False, wait=False, wait_timeout=0,
|
|
|
|
client_token=None, check_mode=False):
|
|
|
|
"""Create an Amazon NAT Gateway.
|
|
|
|
Args:
|
|
|
|
client (botocore.client.EC2): Boto3 client
|
|
|
|
subnet_id (str): The subnet_id the nat resides in.
|
|
|
|
|
|
|
|
Kwargs:
|
|
|
|
allocation_id (str): The EIP Amazon identifier.
|
|
|
|
default = None
|
|
|
|
eip_address (str): The Elastic IP Address of the EIP.
|
|
|
|
default = None
|
|
|
|
if_exist_do_not_create (bool): if a nat gateway already exists in this
|
|
|
|
subnet, than do not create another one.
|
|
|
|
default = False
|
|
|
|
wait (bool): Wait for the nat to be in the deleted state before returning.
|
|
|
|
default = False
|
|
|
|
wait_timeout (int): Number of seconds to wait, until this timeout is reached.
|
|
|
|
default = 0
|
|
|
|
client_token (str):
|
|
|
|
default = None
|
|
|
|
|
|
|
|
Basic Usage:
|
|
|
|
>>> client = boto3.client('ec2')
|
|
|
|
>>> subnet_id = 'subnet-w4t12897'
|
|
|
|
>>> allocation_id = 'eipalloc-36014da3'
|
|
|
|
>>> pre_create(client, subnet_id, allocation_id, if_exist_do_not_create=True, wait=True, wait_timeout=500)
|
|
|
|
[
|
|
|
|
true,
|
|
|
|
"",
|
|
|
|
{
|
|
|
|
"nat_gateway_id": "nat-03835afb6e31df79b",
|
|
|
|
"subnet_id": "subnet-w4t12897",
|
|
|
|
"nat_gateway_addresses": [
|
|
|
|
{
|
|
|
|
"public_ip": "52.87.29.36",
|
|
|
|
"network_interface_id": "eni-5579742d",
|
|
|
|
"private_ip": "10.0.0.102",
|
|
|
|
"allocation_id": "eipalloc-36014da3"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"state": "deleted",
|
|
|
|
"create_time": "2016-03-05T00:33:21.209000+00:00",
|
|
|
|
"delete_time": "2016-03-05T00:36:37.329000+00:00",
|
|
|
|
"vpc_id": "vpc-w68571b5"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Tuple (bool, bool, str, list)
|
|
|
|
"""
|
|
|
|
success = False
|
|
|
|
changed = False
|
|
|
|
err_msg = ""
|
|
|
|
results = list()
|
|
|
|
|
|
|
|
if not allocation_id and not eip_address:
|
|
|
|
existing_gateways, allocation_id_exists = (
|
|
|
|
gateway_in_subnet_exists(client, subnet_id, check_mode=check_mode)
|
|
|
|
)
|
|
|
|
|
|
|
|
if len(existing_gateways) > 0 and if_exist_do_not_create:
|
|
|
|
success = True
|
|
|
|
changed = False
|
|
|
|
results = existing_gateways[0]
|
|
|
|
err_msg = (
|
|
|
|
'NAT Gateway {0} already exists in subnet_id {1}'
|
|
|
|
.format(
|
|
|
|
existing_gateways[0]['nat_gateway_id'], subnet_id
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return success, changed, err_msg, results
|
|
|
|
else:
|
|
|
|
success, err_msg, allocation_id = (
|
|
|
|
allocate_eip_address(client, check_mode=check_mode)
|
|
|
|
)
|
|
|
|
if not success:
|
|
|
|
return success, 'False', err_msg, dict()
|
|
|
|
|
|
|
|
elif eip_address or allocation_id:
|
|
|
|
if eip_address and not allocation_id:
|
|
|
|
allocation_id, err_msg = (
|
|
|
|
get_eip_allocation_id_by_address(
|
|
|
|
client, eip_address, check_mode=check_mode
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if not allocation_id:
|
|
|
|
success = False
|
|
|
|
changed = False
|
|
|
|
return success, changed, err_msg, dict()
|
|
|
|
|
|
|
|
existing_gateways, allocation_id_exists = (
|
|
|
|
gateway_in_subnet_exists(
|
|
|
|
client, subnet_id, allocation_id, check_mode=check_mode
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if len(existing_gateways) > 0 and (allocation_id_exists or if_exist_do_not_create):
|
|
|
|
success = True
|
|
|
|
changed = False
|
|
|
|
results = existing_gateways[0]
|
|
|
|
err_msg = (
|
|
|
|
'NAT Gateway {0} already exists in subnet_id {1}'
|
|
|
|
.format(
|
|
|
|
existing_gateways[0]['nat_gateway_id'], subnet_id
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return success, changed, err_msg, results
|
|
|
|
|
|
|
|
success, changed, err_msg, results = create(
|
|
|
|
client, subnet_id, allocation_id, client_token,
|
|
|
|
wait, wait_timeout, if_exist_do_not_create, check_mode=check_mode
|
|
|
|
)
|
|
|
|
|
|
|
|
return success, changed, err_msg, results
|
|
|
|
|
|
|
|
|
|
|
|
def remove(client, nat_gateway_id, wait=False, wait_timeout=0,
|
|
|
|
release_eip=False, check_mode=False):
|
|
|
|
"""Delete an Amazon NAT Gateway.
|
|
|
|
Args:
|
|
|
|
client (botocore.client.EC2): Boto3 client
|
|
|
|
nat_gateway_id (str): The Amazon nat id.
|
|
|
|
|
|
|
|
Kwargs:
|
|
|
|
wait (bool): Wait for the nat to be in the deleted state before returning.
|
|
|
|
wait_timeout (int): Number of seconds to wait, until this timeout is reached.
|
|
|
|
release_eip (bool): Once the nat has been deleted, you can deallocate the eip from the vpc.
|
|
|
|
|
|
|
|
Basic Usage:
|
|
|
|
>>> client = boto3.client('ec2')
|
|
|
|
>>> nat_gw_id = 'nat-03835afb6e31df79b'
|
|
|
|
>>> remove(client, nat_gw_id, wait=True, wait_timeout=500, release_eip=True)
|
|
|
|
[
|
|
|
|
true,
|
|
|
|
"",
|
|
|
|
{
|
|
|
|
"nat_gateway_id": "nat-03835afb6e31df79b",
|
|
|
|
"subnet_id": "subnet-w4t12897",
|
|
|
|
"nat_gateway_addresses": [
|
|
|
|
{
|
|
|
|
"public_ip": "52.87.29.36",
|
|
|
|
"network_interface_id": "eni-5579742d",
|
|
|
|
"private_ip": "10.0.0.102",
|
|
|
|
"allocation_id": "eipalloc-36014da3"
|
|
|
|
}
|
|
|
|
],
|
|
|
|
"state": "deleted",
|
|
|
|
"create_time": "2016-03-05T00:33:21.209000+00:00",
|
|
|
|
"delete_time": "2016-03-05T00:36:37.329000+00:00",
|
|
|
|
"vpc_id": "vpc-w68571b5"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
Tuple (bool, str, list)
|
|
|
|
"""
|
|
|
|
params = {
|
|
|
|
'NatGatewayId': nat_gateway_id
|
|
|
|
}
|
|
|
|
success = False
|
|
|
|
changed = False
|
|
|
|
err_msg = ""
|
|
|
|
results = list()
|
|
|
|
states = ['pending', 'available' ]
|
|
|
|
try:
|
|
|
|
exist, _, gw = (
|
|
|
|
get_nat_gateways(
|
|
|
|
client, nat_gateway_id=nat_gateway_id,
|
|
|
|
states=states, check_mode=check_mode
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if exist and len(gw) == 1:
|
|
|
|
results = gw[0]
|
|
|
|
if not check_mode:
|
|
|
|
client.delete_nat_gateway(**params)
|
|
|
|
|
|
|
|
allocation_id = (
|
|
|
|
results['nat_gateway_addresses'][0]['allocation_id']
|
|
|
|
)
|
|
|
|
changed = True
|
|
|
|
success = True
|
|
|
|
err_msg = (
|
|
|
|
'NAT gateway {0} is in a deleting state. Delete was successfull'
|
|
|
|
.format(nat_gateway_id)
|
|
|
|
)
|
|
|
|
|
|
|
|
if wait:
|
|
|
|
status_achieved, err_msg, results = (
|
|
|
|
wait_for_status(
|
|
|
|
client, wait_timeout, nat_gateway_id, 'deleted',
|
|
|
|
check_mode=check_mode
|
|
|
|
)
|
|
|
|
)
|
|
|
|
if status_achieved:
|
|
|
|
err_msg = (
|
|
|
|
'NAT gateway {0} was deleted successfully'
|
|
|
|
.format(nat_gateway_id)
|
|
|
|
)
|
|
|
|
|
|
|
|
except botocore.exceptions.ClientError as e:
|
|
|
|
err_msg = str(e)
|
|
|
|
|
|
|
|
if release_eip:
|
|
|
|
eip_released, eip_err = (
|
|
|
|
release_address(client, allocation_id, check_mode)
|
|
|
|
)
|
|
|
|
if not eip_released:
|
|
|
|
err_msg = (
|
|
|
|
"{0}: Failed to release EIP {1}: {2}"
|
|
|
|
.format(err_msg, allocation_id, eip_err)
|
|
|
|
)
|
|
|
|
success = False
|
|
|
|
|
|
|
|
return success, changed, err_msg, results
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
argument_spec = ec2_argument_spec()
|
|
|
|
argument_spec.update(dict(
|
|
|
|
subnet_id=dict(type='str'),
|
|
|
|
eip_address=dict(type='str'),
|
|
|
|
allocation_id=dict(type='str'),
|
|
|
|
if_exist_do_not_create=dict(type='bool', default=False),
|
|
|
|
state=dict(default='present', choices=['present', 'absent']),
|
|
|
|
wait=dict(type='bool', default=False),
|
|
|
|
wait_timeout=dict(type='int', default=320, required=False),
|
|
|
|
release_eip=dict(type='bool', default=False),
|
|
|
|
nat_gateway_id=dict(type='str'),
|
|
|
|
client_token=dict(type='str'),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
module = AnsibleModule(
|
|
|
|
argument_spec=argument_spec,
|
|
|
|
supports_check_mode=True,
|
|
|
|
mutually_exclusive=[
|
|
|
|
['allocation_id', 'eip_address']
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
# Validate Requirements
|
|
|
|
if not HAS_BOTO3:
|
|
|
|
module.fail_json(msg='botocore/boto3 is required.')
|
|
|
|
|
|
|
|
state = module.params.get('state').lower()
|
|
|
|
check_mode = module.check_mode
|
|
|
|
subnet_id = module.params.get('subnet_id')
|
|
|
|
allocation_id = module.params.get('allocation_id')
|
|
|
|
eip_address = module.params.get('eip_address')
|
|
|
|
nat_gateway_id = module.params.get('nat_gateway_id')
|
|
|
|
wait = module.params.get('wait')
|
|
|
|
wait_timeout = module.params.get('wait_timeout')
|
|
|
|
release_eip = module.params.get('release_eip')
|
|
|
|
client_token = module.params.get('client_token')
|
|
|
|
if_exist_do_not_create = module.params.get('if_exist_do_not_create')
|
|
|
|
|
|
|
|
try:
|
|
|
|
region, ec2_url, aws_connect_kwargs = (
|
|
|
|
get_aws_connection_info(module, boto3=True)
|
|
|
|
)
|
|
|
|
client = (
|
|
|
|
boto3_conn(
|
|
|
|
module, conn_type='client', resource='ec2',
|
|
|
|
region=region, endpoint=ec2_url, **aws_connect_kwargs
|
|
|
|
)
|
|
|
|
)
|
|
|
|
except botocore.exceptions.ClientError as e:
|
|
|
|
module.fail_json(msg="Boto3 Client Error - " + str(e.msg))
|
|
|
|
|
|
|
|
changed = False
|
|
|
|
err_msg = ''
|
|
|
|
|
|
|
|
if state == 'present':
|
|
|
|
if not subnet_id:
|
|
|
|
module.fail_json(msg='subnet_id is required for creation')
|
|
|
|
|
|
|
|
success, changed, err_msg, results = (
|
|
|
|
pre_create(
|
|
|
|
client, subnet_id, allocation_id, eip_address,
|
|
|
|
if_exist_do_not_create, wait, wait_timeout,
|
|
|
|
client_token, check_mode=check_mode
|
|
|
|
)
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
if not nat_gateway_id:
|
|
|
|
module.fail_json(msg='nat_gateway_id is required for removal')
|
|
|
|
|
|
|
|
else:
|
|
|
|
success, changed, err_msg, results = (
|
|
|
|
remove(
|
|
|
|
client, nat_gateway_id, wait, wait_timeout, release_eip,
|
|
|
|
check_mode=check_mode
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
if not success:
|
|
|
|
module.fail_json(
|
|
|
|
msg=err_msg, success=success, changed=changed
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
module.exit_json(
|
|
|
|
msg=err_msg, success=success, changed=changed, **results
|
|
|
|
)
|
|
|
|
|
|
|
|
# import module snippets
|
|
|
|
from ansible.module_utils.basic import *
|
|
|
|
from ansible.module_utils.ec2 import *
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|