@ -23,14 +23,21 @@ requirements: [ boto3 ]
options :
options :
az :
az :
description :
description :
- " The availability zone for the subnet. Only required when state=present. "
- " The availability zone for the subnet. "
required : false
required : false
default : null
default : null
cidr :
cidr :
description :
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
required : false
default : null
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 :
tags :
description :
description :
- " A dict of tags to apply to the subnet. Any tags currently applied to the subnet and not present here will be removed. "
- " 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 ' ]
choices : [ ' present ' , ' absent ' ]
vpc_id :
vpc_id :
description :
description :
- " VPC ID of the VPC in which to create the subnet."
- " VPC ID of the VPC in which to create or delete the subnet."
required : fals e
required : tru e
default : null
default : null
map_public :
map_public :
description :
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
required : false
default : false
default : false
version_added : " 2.4 "
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 :
extends_documentation_fragment :
- aws
- aws
- ec2
- ec2
@ -77,8 +108,112 @@ EXAMPLES = '''
vpc_id : vpc - 123456
vpc_id : vpc - 123456
cidr : 10.0 .1 .16 / 28
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 - 1 a
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 time
import traceback
import traceback
@ -90,7 +225,7 @@ except ImportError:
from ansible . module_utils . aws . core import AnsibleAWSModule
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 ,
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 ,
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 ) :
def get_subnet_info ( subnet ) :
@ -110,6 +245,15 @@ def get_subnet_info(subnet):
subnet [ ' id ' ] = subnet [ ' subnet_id ' ]
subnet [ ' id ' ] = subnet [ ' subnet_id ' ]
del 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
return subnet
@ -118,56 +262,75 @@ def describe_subnets_with_backoff(client, **params):
return client . describe_subnets ( * * params )
return client . describe_subnets ( * * params )
def subnet_exists ( conn , module , subnet_id ) :
def create_subnet ( conn , module , vpc_id , cidr , ipv6_cidr = None , az = None ) :
filters = ansible_dict_to_boto3_filter_list ( { ' subnet-id ' : subnet_id } )
wait = module . params [ ' wait ' ]
try :
wait_timeout = module . params [ ' wait_timeout ' ]
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
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 :
if az :
params [ ' AvailabilityZone ' ] = az
params [ ' AvailabilityZone ' ] = az
try :
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 :
except ( botocore . exceptions . ClientError , botocore . exceptions . BotoCoreError ) as e :
module . fail_json_aws ( e , msg = " Couldn ' t create subnet " )
module . fail_json_aws ( e , msg = " Couldn ' t create subnet " )
# Sometimes AWS takes its time to create a subnet and so using
# Sometimes AWS takes its time to create a subnet and so using
# new subnets's id to do things like create tags results in
# new subnets's id to do things like create tags results in
# exception. boto doesn't seem to refresh 'state' of the newly
# exception.
# created subnet, i.e.: it's always 'pending'.
if wait and subnet . get ( ' state ' ) != ' available ' :
subnet = False
delay = 5
while subnet is False :
max_attempts = wait_timeout / delay
subnet = subnet_exists ( conn , module , new_subnet [ ' id ' ] )
waiter_config = dict ( Delay = delay , MaxAttempts = max_attempts )
time . sleep ( 0.1 )
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
return subnet
def ensure_tags ( conn , module , subnet , tags , add_only , check_mode ) :
def ensure_tags ( conn , module , subnet , tags , purge_tags ) :
cur_tags = subnet [ ' tags ' ]
changed = False
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 )
to_delete = dict ( ( k , cur_tags [ k ] ) for k in cur_tags if k not in tags )
if to_update :
if to_delete and not add_only and not check_mode :
try :
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 :
except ( botocore . exceptions . ClientError , botocore . exceptions . BotoCoreError ) as e :
module . fail_json_aws ( e , msg = " Couldn ' t delete tags " )
module . fail_json_aws ( e , msg = " Couldn ' t crea te 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_delete :
if to_add and not check_mode :
try :
try :
conn . create_tags ( Resources = [ subnet [ ' id ' ] ] , Tags = ansible_dict_to_boto3_tag_list ( to_add ) )
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 :
except ( botocore . exceptions . ClientError , botocore . exceptions . BotoCoreError ) as e :
module . fail_json_aws ( e , msg = " Couldn ' t create tags " )
module . fail_json_aws ( e , msg = " Couldn ' t delete tags " )
return changed
def ensure_map_public ( conn , module , subnet , map_public , check_mode ) :
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 " )
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 ) :
def get_matching_subnet ( conn , module , vpc_id , cidr ) :
filters = ansible_dict_to_boto3_filter_list ( { ' vpc-id ' : vpc_id , ' cidr-block ' : cidr } )
filters = ansible_dict_to_boto3_filter_list ( { ' vpc-id ' : vpc_id , ' cidr-block ' : cidr } )
try :
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 :
except ( botocore . exceptions . ClientError , botocore . exceptions . BotoCoreError ) as e :
module . fail_json_aws ( e , msg = " Couldn ' t get matching subnet " )
module . fail_json_aws ( e , msg = " Couldn ' t get matching subnet " )
if len ( subnets ) > 0 :
if subnets :
return subnets [ 0 ]
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
changed = False
if subnet is None :
if subnet is None :
if not check_mode :
if not module. check_mode:
subnet = create_subnet ( conn , module , vpc_id , cidr , az , 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
changed = True
# Subnet will be None when check_mode is true
# Subnet will be None when check_mode is true
if subnet is None :
if subnet is None :
@ -205,30 +432,39 @@ def ensure_subnet_present(conn, module, vpc_id, cidr, az, tags, map_public, chec
' changed ' : changed ,
' changed ' : changed ,
' subnet ' : { }
' subnet ' : { }
}
}
if map_public != subnet [ ' map_public_ip_on_launch ' ] :
ensure_map_public ( conn , module , subnet , map_public , check_mode )
if module . params [ ' ipv6_cidr ' ] != subnet . get ( ' ipv6_cidr_block ' ) :
subnet [ ' map_public_ip_on_launch ' ] = map_public
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
changed = True
if tags != subnet [ ' tags ' ] :
if module . params [ ' assign_instances_ipv6 ' ] != subnet . get ( ' assign_ipv6_address_on_creation ' ) :
ensure_tags ( conn , module , subnet , tags , False , check_mode )
ensure_assign_ipv6_on_create ( conn , module , subnet , module . params [ ' assign_instances_ipv6 ' ] , module . check_mode )
subnet [ ' tags ' ] = tags
changed = True
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 {
return {
' changed ' : changed ,
' changed ' : changed ,
' subnet ' : subnet
' subnet ' : subnet
}
}
def ensure_subnet_absent ( conn , module , vpc_id , cidr , check_mode ):
def ensure_subnet_absent ( conn , module ):
subnet = get_matching_subnet ( conn , module , vpc_id, cidr)
subnet = get_matching_subnet ( conn , module , module. params [ ' vpc_id' ] , module. params [ ' cidr' ] )
if subnet is None :
if subnet is None :
return { ' changed ' : False }
return { ' changed ' : False }
try :
try :
if not check_mode:
if not module. check_mode:
conn . delete_subnet ( SubnetId = subnet [ ' id ' ] , DryRun = check_mode )
conn . delete_subnet ( SubnetId = subnet [ ' id ' ] )
return { ' changed ' : True }
return { ' changed ' : True }
except ( botocore . exceptions . ClientError , botocore . exceptions . BotoCoreError ) as e :
except ( botocore . exceptions . ClientError , botocore . exceptions . BotoCoreError ) as e :
module . fail_json_aws ( e , msg = " Couldn ' t delete subnet " )
module . fail_json_aws ( e , msg = " Couldn ' t delete subnet " )
@ -240,36 +476,35 @@ def main():
dict (
dict (
az = dict ( default = None , required = False ) ,
az = dict ( default = None , required = False ) ,
cidr = dict ( default = None , required = True ) ,
cidr = dict ( default = None , required = True ) ,
ipv6_cidr = dict ( default = ' ' , required = False ) ,
state = dict ( default = ' present ' , choices = [ ' present ' , ' absent ' ] ) ,
state = dict ( default = ' present ' , choices = [ ' present ' , ' absent ' ] ) ,
tags = dict ( default = { } , required = False , type = ' dict ' , aliases = [ ' resource_tags ' ] ) ,
tags = dict ( default = { } , required = False , type = ' dict ' , aliases = [ ' resource_tags ' ] ) ,
vpc_id = dict ( default = None , required = True ) ,
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 ' ] ) ]
region, ec2_url , aws_connect_params = get_aws_connection_info ( module , boto3 = True )
module = AnsibleAWSModule ( argument_spec = argument_spec , supports_check_mode = True , required_if = required_if )
if region :
if module . params . get ( ' assign_instances_ipv6 ' ) and not module . params . get ( ' ipv6_cidr ' ) :
connection = boto3_conn ( module , conn_type = ' client ' , resource = ' ec2 ' , region = region , endpoint = ec2_url , * * aws_connect_params )
module . fail_json ( msg = " assign_instances_ipv6 is True but ipv6_cidr is None or an empty string " )
else :
module . fail_json ( msg = " region must be specified " )
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 )
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 ' )
state = module . params . get ( ' state ' )
map_public = module . params . get ( ' map_public ' )
try :
try :
if state == ' present ' :
if state == ' present ' :
result = ensure_subnet_present ( connection , module , vpc_id , cidr , az , tags , map_public ,
result = ensure_subnet_present ( connection , module )
check_mode = module . check_mode )
elif state == ' absent ' :
elif state == ' absent ' :
result = ensure_subnet_absent ( connection , module , vpc_id , cidr ,
result = ensure_subnet_absent ( connection , module )
check_mode = module . check_mode )
except botocore . exceptions . ClientError as e :
except botocore . exceptions . ClientError as e :
module . fail_json ( msg = e . message , exception = traceback . format_exc ( ) ,
module . fail_json ( msg = e . message , exception = traceback . format_exc ( ) ,
* * camel_dict_to_snake_dict ( e . response ) )
* * camel_dict_to_snake_dict ( e . response ) )