@ -18,10 +18,11 @@ short_description: configure AWS virtual private clouds
description :
- Create or terminates AWS virtual private clouds . This module has a dependency on python - boto .
version_added : " 1.4 "
deprecated : > -
Deprecated in 2.3 . Use M ( ec2_vpc_net ) along with supporting modules including
M ( ec2_vpc_igw ) , M ( ec2_vpc_route_table ) , M ( ec2_vpc_subnet ) , M ( ec2_vpc_dhcp_options ) ,
M ( ec2_vpc_nat_gateway ) , M ( ec2_vpc_nacl ) .
deprecated :
removed_in : " 2.5 "
why : Replaced by dedicated modules .
alternative : Use M ( ec2_vpc_net ) along with supporting modules including M ( ec2_vpc_igw ) , M ( ec2_vpc_route_table ) , M ( ec2_vpc_subnet ) ,
M ( ec2_vpc_dhcp_options ) , M ( ec2_vpc_nat_gateway ) , M ( ec2_vpc_nacl ) .
options :
cidr_block :
description :
@ -159,605 +160,7 @@ EXAMPLES = '''
# the delete will fail until those dependencies are removed.
'''
import time
try :
import boto
import boto . ec2
import boto . vpc
from boto . exception import EC2ResponseError
HAS_BOTO = True
except ImportError :
HAS_BOTO = False
from ansible . module_utils . basic import AnsibleModule
from ansible . module_utils . ec2 import connect_to_aws , ec2_argument_spec , get_aws_connection_info
def get_vpc_info ( vpc ) :
"""
Retrieves vpc information from an instance
ID and returns it as a dictionary
"""
return ( {
' id ' : vpc . id ,
' cidr_block ' : vpc . cidr_block ,
' dhcp_options_id ' : vpc . dhcp_options_id ,
' region ' : vpc . region . name ,
' state ' : vpc . state ,
} )
def find_vpc ( module , vpc_conn , vpc_id = None , cidr = None ) :
"""
Finds a VPC that matches a specific id or cidr + tags
module : AnsibleModule object
vpc_conn : authenticated VPCConnection connection object
Returns :
A VPC object that matches either an ID or CIDR and one or more tag values
"""
if vpc_id is None and cidr is None :
module . fail_json (
msg = ' You must specify either a vpc_id or a cidr block + list of unique tags, aborting '
)
found_vpcs = [ ]
resource_tags = module . params . get ( ' resource_tags ' )
# Check for existing VPC by cidr_block or id
if vpc_id is not None :
found_vpcs = vpc_conn . get_all_vpcs ( None , { ' vpc-id ' : vpc_id , ' state ' : ' available ' , } )
else :
previous_vpcs = vpc_conn . get_all_vpcs ( None , { ' cidr ' : cidr , ' state ' : ' available ' } )
for vpc in previous_vpcs :
# Get all tags for each of the found VPCs
vpc_tags = dict ( ( t . name , t . value ) for t in vpc_conn . get_all_tags ( filters = { ' resource-id ' : vpc . id } ) )
# If the supplied list of ID Tags match a subset of the VPC Tags, we found our VPC
if resource_tags and set ( resource_tags . items ( ) ) . issubset ( set ( vpc_tags . items ( ) ) ) :
found_vpcs . append ( vpc )
found_vpc = None
if len ( found_vpcs ) == 1 :
found_vpc = found_vpcs [ 0 ]
if len ( found_vpcs ) > 1 :
module . fail_json ( msg = ' Found more than one vpc based on the supplied criteria, aborting ' )
return ( found_vpc )
def routes_match ( rt_list = None , rt = None , igw = None ) :
"""
Check if the route table has all routes as in given list
rt_list : A list if routes provided in the module
rt : The Remote route table object
igw : The internet gateway object for this vpc
Returns :
True when there provided routes and remote routes are the same .
False when provided routes and remote routes are different .
"""
local_routes = [ ]
remote_routes = [ ]
for route in rt_list :
route_kwargs = {
' gateway_id ' : None ,
' instance_id ' : None ,
' interface_id ' : None ,
' vpc_peering_connection_id ' : None ,
' state ' : ' active '
}
if route [ ' gw ' ] == ' igw ' :
route_kwargs [ ' gateway_id ' ] = igw . id
elif route [ ' gw ' ] . startswith ( ' i- ' ) :
route_kwargs [ ' instance_id ' ] = route [ ' gw ' ]
elif route [ ' gw ' ] . startswith ( ' eni- ' ) :
route_kwargs [ ' interface_id ' ] = route [ ' gw ' ]
elif route [ ' gw ' ] . startswith ( ' pcx- ' ) :
route_kwargs [ ' vpc_peering_connection_id ' ] = route [ ' gw ' ]
else :
route_kwargs [ ' gateway_id ' ] = route [ ' gw ' ]
route_kwargs [ ' destination_cidr_block ' ] = route [ ' dest ' ]
local_routes . append ( route_kwargs )
for j in rt . routes :
remote_routes . append ( j . __dict__ )
match = [ ]
for i in local_routes :
change = " false "
for j in remote_routes :
if set ( i . items ( ) ) . issubset ( set ( j . items ( ) ) ) :
change = " true "
match . append ( change )
if ' false ' in match :
return False
else :
return True
def rtb_changed ( route_tables = None , vpc_conn = None , module = None , vpc = None , igw = None ) :
"""
Checks if the remote routes match the local routes .
route_tables : Route_tables parameter in the module
vpc_conn : The VPC connection object
module : The module object
vpc : The vpc object for this route table
igw : The internet gateway object for this vpc
Returns :
True when there is difference between the provided routes and remote routes and if subnet associations are different .
False when both routes and subnet associations matched .
"""
# We add a one for the main table
rtb_len = len ( route_tables ) + 1
remote_rtb_len = len ( vpc_conn . get_all_route_tables ( filters = { ' vpc_id ' : vpc . id } ) )
if remote_rtb_len != rtb_len :
return True
for rt in route_tables :
rt_id = None
for sn in rt [ ' subnets ' ] :
rsn = vpc_conn . get_all_subnets ( filters = { ' cidr ' : sn , ' vpc_id ' : vpc . id } )
if len ( rsn ) != 1 :
module . fail_json (
msg = ' The subnet {0} to associate with route_table {1} '
' does not exist, aborting ' . format ( sn , rt )
)
nrt = vpc_conn . get_all_route_tables ( filters = { ' vpc_id ' : vpc . id , ' association.subnet-id ' : rsn [ 0 ] . id } )
if not nrt :
return True
else :
nrt = nrt [ 0 ]
if not rt_id :
rt_id = nrt . id
if not routes_match ( rt [ ' routes ' ] , nrt , igw ) :
return True
continue
else :
if rt_id == nrt . id :
continue
else :
return True
return True
return False
def create_vpc ( module , vpc_conn ) :
"""
Creates a new or modifies an existing VPC .
module : AnsibleModule object
vpc_conn : authenticated VPCConnection connection object
Returns :
A dictionary with information
about the VPC and subnets that were launched
"""
id = module . params . get ( ' vpc_id ' )
cidr_block = module . params . get ( ' cidr_block ' )
instance_tenancy = module . params . get ( ' instance_tenancy ' )
dns_support = module . params . get ( ' dns_support ' )
dns_hostnames = module . params . get ( ' dns_hostnames ' )
subnets = module . params . get ( ' subnets ' )
internet_gateway = module . params . get ( ' internet_gateway ' )
route_tables = module . params . get ( ' route_tables ' )
vpc_spec_tags = module . params . get ( ' resource_tags ' )
wait = module . params . get ( ' wait ' )
wait_timeout = int ( module . params . get ( ' wait_timeout ' ) )
changed = False
# Check for existing VPC by cidr_block + tags or id
previous_vpc = find_vpc ( module , vpc_conn , id , cidr_block )
if previous_vpc is not None :
changed = False
vpc = previous_vpc
else :
changed = True
try :
vpc = vpc_conn . create_vpc ( cidr_block , instance_tenancy )
# wait here until the vpc is available
pending = True
wait_timeout = time . time ( ) + wait_timeout
while wait and wait_timeout > time . time ( ) and pending :
try :
pvpc = vpc_conn . get_all_vpcs ( vpc . id )
if hasattr ( pvpc , ' state ' ) :
if pvpc . state == " available " :
pending = False
elif hasattr ( pvpc [ 0 ] , ' state ' ) :
if pvpc [ 0 ] . state == " available " :
pending = False
# sometimes vpc_conn.create_vpc() will return a vpc that can't be found yet by vpc_conn.get_all_vpcs()
# when that happens, just wait a bit longer and try again
except boto . exception . BotoServerError as e :
if e . error_code != ' InvalidVpcID.NotFound ' :
raise
if pending :
time . sleep ( 5 )
if wait and wait_timeout < = time . time ( ) :
# waiting took too long
module . fail_json ( msg = " wait for vpc availability timeout on %s " % time . asctime ( ) )
except boto . exception . BotoServerError as e :
module . fail_json ( msg = " %s : %s " % ( e . error_code , e . error_message ) )
# Done with base VPC, now change to attributes and features.
# Add resource tags
vpc_tags = dict ( ( t . name , t . value ) for t in vpc_conn . get_all_tags ( filters = { ' resource-id ' : vpc . id } ) )
if not set ( vpc_spec_tags . items ( ) ) . issubset ( set ( vpc_tags . items ( ) ) ) :
new_tags = { }
for ( key , value ) in set ( vpc_spec_tags . items ( ) ) :
if ( key , value ) not in set ( vpc_tags . items ( ) ) :
new_tags [ key ] = value
if new_tags :
vpc_conn . create_tags ( vpc . id , new_tags )
# boto doesn't appear to have a way to determine the existing
# value of the dns attributes, so we just set them.
# It also must be done one at a time.
vpc_conn . modify_vpc_attribute ( vpc . id , enable_dns_support = dns_support )
vpc_conn . modify_vpc_attribute ( vpc . id , enable_dns_hostnames = dns_hostnames )
# Process all subnet properties
if subnets is not None :
if not isinstance ( subnets , list ) :
module . fail_json ( msg = ' subnets needs to be a list of cidr blocks ' )
current_subnets = vpc_conn . get_all_subnets ( filters = { ' vpc_id ' : vpc . id } )
# First add all new subnets
for subnet in subnets :
add_subnet = True
subnet_tags_current = True
new_subnet_tags = subnet . get ( ' resource_tags ' , { } )
subnet_tags_delete = [ ]
for csn in current_subnets :
if subnet [ ' cidr ' ] == csn . cidr_block :
add_subnet = False
# Check if AWS subnet tags are in playbook subnet tags
existing_tags_subset_of_new_tags = ( set ( csn . tags . items ( ) ) . issubset ( set ( new_subnet_tags . items ( ) ) ) )
# Check if subnet tags in playbook are in AWS subnet tags
new_tags_subset_of_existing_tags = ( set ( new_subnet_tags . items ( ) ) . issubset ( set ( csn . tags . items ( ) ) ) )
if existing_tags_subset_of_new_tags is False :
try :
for item in csn . tags . items ( ) :
if item not in new_subnet_tags . items ( ) :
subnet_tags_delete . append ( item )
subnet_tags_delete = [ key [ 0 ] for key in subnet_tags_delete ]
delete_subnet_tag = vpc_conn . delete_tags ( csn . id , subnet_tags_delete )
changed = True
except EC2ResponseError as e :
module . fail_json ( msg = ' Unable to delete resource tag, error {0} ' . format ( e ) )
# Add new subnet tags if not current
if new_tags_subset_of_existing_tags is False :
try :
changed = True
create_subnet_tag = vpc_conn . create_tags ( csn . id , new_subnet_tags )
except EC2ResponseError as e :
module . fail_json ( msg = ' Unable to create resource tag, error: {0} ' . format ( e ) )
if add_subnet :
try :
new_subnet = vpc_conn . create_subnet ( vpc . id , subnet [ ' cidr ' ] , subnet . get ( ' az ' , None ) )
new_subnet_tags = subnet . get ( ' resource_tags ' , { } )
if new_subnet_tags :
# Sometimes AWS takes its time to create a subnet and so using new subnets's id
# to create tags results in exception.
# boto doesn't seem to refresh 'state' of the newly created subnet, i.e.: it's always 'pending'
# so i resorted to polling vpc_conn.get_all_subnets with the id of the newly added subnet
while len ( vpc_conn . get_all_subnets ( filters = { ' subnet-id ' : new_subnet . id } ) ) == 0 :
time . sleep ( 0.1 )
vpc_conn . create_tags ( new_subnet . id , new_subnet_tags )
changed = True
except EC2ResponseError as e :
module . fail_json ( msg = ' Unable to create subnet {0} , error: {1} ' . format ( subnet [ ' cidr ' ] , e ) )
# Now delete all absent subnets
for csubnet in current_subnets :
delete_subnet = True
for subnet in subnets :
if csubnet . cidr_block == subnet [ ' cidr ' ] :
delete_subnet = False
if delete_subnet :
try :
vpc_conn . delete_subnet ( csubnet . id )
changed = True
except EC2ResponseError as e :
module . fail_json ( msg = ' Unable to delete subnet {0} , error: {1} ' . format ( csubnet . cidr_block , e ) )
# Handle Internet gateway (create/delete igw)
igw = None
igw_id = None
igws = vpc_conn . get_all_internet_gateways ( filters = { ' attachment.vpc-id ' : vpc . id } )
if len ( igws ) > 1 :
module . fail_json ( msg = ' EC2 returned more than one Internet Gateway for id %s , aborting ' % vpc . id )
if internet_gateway :
if len ( igws ) != 1 :
try :
igw = vpc_conn . create_internet_gateway ( )
vpc_conn . attach_internet_gateway ( igw . id , vpc . id )
changed = True
except EC2ResponseError as e :
module . fail_json ( msg = ' Unable to create Internet Gateway, error: {0} ' . format ( e ) )
else :
# Set igw variable to the current igw instance for use in route tables.
igw = igws [ 0 ]
else :
if len ( igws ) > 0 :
try :
vpc_conn . detach_internet_gateway ( igws [ 0 ] . id , vpc . id )
vpc_conn . delete_internet_gateway ( igws [ 0 ] . id )
changed = True
except EC2ResponseError as e :
module . fail_json ( msg = ' Unable to delete Internet Gateway, error: {0} ' . format ( e ) )
if igw is not None :
igw_id = igw . id
# Handle route tables - this may be worth splitting into a
# different module but should work fine here. The strategy to stay
# idempotent is to basically build all the route tables as
# defined, track the route table ids, and then run through the
# remote list of route tables and delete any that we didn't
# create. This shouldn't interrupt traffic in theory, but is the
# only way to really work with route tables over time that I can
# think of without using painful aws ids. Hopefully boto will add
# the replace-route-table API to make this smoother and
# allow control of the 'main' routing table.
if route_tables is not None :
rtb_needs_change = rtb_changed ( route_tables , vpc_conn , module , vpc , igw )
if route_tables is not None and rtb_needs_change :
if not isinstance ( route_tables , list ) :
module . fail_json ( msg = ' route tables need to be a list of dictionaries ' )
# Work through each route table and update/create to match dictionary array
all_route_tables = [ ]
for rt in route_tables :
try :
new_rt = vpc_conn . create_route_table ( vpc . id )
new_rt_tags = rt . get ( ' resource_tags ' , None )
if new_rt_tags :
vpc_conn . create_tags ( new_rt . id , new_rt_tags )
for route in rt [ ' routes ' ] :
route_kwargs = { }
if route [ ' gw ' ] == ' igw ' :
if not internet_gateway :
module . fail_json (
msg = ' You asked for an Internet Gateway '
' (igw) route, but you have no Internet Gateway '
)
route_kwargs [ ' gateway_id ' ] = igw . id
elif route [ ' gw ' ] . startswith ( ' i- ' ) :
route_kwargs [ ' instance_id ' ] = route [ ' gw ' ]
elif route [ ' gw ' ] . startswith ( ' eni- ' ) :
route_kwargs [ ' interface_id ' ] = route [ ' gw ' ]
elif route [ ' gw ' ] . startswith ( ' pcx- ' ) :
route_kwargs [ ' vpc_peering_connection_id ' ] = route [ ' gw ' ]
else :
route_kwargs [ ' gateway_id ' ] = route [ ' gw ' ]
vpc_conn . create_route ( new_rt . id , route [ ' dest ' ] , * * route_kwargs )
# Associate with subnets
for sn in rt [ ' subnets ' ] :
rsn = vpc_conn . get_all_subnets ( filters = { ' cidr ' : sn , ' vpc_id ' : vpc . id } )
if len ( rsn ) != 1 :
module . fail_json (
msg = ' The subnet {0} to associate with route_table {1} '
' does not exist, aborting ' . format ( sn , rt )
)
rsn = rsn [ 0 ]
# Disassociate then associate since we don't have replace
old_rt = vpc_conn . get_all_route_tables (
filters = { ' association.subnet_id ' : rsn . id , ' vpc_id ' : vpc . id }
)
old_rt = [ x for x in old_rt if x . id is not None ]
if len ( old_rt ) == 1 :
old_rt = old_rt [ 0 ]
association_id = None
for a in old_rt . associations :
if a . subnet_id == rsn . id :
association_id = a . id
vpc_conn . disassociate_route_table ( association_id )
vpc_conn . associate_route_table ( new_rt . id , rsn . id )
all_route_tables . append ( new_rt )
changed = True
except EC2ResponseError as e :
module . fail_json (
msg = ' Unable to create and associate route table {0} , error: '
' {1} ' . format ( rt , e )
)
# Now that we are good to go on our new route tables, delete the
# old ones except the 'main' route table as boto can't set the main
# table yet.
all_rts = vpc_conn . get_all_route_tables ( filters = { ' vpc-id ' : vpc . id } )
for rt in all_rts :
if rt . id is None :
continue
delete_rt = True
for newrt in all_route_tables :
if newrt . id == rt . id :
delete_rt = False
break
if delete_rt :
rta = rt . associations
is_main = False
for a in rta :
if a . main :
is_main = True
break
try :
if not is_main :
vpc_conn . delete_route_table ( rt . id )
changed = True
except EC2ResponseError as e :
module . fail_json ( msg = ' Unable to delete old route table {0} , error: {1} ' . format ( rt . id , e ) )
vpc_dict = get_vpc_info ( vpc )
created_vpc_id = vpc . id
returned_subnets = [ ]
current_subnets = vpc_conn . get_all_subnets ( filters = { ' vpc_id ' : vpc . id } )
for sn in current_subnets :
returned_subnets . append ( {
' resource_tags ' : dict ( ( t . name , t . value ) for t in vpc_conn . get_all_tags ( filters = { ' resource-id ' : sn . id } ) ) ,
' cidr ' : sn . cidr_block ,
' az ' : sn . availability_zone ,
' id ' : sn . id ,
} )
if subnets is not None :
# Sort subnets by the order they were listed in the play
order = { }
for idx , val in enumerate ( subnets ) :
order [ val [ ' cidr ' ] ] = idx
# Number of subnets in the play
subnets_in_play = len ( subnets )
returned_subnets . sort ( key = lambda x : order . get ( x [ ' cidr ' ] , subnets_in_play ) )
return ( vpc_dict , created_vpc_id , returned_subnets , igw_id , changed )
def terminate_vpc ( module , vpc_conn , vpc_id = None , cidr = None ) :
"""
Terminates a VPC
module : Ansible module object
vpc_conn : authenticated VPCConnection connection object
vpc_id : a vpc id to terminate
cidr : The cidr block of the VPC - can be used in lieu of an ID
Returns a dictionary of VPC information
about the VPC terminated .
If the VPC to be terminated is available
" changed " will be set to True .
"""
vpc_dict = { }
terminated_vpc_id = ' '
changed = False
vpc = find_vpc ( module , vpc_conn , vpc_id , cidr )
if vpc is not None :
if vpc . state == ' available ' :
terminated_vpc_id = vpc . id
vpc_dict = get_vpc_info ( vpc )
try :
subnets = vpc_conn . get_all_subnets ( filters = { ' vpc_id ' : vpc . id } )
for sn in subnets :
vpc_conn . delete_subnet ( sn . id )
igws = vpc_conn . get_all_internet_gateways (
filters = { ' attachment.vpc-id ' : vpc . id }
)
for igw in igws :
vpc_conn . detach_internet_gateway ( igw . id , vpc . id )
vpc_conn . delete_internet_gateway ( igw . id )
rts = vpc_conn . get_all_route_tables ( filters = { ' vpc_id ' : vpc . id } )
for rt in rts :
rta = rt . associations
is_main = False
for a in rta :
if a . main :
is_main = True
if not is_main :
vpc_conn . delete_route_table ( rt . id )
vpc_conn . delete_vpc ( vpc . id )
except EC2ResponseError as e :
module . fail_json (
msg = ' Unable to delete VPC {0} , error: {1} ' . format ( vpc . id , e )
)
changed = True
vpc_dict [ ' state ' ] = " terminated "
return ( changed , vpc_dict , terminated_vpc_id )
def main ( ) :
argument_spec = ec2_argument_spec ( )
argument_spec . update ( dict (
cidr_block = dict ( ) ,
instance_tenancy = dict ( choices = [ ' default ' , ' dedicated ' ] , default = ' default ' ) ,
wait = dict ( type = ' bool ' , default = False ) ,
wait_timeout = dict ( default = 300 ) ,
dns_support = dict ( type = ' bool ' , default = True ) ,
dns_hostnames = dict ( type = ' bool ' , default = True ) ,
subnets = dict ( type = ' list ' ) ,
vpc_id = dict ( ) ,
internet_gateway = dict ( type = ' bool ' , default = False ) ,
resource_tags = dict ( type = ' dict ' , required = True ) ,
route_tables = dict ( type = ' list ' ) ,
state = dict ( choices = [ ' present ' , ' absent ' ] , default = ' present ' ) ,
)
)
module = AnsibleModule (
argument_spec = argument_spec ,
)
if not HAS_BOTO :
module . fail_json ( msg = ' boto required for this module ' )
state = module . params . get ( ' state ' )
region , ec2_url , aws_connect_kwargs = get_aws_connection_info ( module )
# If we have a region specified, connect to its endpoint.
if region :
try :
vpc_conn = connect_to_aws ( boto . vpc , region , * * aws_connect_kwargs )
except boto . exception . NoAuthHandlerFound as e :
module . fail_json ( msg = str ( e ) )
else :
module . fail_json ( msg = " region must be specified " )
igw_id = None
if module . params . get ( ' state ' ) == ' absent ' :
vpc_id = module . params . get ( ' vpc_id ' )
cidr = module . params . get ( ' cidr_block ' )
( changed , vpc_dict , new_vpc_id ) = terminate_vpc ( module , vpc_conn , vpc_id , cidr )
subnets_changed = None
elif module . params . get ( ' state ' ) == ' present ' :
# Changed is always set to true when provisioning a new VPC
( vpc_dict , new_vpc_id , subnets_changed , igw_id , changed ) = create_vpc ( module , vpc_conn )
module . exit_json ( changed = changed , vpc_id = new_vpc_id , vpc = vpc_dict , igw_id = igw_id , subnets = subnets_changed )
from ansible . module_utils . common . removed import removed_module
if __name__ == ' __main__ ' :
main ( )
removed_module ( )