[cloud] Add IPv6 support for ec2_vpc_subnet module(#30444)
* Add integration test suite for ec2_vpc_subnet * wrap boto3 connection in try/except update module documentation and add RETURN docs add IPv6 support to VPC subnet module rename ipv6cidr to ipv6_cidr, use required_if for parameter testing, update some failure messages to be more descriptive DryRun mode was removed from this function a while ago but exception handling was still checking for it, removed add wait and timeout for subnet creation process fixup the ipv6 cidr disassociation logic a bit per review update RETURN values per review added module parameter check removed DryRun parameter from boto3 call since it would always be false here fix subnet wait loop add a purge_tags parameter, fix the ensure_tags function, update to use compare_aws_tags func fix tags type error per review remove **kwargs use in create_subnet function per review * rebased on #31870, fixed merge conflicts, and updated error messages * fixes to pass tests * add test for failure on invalid ipv6 block and update tags test for purge_tags=true function * fix pylint issue * fix exception handling error when run with python3 * add ipv6 tests and fix module code * Add permissions to hacking/aws_config/testing_policies/ec2-policy.json for adding IPv6 cidr blocks to VPC and subnets * fix type in tests and update assert conditional to check entire returned value * add AWS_SESSION_TOKEN into environment for aws cli commands to work in CI * remove key and value options from call to boto3_tag_list_to_ansible_dict * remove wait loop and use boto3 EC2 waiter * remove unused register: result vars * revert az argument default value to original setting default=None
This commit is contained in:
parent
23b1dbacaf
commit
cfbe9c8aee
3 changed files with 499 additions and 87 deletions
|
@ -10,6 +10,8 @@
|
|||
"ec2:AllocateAddress",
|
||||
"ec2:AssociateAddress",
|
||||
"ec2:AssociateRouteTable",
|
||||
"ec2:AssociateVpcCidrBlock",
|
||||
"ec2:AssociateSubnetCidrBlock",
|
||||
"ec2:CreateImage",
|
||||
"ec2:AttachInternetGateway",
|
||||
"ec2:CreateInternetGateway",
|
||||
|
|
|
@ -23,14 +23,21 @@ requirements: [ boto3 ]
|
|||
options:
|
||||
az:
|
||||
description:
|
||||
- "The availability zone for the subnet. Only required when state=present."
|
||||
- "The availability zone for the subnet."
|
||||
required: false
|
||||
default: null
|
||||
cidr:
|
||||
description:
|
||||
- "The CIDR block for the subnet. E.g. 192.0.2.0/24. Only required when state=present."
|
||||
- "The CIDR block for the subnet. E.g. 192.0.2.0/24."
|
||||
required: false
|
||||
default: null
|
||||
ipv6_cidr:
|
||||
description:
|
||||
- "The IPv6 CIDR block for the subnet. The VPC must have a /56 block assigned and this value must be a valid IPv6 /64 that falls in the VPC range."
|
||||
- "Required if I(assign_instances_ipv6=true)"
|
||||
required: false
|
||||
default: null
|
||||
version_added: "2.5"
|
||||
tags:
|
||||
description:
|
||||
- "A dict of tags to apply to the subnet. Any tags currently applied to the subnet and not present here will be removed."
|
||||
|
@ -45,15 +52,39 @@ options:
|
|||
choices: [ 'present', 'absent' ]
|
||||
vpc_id:
|
||||
description:
|
||||
- "VPC ID of the VPC in which to create the subnet."
|
||||
required: false
|
||||
- "VPC ID of the VPC in which to create or delete the subnet."
|
||||
required: true
|
||||
default: null
|
||||
map_public:
|
||||
description:
|
||||
- "Specify true to indicate that instances launched into the subnet should be assigned public IP address by default."
|
||||
- "Specify true to indicate that instances launched into the subnet should be assigned public IP address by default."
|
||||
required: false
|
||||
default: false
|
||||
version_added: "2.4"
|
||||
assign_instances_ipv6:
|
||||
description:
|
||||
- "Specify true to indicate that instances launched into the subnet should be automatically assigned an IPv6 address."
|
||||
required: false
|
||||
default: false
|
||||
version_added: "2.5"
|
||||
wait:
|
||||
description:
|
||||
- "When specified,I(state=present) module will wait for subnet to be in available state before continuing."
|
||||
required: false
|
||||
default: true
|
||||
version_added: "2.5"
|
||||
wait_timeout:
|
||||
description:
|
||||
- "Number of seconds to wait for subnet to become available I(wait=True)."
|
||||
required: false
|
||||
default: 300
|
||||
version_added: "2.5"
|
||||
purge_tags:
|
||||
description:
|
||||
- Whether or not to remove tags that do not appear in the I(tags) list. Defaults to true.
|
||||
required: false
|
||||
default: true
|
||||
version_added: "2.5"
|
||||
extends_documentation_fragment:
|
||||
- aws
|
||||
- ec2
|
||||
|
@ -77,8 +108,112 @@ EXAMPLES = '''
|
|||
vpc_id: vpc-123456
|
||||
cidr: 10.0.1.16/28
|
||||
|
||||
- name: Create subnet with IPv6 block assigned
|
||||
ec2_vpc_subnet:
|
||||
state: present
|
||||
vpc_id: vpc-123456
|
||||
cidr: 10.1.100.0/24
|
||||
ipv6_cidr: 2001:db8:0:102::/64
|
||||
|
||||
- name: Remove IPv6 block assigned to subnet
|
||||
ec2_vpc_subnet:
|
||||
state: present
|
||||
vpc_id: vpc-123456
|
||||
cidr: 10.1.100.0/24
|
||||
ipv6_cidr: ''
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
subnet:
|
||||
description: Dictionary of subnet values
|
||||
returned: I(state=present)
|
||||
type: complex
|
||||
contains:
|
||||
id:
|
||||
description: Subnet resource id
|
||||
returned: I(state=present)
|
||||
type: string
|
||||
sample: subnet-b883b2c4
|
||||
cidr_block:
|
||||
description: The IPv4 CIDR of the Subnet
|
||||
returned: I(state=present)
|
||||
type: string
|
||||
sample: "10.0.0.0/16"
|
||||
ipv6_cidr_block:
|
||||
description: The IPv6 CIDR block actively associated with the Subnet
|
||||
returned: I(state=present)
|
||||
type: string
|
||||
sample: "2001:db8:0:102::/64"
|
||||
availability_zone:
|
||||
description: Availability zone of the Subnet
|
||||
returned: I(state=present)
|
||||
type: string
|
||||
sample: us-east-1a
|
||||
state:
|
||||
description: state of the Subnet
|
||||
returned: I(state=present)
|
||||
type: string
|
||||
sample: available
|
||||
tags:
|
||||
description: tags attached to the Subnet, includes name
|
||||
returned: I(state=present)
|
||||
type: dict
|
||||
sample: {"Name": "My Subnet", "env": "staging"}
|
||||
map_public_ip_on_launch:
|
||||
description: whether public IP is auto-assigned to new instances
|
||||
returned: I(state=present)
|
||||
type: boolean
|
||||
sample: false
|
||||
assign_ipv6_address_on_creation:
|
||||
description: whether IPv6 address is auto-assigned to new instances
|
||||
returned: I(state=present)
|
||||
type: boolean
|
||||
sample: false
|
||||
vpc_id:
|
||||
description: the id of the VPC where this Subnet exists
|
||||
returned: I(state=present)
|
||||
type: string
|
||||
sample: vpc-67236184
|
||||
available_ip_address_count:
|
||||
description: number of available IPv4 addresses
|
||||
returned: I(state=present)
|
||||
type: string
|
||||
sample: 251
|
||||
default_for_az:
|
||||
description: indicates whether this is the default Subnet for this Availability Zone
|
||||
returned: I(state=present)
|
||||
type: boolean
|
||||
sample: false
|
||||
ipv6_association_id:
|
||||
description: The IPv6 association ID for the currently associated CIDR
|
||||
returned: I(state=present)
|
||||
type: string
|
||||
sample: subnet-cidr-assoc-b85c74d2
|
||||
ipv6_cidr_block_association_set:
|
||||
description: An array of IPv6 cidr block association set information.
|
||||
returned: I(state=present)
|
||||
type: complex
|
||||
contains:
|
||||
association_id:
|
||||
description: The association ID
|
||||
returned: always
|
||||
type: string
|
||||
ipv6_cidr_block:
|
||||
description: The IPv6 CIDR block that is associated with the subnet.
|
||||
returned: always
|
||||
type: string
|
||||
ipv6_cidr_block_state:
|
||||
description: A hash/dict that contains a single item. The state of the cidr block association.
|
||||
returned: always
|
||||
type: dict
|
||||
contains:
|
||||
state:
|
||||
description: The CIDR block association state.
|
||||
returned: always
|
||||
type: string
|
||||
'''
|
||||
|
||||
|
||||
import time
|
||||
import traceback
|
||||
|
||||
|
@ -90,7 +225,7 @@ except ImportError:
|
|||
from ansible.module_utils.aws.core import AnsibleAWSModule
|
||||
from ansible.module_utils.ec2 import (ansible_dict_to_boto3_filter_list, ansible_dict_to_boto3_tag_list,
|
||||
ec2_argument_spec, camel_dict_to_snake_dict, get_aws_connection_info,
|
||||
boto3_conn, boto3_tag_list_to_ansible_dict, AWSRetry)
|
||||
boto3_conn, boto3_tag_list_to_ansible_dict, compare_aws_tags, AWSRetry)
|
||||
|
||||
|
||||
def get_subnet_info(subnet):
|
||||
|
@ -110,6 +245,15 @@ def get_subnet_info(subnet):
|
|||
subnet['id'] = subnet['subnet_id']
|
||||
del subnet['subnet_id']
|
||||
|
||||
subnet['ipv6_cidr_block'] = ''
|
||||
subnet['ipv6_association_id'] = ''
|
||||
ipv6set = subnet.get('ipv6_cidr_block_association_set')
|
||||
if ipv6set:
|
||||
for item in ipv6set:
|
||||
if item.get('ipv6_cidr_block_state', {}).get('state') in ('associated', 'associating'):
|
||||
subnet['ipv6_cidr_block'] = item['ipv6_cidr_block']
|
||||
subnet['ipv6_association_id'] = item['association_id']
|
||||
|
||||
return subnet
|
||||
|
||||
|
||||
|
@ -118,56 +262,75 @@ def describe_subnets_with_backoff(client, **params):
|
|||
return client.describe_subnets(**params)
|
||||
|
||||
|
||||
def subnet_exists(conn, module, subnet_id):
|
||||
filters = ansible_dict_to_boto3_filter_list({'subnet-id': subnet_id})
|
||||
try:
|
||||
subnets = get_subnet_info(describe_subnets_with_backoff(conn, Filters=filters))
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't check if subnet exists")
|
||||
if len(subnets) > 0 and 'state' in subnets[0] and subnets[0]['state'] == "available":
|
||||
return subnets[0]
|
||||
else:
|
||||
return False
|
||||
def create_subnet(conn, module, vpc_id, cidr, ipv6_cidr=None, az=None):
|
||||
wait = module.params['wait']
|
||||
wait_timeout = module.params['wait_timeout']
|
||||
|
||||
params = dict(VpcId=vpc_id,
|
||||
CidrBlock=cidr)
|
||||
|
||||
if ipv6_cidr:
|
||||
params['Ipv6CidrBlock'] = ipv6_cidr
|
||||
|
||||
def create_subnet(conn, module, vpc_id, cidr, az, check_mode):
|
||||
if check_mode:
|
||||
return
|
||||
params = dict(VpcId=vpc_id, CidrBlock=cidr)
|
||||
if az:
|
||||
params['AvailabilityZone'] = az
|
||||
|
||||
try:
|
||||
new_subnet = get_subnet_info(conn.create_subnet(**params))
|
||||
subnet = get_subnet_info(conn.create_subnet(**params))
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't create subnet")
|
||||
|
||||
# Sometimes AWS takes its time to create a subnet and so using
|
||||
# new subnets's id to do things like create tags results in
|
||||
# exception. boto doesn't seem to refresh 'state' of the newly
|
||||
# created subnet, i.e.: it's always 'pending'.
|
||||
subnet = False
|
||||
while subnet is False:
|
||||
subnet = subnet_exists(conn, module, new_subnet['id'])
|
||||
time.sleep(0.1)
|
||||
# exception.
|
||||
if wait and subnet.get('state') != 'available':
|
||||
delay = 5
|
||||
max_attempts = wait_timeout / delay
|
||||
waiter_config = dict(Delay=delay, MaxAttempts=max_attempts)
|
||||
waiter = conn.get_waiter('subnet_available')
|
||||
try:
|
||||
waiter.wait(SubnetIds=[subnet['id']], WaiterConfig=waiter_config)
|
||||
subnet['state'] = 'available'
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json(msg="Create subnet action timed out waiting for Subnet to become available.")
|
||||
|
||||
return subnet
|
||||
|
||||
|
||||
def ensure_tags(conn, module, subnet, tags, add_only, check_mode):
|
||||
cur_tags = subnet['tags']
|
||||
def ensure_tags(conn, module, subnet, tags, purge_tags):
|
||||
changed = False
|
||||
|
||||
to_delete = dict((k, cur_tags[k]) for k in cur_tags if k not in tags)
|
||||
if to_delete and not add_only and not check_mode:
|
||||
filters = ansible_dict_to_boto3_filter_list({'resource-id': subnet['id'], 'resource-type': 'subnet'})
|
||||
try:
|
||||
cur_tags = conn.describe_tags(Filters=filters)
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't describe tags")
|
||||
|
||||
to_update, to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(cur_tags.get('Tags')), tags, purge_tags)
|
||||
|
||||
if to_update:
|
||||
try:
|
||||
conn.delete_tags(Resources=[subnet['id']], Tags=ansible_dict_to_boto3_tag_list(to_delete))
|
||||
if not module.check_mode:
|
||||
conn.create_tags(Resources=[subnet['id']], Tags=ansible_dict_to_boto3_tag_list(to_update))
|
||||
|
||||
changed = True
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't create tags")
|
||||
|
||||
if to_delete:
|
||||
try:
|
||||
if not module.check_mode:
|
||||
tags_list = []
|
||||
for key in to_delete:
|
||||
tags_list.append({'Key': key})
|
||||
|
||||
conn.delete_tags(Resources=[subnet['id']], Tags=tags_list)
|
||||
|
||||
changed = True
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't delete tags")
|
||||
|
||||
to_add = dict((k, tags[k]) for k in tags if k not in cur_tags or cur_tags[k] != tags[k])
|
||||
if to_add and not check_mode:
|
||||
try:
|
||||
conn.create_tags(Resources=[subnet['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")
|
||||
return changed
|
||||
|
||||
|
||||
def ensure_map_public(conn, module, subnet, map_public, check_mode):
|
||||
|
@ -179,25 +342,89 @@ def ensure_map_public(conn, module, subnet, map_public, check_mode):
|
|||
module.fail_json_aws(e, msg="Couldn't modify subnet attribute")
|
||||
|
||||
|
||||
def ensure_assign_ipv6_on_create(conn, module, subnet, assign_instances_ipv6, check_mode):
|
||||
if check_mode:
|
||||
return
|
||||
|
||||
try:
|
||||
conn.modify_subnet_attribute(SubnetId=subnet['id'], AssignIpv6AddressOnCreation={'Value': assign_instances_ipv6})
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't modify subnet attribute")
|
||||
|
||||
|
||||
def disassociate_ipv6_cidr(conn, module, subnet):
|
||||
if subnet.get('assign_ipv6_address_on_creation'):
|
||||
ensure_assign_ipv6_on_create(conn, module, subnet, False, False)
|
||||
|
||||
try:
|
||||
conn.disassociate_subnet_cidr_block(AssociationId=subnet['ipv6_association_id'])
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't disassociate ipv6 cidr block id {0} from subnet {1}"
|
||||
.format(subnet['ipv6_association_id'], subnet['id']))
|
||||
|
||||
|
||||
def ensure_ipv6_cidr_block(conn, module, subnet, ipv6_cidr, check_mode):
|
||||
changed = False
|
||||
|
||||
if subnet['ipv6_association_id'] and not ipv6_cidr:
|
||||
if not check_mode:
|
||||
disassociate_ipv6_cidr(conn, module, subnet)
|
||||
changed = True
|
||||
|
||||
if ipv6_cidr:
|
||||
filters = ansible_dict_to_boto3_filter_list({'ipv6-cidr-block-association.ipv6-cidr-block': ipv6_cidr,
|
||||
'vpc-id': subnet['vpc_id']})
|
||||
|
||||
try:
|
||||
check_subnets = get_subnet_info(describe_subnets_with_backoff(conn, Filters=filters))
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't get subnet info")
|
||||
|
||||
if check_subnets and check_subnets[0]['ipv6_cidr_block']:
|
||||
module.fail_json(msg="The IPv6 CIDR '{0}' conflicts with another subnet".format(ipv6_cidr))
|
||||
|
||||
if subnet['ipv6_association_id']:
|
||||
if not check_mode:
|
||||
disassociate_ipv6_cidr(conn, module, subnet)
|
||||
changed = True
|
||||
|
||||
try:
|
||||
if not check_mode:
|
||||
associate_resp = conn.associate_subnet_cidr_block(SubnetId=subnet['id'], Ipv6CidrBlock=ipv6_cidr)
|
||||
changed = True
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't associate ipv6 cidr {0} to {1}".format(ipv6_cidr, subnet['id']))
|
||||
|
||||
if associate_resp.get('Ipv6CidrBlockAssociation', {}).get('AssociationId'):
|
||||
subnet['ipv6_association_id'] = associate_resp['Ipv6CidrBlockAssociation']['AssociationId']
|
||||
subnet['ipv6_cidr_block'] = associate_resp['Ipv6CidrBlockAssociation']['Ipv6CidrBlock']
|
||||
if subnet['ipv6_cidr_block_association_set']:
|
||||
subnet['ipv6_cidr_block_association_set'][0] = camel_dict_to_snake_dict(associate_resp['Ipv6CidrBlockAssociation'])
|
||||
else:
|
||||
subnet['ipv6_cidr_block_association_set'].append(camel_dict_to_snake_dict(associate_resp['Ipv6CidrBlockAssociation']))
|
||||
|
||||
return changed
|
||||
|
||||
|
||||
def get_matching_subnet(conn, module, vpc_id, cidr):
|
||||
filters = ansible_dict_to_boto3_filter_list({'vpc-id': vpc_id, 'cidr-block': cidr})
|
||||
try:
|
||||
subnets = get_subnet_info(conn.describe_subnets(Filters=filters))
|
||||
subnets = get_subnet_info(describe_subnets_with_backoff(conn, Filters=filters))
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't get matching subnet")
|
||||
|
||||
if len(subnets) > 0:
|
||||
if subnets:
|
||||
return subnets[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def ensure_subnet_present(conn, module, vpc_id, cidr, az, tags, map_public, check_mode):
|
||||
subnet = get_matching_subnet(conn, module, vpc_id, cidr)
|
||||
def ensure_subnet_present(conn, module):
|
||||
subnet = get_matching_subnet(conn, module, module.params['vpc_id'], module.params['cidr'])
|
||||
changed = False
|
||||
if subnet is None:
|
||||
if not check_mode:
|
||||
subnet = create_subnet(conn, module, vpc_id, cidr, az, check_mode)
|
||||
if not module.check_mode:
|
||||
subnet = create_subnet(conn, module, module.params['vpc_id'], module.params['cidr'], ipv6_cidr=module.params['ipv6_cidr'], az=module.params['az'])
|
||||
changed = True
|
||||
# Subnet will be None when check_mode is true
|
||||
if subnet is None:
|
||||
|
@ -205,30 +432,39 @@ def ensure_subnet_present(conn, module, vpc_id, cidr, az, tags, map_public, chec
|
|||
'changed': changed,
|
||||
'subnet': {}
|
||||
}
|
||||
if map_public != subnet['map_public_ip_on_launch']:
|
||||
ensure_map_public(conn, module, subnet, map_public, check_mode)
|
||||
subnet['map_public_ip_on_launch'] = map_public
|
||||
|
||||
if module.params['ipv6_cidr'] != subnet.get('ipv6_cidr_block'):
|
||||
if ensure_ipv6_cidr_block(conn, module, subnet, module.params['ipv6_cidr'], module.check_mode):
|
||||
changed = True
|
||||
|
||||
if module.params['map_public'] != subnet['map_public_ip_on_launch']:
|
||||
ensure_map_public(conn, module, subnet, module.params['map_public'], module.check_mode)
|
||||
changed = True
|
||||
|
||||
if tags != subnet['tags']:
|
||||
ensure_tags(conn, module, subnet, tags, False, check_mode)
|
||||
subnet['tags'] = tags
|
||||
if module.params['assign_instances_ipv6'] != subnet.get('assign_ipv6_address_on_creation'):
|
||||
ensure_assign_ipv6_on_create(conn, module, subnet, module.params['assign_instances_ipv6'], module.check_mode)
|
||||
changed = True
|
||||
|
||||
if module.params['tags'] != subnet['tags']:
|
||||
if ensure_tags(conn, module, subnet, module.params['tags'], module.params['purge_tags']):
|
||||
changed = True
|
||||
|
||||
subnet = get_matching_subnet(conn, module, module.params['vpc_id'], module.params['cidr'])
|
||||
|
||||
return {
|
||||
'changed': changed,
|
||||
'subnet': subnet
|
||||
}
|
||||
|
||||
|
||||
def ensure_subnet_absent(conn, module, vpc_id, cidr, check_mode):
|
||||
subnet = get_matching_subnet(conn, module, vpc_id, cidr)
|
||||
def ensure_subnet_absent(conn, module):
|
||||
subnet = get_matching_subnet(conn, module, module.params['vpc_id'], module.params['cidr'])
|
||||
if subnet is None:
|
||||
return {'changed': False}
|
||||
|
||||
try:
|
||||
if not check_mode:
|
||||
conn.delete_subnet(SubnetId=subnet['id'], DryRun=check_mode)
|
||||
if not module.check_mode:
|
||||
conn.delete_subnet(SubnetId=subnet['id'])
|
||||
return {'changed': True}
|
||||
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Couldn't delete subnet")
|
||||
|
@ -240,36 +476,35 @@ def main():
|
|||
dict(
|
||||
az=dict(default=None, required=False),
|
||||
cidr=dict(default=None, required=True),
|
||||
ipv6_cidr=dict(default='', required=False),
|
||||
state=dict(default='present', choices=['present', 'absent']),
|
||||
tags=dict(default={}, required=False, type='dict', aliases=['resource_tags']),
|
||||
vpc_id=dict(default=None, required=True),
|
||||
map_public=dict(default=False, required=False, type='bool')
|
||||
map_public=dict(default=False, required=False, type='bool'),
|
||||
assign_instances_ipv6=dict(default=False, required=False, type='bool'),
|
||||
wait=dict(type='bool', default=True),
|
||||
wait_timeout=dict(type='int', default=300, required=False),
|
||||
purge_tags=dict(default=True, type='bool')
|
||||
)
|
||||
)
|
||||
|
||||
module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True)
|
||||
required_if = [('assign_instances_ipv6', True, ['ipv6_cidr'])]
|
||||
|
||||
module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True, required_if=required_if)
|
||||
|
||||
if module.params.get('assign_instances_ipv6') and not module.params.get('ipv6_cidr'):
|
||||
module.fail_json(msg="assign_instances_ipv6 is True but ipv6_cidr is None or an empty string")
|
||||
|
||||
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)
|
||||
|
||||
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")
|
||||
|
||||
vpc_id = module.params.get('vpc_id')
|
||||
tags = module.params.get('tags')
|
||||
cidr = module.params.get('cidr')
|
||||
az = module.params.get('az')
|
||||
state = module.params.get('state')
|
||||
map_public = module.params.get('map_public')
|
||||
|
||||
try:
|
||||
if state == 'present':
|
||||
result = ensure_subnet_present(connection, module, vpc_id, cidr, az, tags, map_public,
|
||||
check_mode=module.check_mode)
|
||||
result = ensure_subnet_present(connection, module)
|
||||
elif state == 'absent':
|
||||
result = ensure_subnet_absent(connection, module, vpc_id, cidr,
|
||||
check_mode=module.check_mode)
|
||||
result = ensure_subnet_absent(connection, module)
|
||||
except botocore.exceptions.ClientError as e:
|
||||
module.fail_json(msg=e.message, exception=traceback.format_exc(),
|
||||
**camel_dict_to_snake_dict(e.response))
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
name: "{{ resource_prefix }}-vpc"
|
||||
state: present
|
||||
cidr_block: "10.232.232.128/26"
|
||||
region: '{{ec2_region}}'
|
||||
region: '{{ ec2_region }}'
|
||||
aws_access_key: '{{ aws_access_key }}'
|
||||
aws_secret_key: '{{ aws_secret_key }}'
|
||||
security_token: '{{ security_token }}'
|
||||
|
@ -33,7 +33,7 @@
|
|||
tags:
|
||||
Name: '{{ec2_vpc_subnet_name}}'
|
||||
Description: '{{ec2_vpc_subnet_description}}'
|
||||
region: '{{ec2_region}}'
|
||||
region: '{{ ec2_region }}'
|
||||
aws_access_key: '{{ aws_access_key }}'
|
||||
aws_secret_key: '{{ aws_secret_key }}'
|
||||
security_token: '{{ security_token }}'
|
||||
|
@ -56,20 +56,41 @@
|
|||
tags:
|
||||
Name: '{{ec2_vpc_subnet_name}}'
|
||||
Description: '{{ec2_vpc_subnet_description}}'
|
||||
region: '{{ec2_region}}'
|
||||
region: '{{ ec2_region }}'
|
||||
aws_access_key: '{{ aws_access_key }}'
|
||||
aws_secret_key: '{{ aws_secret_key }}'
|
||||
security_token: '{{ security_token }}'
|
||||
state: present
|
||||
register: vpc_subnet_recreate
|
||||
|
||||
- name: assert recreation changed nothing (expected changed=true)
|
||||
- name: assert recreation changed nothing (expected changed=false)
|
||||
assert:
|
||||
that:
|
||||
- 'not vpc_subnet_recreate.changed'
|
||||
- 'vpc_subnet_recreate.subnet.id.startswith("subnet-")'
|
||||
- '"Name" in vpc_subnet_recreate.subnet.tags and vpc_subnet_recreate.subnet.tags["Name"] == ec2_vpc_subnet_name'
|
||||
- '"Description" in vpc_subnet_recreate.subnet.tags and vpc_subnet_recreate.subnet.tags["Description"] == ec2_vpc_subnet_description'
|
||||
- 'vpc_subnet_recreate.subnet == vpc_subnet_create.subnet'
|
||||
|
||||
- name: add invalid ipv6 block to subnet (expected failed)
|
||||
ec2_vpc_subnet:
|
||||
cidr: "10.232.232.128/28"
|
||||
az: "{{ ec2_region }}a"
|
||||
vpc_id: "{{ vpc_result.vpc.id }}"
|
||||
ipv6_cidr: 2001:db8::/64
|
||||
tags:
|
||||
Name: '{{ec2_vpc_subnet_name}}'
|
||||
Description: '{{ec2_vpc_subnet_description}}'
|
||||
region: '{{ec2_region}}'
|
||||
aws_access_key: '{{ aws_access_key }}'
|
||||
aws_secret_key: '{{ aws_secret_key }}'
|
||||
security_token: '{{ security_token }}'
|
||||
state: present
|
||||
register: vpc_subnet_ipv6_failed
|
||||
ignore_errors: yes
|
||||
|
||||
- name: assert failure happened (expected failed)
|
||||
assert:
|
||||
that:
|
||||
- 'vpc_subnet_ipv6_failed.failed'
|
||||
- "'Couldn\\'t associate ipv6 cidr' in vpc_subnet_ipv6_failed.msg"
|
||||
|
||||
- name: add a tag (expected changed=true)
|
||||
ec2_vpc_subnet:
|
||||
|
@ -80,7 +101,7 @@
|
|||
Name: '{{ec2_vpc_subnet_name}}'
|
||||
Description: '{{ec2_vpc_subnet_description}}'
|
||||
AnotherTag: SomeValue
|
||||
region: '{{ec2_region}}'
|
||||
region: '{{ ec2_region }}'
|
||||
aws_access_key: '{{ aws_access_key }}'
|
||||
aws_secret_key: '{{ aws_secret_key }}'
|
||||
security_token: '{{ security_token }}'
|
||||
|
@ -95,16 +116,14 @@
|
|||
- '"Description" in vpc_subnet_add_a_tag.subnet.tags and vpc_subnet_add_a_tag.subnet.tags["Description"] == ec2_vpc_subnet_description'
|
||||
- '"AnotherTag" in vpc_subnet_add_a_tag.subnet.tags and vpc_subnet_add_a_tag.subnet.tags["AnotherTag"] == "SomeValue"'
|
||||
|
||||
# We may want to change this behaviour by adding purge_tags to the module
|
||||
# and setting it to false by default
|
||||
- name: remove tags (expected changed=true)
|
||||
- name: remove tags with default purge_tags=true (expected changed=true)
|
||||
ec2_vpc_subnet:
|
||||
cidr: "10.232.232.128/28"
|
||||
az: "{{ ec2_region }}a"
|
||||
vpc_id: "{{ vpc_result.vpc.id }}"
|
||||
tags:
|
||||
AnotherTag: SomeValue
|
||||
region: '{{ec2_region}}'
|
||||
region: '{{ ec2_region }}'
|
||||
aws_access_key: '{{ aws_access_key }}'
|
||||
aws_secret_key: '{{ aws_secret_key }}'
|
||||
security_token: '{{ security_token }}'
|
||||
|
@ -119,12 +138,36 @@
|
|||
- '"Description" not in vpc_subnet_remove_tags.subnet.tags'
|
||||
- '"AnotherTag" in vpc_subnet_remove_tags.subnet.tags and vpc_subnet_remove_tags.subnet.tags["AnotherTag"] == "SomeValue"'
|
||||
|
||||
- name: change tags with purge_tags=false (expected changed=true)
|
||||
ec2_vpc_subnet:
|
||||
cidr: "10.232.232.128/28"
|
||||
az: "{{ ec2_region }}a"
|
||||
vpc_id: "{{ vpc_result.vpc.id }}"
|
||||
tags:
|
||||
Name: '{{ec2_vpc_subnet_name}}'
|
||||
Description: '{{ec2_vpc_subnet_description}}'
|
||||
region: '{{ec2_region}}'
|
||||
aws_access_key: '{{ aws_access_key }}'
|
||||
aws_secret_key: '{{ aws_secret_key }}'
|
||||
security_token: '{{ security_token }}'
|
||||
state: present
|
||||
purge_tags: false
|
||||
register: vpc_subnet_change_tags
|
||||
|
||||
- name: assert tag addition happened (expected changed=true)
|
||||
assert:
|
||||
that:
|
||||
- 'vpc_subnet_change_tags.changed'
|
||||
- '"Name" in vpc_subnet_change_tags.subnet.tags and vpc_subnet_change_tags.subnet.tags["Name"] == ec2_vpc_subnet_name'
|
||||
- '"Description" in vpc_subnet_change_tags.subnet.tags and vpc_subnet_change_tags.subnet.tags["Description"] == ec2_vpc_subnet_description'
|
||||
- '"AnotherTag" in vpc_subnet_change_tags.subnet.tags and vpc_subnet_change_tags.subnet.tags["AnotherTag"] == "SomeValue"'
|
||||
|
||||
- name: test state=absent (expected changed=true)
|
||||
ec2_vpc_subnet:
|
||||
cidr: "10.232.232.128/28"
|
||||
vpc_id: "{{ vpc_result.vpc.id }}"
|
||||
state: absent
|
||||
region: '{{ec2_region}}'
|
||||
region: '{{ ec2_region }}'
|
||||
aws_access_key: '{{ aws_access_key }}'
|
||||
aws_secret_key: '{{ aws_secret_key }}'
|
||||
security_token: '{{ security_token }}'
|
||||
|
@ -166,6 +209,138 @@
|
|||
assert:
|
||||
that:
|
||||
- 'result.changed'
|
||||
|
||||
# FIXME - Replace by creating IPv6 enabled VPC once ec2_vpc_net module supports it.
|
||||
- name: install aws cli - FIXME temporary this should go for a lighterweight solution
|
||||
command: pip install awscli
|
||||
|
||||
- name: Assign an Amazon provided IPv6 CIDR block to the VPC
|
||||
command: aws ec2 associate-vpc-cidr-block --amazon-provided-ipv6-cidr-block --vpc-id '{{ vpc_result.vpc.id }}'
|
||||
environment:
|
||||
AWS_ACCESS_KEY_ID: '{{aws_access_key}}'
|
||||
AWS_SECRET_ACCESS_KEY: '{{aws_secret_key}}'
|
||||
AWS_SESSION_TOKEN: '{{security_token}}'
|
||||
AWS_DEFAULT_REGION: '{{ec2_region}}'
|
||||
|
||||
- name: Get the assigned IPv6 CIDR
|
||||
command: aws ec2 describe-vpcs --vpc-ids '{{ vpc_result.vpc.id }}'
|
||||
environment:
|
||||
AWS_ACCESS_KEY_ID: '{{aws_access_key}}'
|
||||
AWS_SECRET_ACCESS_KEY: '{{aws_secret_key}}'
|
||||
AWS_SESSION_TOKEN: '{{security_token}}'
|
||||
AWS_DEFAULT_REGION: '{{ec2_region}}'
|
||||
register: vpc_ipv6
|
||||
|
||||
- set_fact:
|
||||
vpc_ipv6_cidr: "{{ vpc_ipv6.stdout | from_json | json_query('Vpcs[0].Ipv6CidrBlockAssociationSet[0].Ipv6CidrBlock') }}"
|
||||
|
||||
- name: create subnet with IPv6 (expected changed=true)
|
||||
ec2_vpc_subnet:
|
||||
cidr: "10.232.232.128/28"
|
||||
vpc_id: "{{ vpc_result.vpc.id }}"
|
||||
ipv6_cidr: "{{ vpc_ipv6_cidr | regex_replace('::/56', '::/64') }}"
|
||||
assign_instances_ipv6: true
|
||||
state: present
|
||||
region: '{{ec2_region}}'
|
||||
aws_access_key: '{{ aws_access_key }}'
|
||||
aws_secret_key: '{{ aws_secret_key }}'
|
||||
security_token: '{{ security_token }}'
|
||||
tags:
|
||||
Name: '{{ec2_vpc_subnet_name}}'
|
||||
Description: '{{ec2_vpc_subnet_description}}'
|
||||
register: vpc_subnet_ipv6_create
|
||||
|
||||
- name: assert creation with IPv6 happened (expected changed=true)
|
||||
assert:
|
||||
that:
|
||||
- 'vpc_subnet_ipv6_create'
|
||||
- 'vpc_subnet_ipv6_create.subnet.id.startswith("subnet-")'
|
||||
- "vpc_subnet_ipv6_create.subnet.ipv6_cidr_block == '{{ vpc_ipv6_cidr | regex_replace('::/56', '::/64') }}'"
|
||||
- '"Name" in vpc_subnet_ipv6_create.subnet.tags and vpc_subnet_ipv6_create.subnet.tags["Name"] == ec2_vpc_subnet_name'
|
||||
- '"Description" in vpc_subnet_ipv6_create.subnet.tags and vpc_subnet_ipv6_create.subnet.tags["Description"] == ec2_vpc_subnet_description'
|
||||
- 'vpc_subnet_ipv6_create.subnet.assign_ipv6_address_on_creation'
|
||||
|
||||
- name: recreate subnet (expected changed=false)
|
||||
ec2_vpc_subnet:
|
||||
cidr: "10.232.232.128/28"
|
||||
vpc_id: "{{ vpc_result.vpc.id }}"
|
||||
ipv6_cidr: "{{ vpc_ipv6_cidr | regex_replace('::/56', '::/64') }}"
|
||||
assign_instances_ipv6: true
|
||||
region: '{{ec2_region}}'
|
||||
aws_access_key: '{{ aws_access_key }}'
|
||||
aws_secret_key: '{{ aws_secret_key }}'
|
||||
security_token: '{{ security_token }}'
|
||||
state: present
|
||||
tags:
|
||||
Name: '{{ec2_vpc_subnet_name}}'
|
||||
Description: '{{ec2_vpc_subnet_description}}'
|
||||
register: vpc_subnet_ipv6_recreate
|
||||
|
||||
- name: assert recreation changed nothing (expected changed=false)
|
||||
assert:
|
||||
that:
|
||||
- 'not vpc_subnet_ipv6_recreate.changed'
|
||||
- 'vpc_subnet_ipv6_recreate.subnet == vpc_subnet_ipv6_create.subnet'
|
||||
|
||||
- name: change subnet ipv6 attribute (expected changed=true)
|
||||
ec2_vpc_subnet:
|
||||
cidr: "10.232.232.128/28"
|
||||
vpc_id: "{{ vpc_result.vpc.id }}"
|
||||
ipv6_cidr: "{{ vpc_ipv6_cidr | regex_replace('::/56', '::/64') }}"
|
||||
assign_instances_ipv6: false
|
||||
region: '{{ec2_region}}'
|
||||
aws_access_key: '{{ aws_access_key }}'
|
||||
aws_secret_key: '{{ aws_secret_key }}'
|
||||
security_token: '{{ security_token }}'
|
||||
state: present
|
||||
purge_tags: false
|
||||
register: vpc_change_attribute
|
||||
|
||||
- name: assert assign_instances_ipv6 attribute changed (expected changed=true)
|
||||
assert:
|
||||
that:
|
||||
- 'vpc_change_attribute.changed'
|
||||
- 'not vpc_change_attribute.subnet.assign_ipv6_address_on_creation'
|
||||
|
||||
- name: add second subnet with duplicate ipv6 cidr (expected failure)
|
||||
ec2_vpc_subnet:
|
||||
cidr: "10.232.232.144/28"
|
||||
vpc_id: "{{ vpc_result.vpc.id }}"
|
||||
ipv6_cidr: "{{ vpc_ipv6_cidr | regex_replace('::/56', '::/64') }}"
|
||||
region: '{{ec2_region}}'
|
||||
aws_access_key: '{{ aws_access_key }}'
|
||||
aws_secret_key: '{{ aws_secret_key }}'
|
||||
security_token: '{{ security_token }}'
|
||||
state: present
|
||||
purge_tags: false
|
||||
register: vpc_add_duplicate_ipv6
|
||||
ignore_errors: true
|
||||
|
||||
- name: assert graceful failure (expected failed)
|
||||
assert:
|
||||
that:
|
||||
- 'vpc_add_duplicate_ipv6.failed'
|
||||
- "'The IPv6 CIDR \\'{{ vpc_ipv6_cidr | regex_replace('::/56', '::/64') }}\\' conflicts with another subnet' in vpc_add_duplicate_ipv6.msg"
|
||||
|
||||
- name: remove subnet ipv6 cidr (expected changed=true)
|
||||
ec2_vpc_subnet:
|
||||
cidr: "10.232.232.128/28"
|
||||
vpc_id: "{{ vpc_result.vpc.id }}"
|
||||
region: '{{ec2_region}}'
|
||||
aws_access_key: '{{ aws_access_key }}'
|
||||
aws_secret_key: '{{ aws_secret_key }}'
|
||||
security_token: '{{ security_token }}'
|
||||
state: present
|
||||
purge_tags: false
|
||||
register: vpc_remove_ipv6_cidr
|
||||
|
||||
- name: assert subnet ipv6 cidr removed (expected changed=true)
|
||||
assert:
|
||||
that:
|
||||
- 'vpc_remove_ipv6_cidr.changed'
|
||||
- "vpc_remove_ipv6_cidr.subnet.ipv6_cidr_block == ''"
|
||||
- 'not vpc_remove_ipv6_cidr.subnet.assign_ipv6_address_on_creation'
|
||||
|
||||
always:
|
||||
|
||||
################################################
|
||||
|
@ -177,7 +352,7 @@
|
|||
cidr: "10.232.232.128/28"
|
||||
vpc_id: "{{ vpc_result.vpc.id }}"
|
||||
state: absent
|
||||
region: '{{ec2_region}}'
|
||||
region: '{{ ec2_region }}'
|
||||
aws_access_key: '{{ aws_access_key }}'
|
||||
aws_secret_key: '{{ aws_secret_key }}'
|
||||
security_token: '{{ security_token }}'
|
||||
|
@ -187,7 +362,7 @@
|
|||
name: "{{ resource_prefix }}-vpc"
|
||||
state: absent
|
||||
cidr_block: "10.232.232.128/26"
|
||||
region: '{{ec2_region}}'
|
||||
region: '{{ ec2_region }}'
|
||||
aws_access_key: '{{ aws_access_key }}'
|
||||
aws_secret_key: '{{ aws_secret_key }}'
|
||||
security_token: '{{ security_token }}'
|
||||
|
|
Loading…
Reference in a new issue