@ -14,21 +14,24 @@ ANSIBLE_METADATA = {'metadata_version': '1.1',
DOCUMENTATION = '''
DOCUMENTATION = '''
- - -
- - -
module : lightsail
module : lightsail
short_description : Create or delete a virtual machine instance in AWS Lightsail
short_description : Manage instances in AWS Lightsail
description :
description :
- Creates or instances in AWS Lightsail and optionally wait for it to be ' running ' .
- Manage instances in AWS Lightsail .
- Instance tagging is not yet supported in this module .
version_added : " 2.4 "
version_added : " 2.4 "
author : " Nick Ball (@nickball) "
author :
- " Nick Ball (@nickball) "
- " Prasad Katti (@prasadkatti) "
options :
options :
state :
state :
description :
description :
- Indicate desired state of the target .
- Indicate desired state of the target .
- I ( rebooted ) and I ( restarted ) are aliases .
default : present
default : present
choices : [ ' present ' , ' absent ' , ' running ' , ' restarted ' , ' stopped' ]
choices : [ ' present ' , ' absent ' , ' running ' , ' restarted ' , ' rebooted' , ' stopped' ]
type : str
type : str
name :
name :
description :
description : Name of the instance .
- Name of the instance .
required : true
required : true
type : str
type : str
zone :
zone :
@ -53,11 +56,13 @@ options:
key_pair_name :
key_pair_name :
description :
description :
- Name of the key pair to use with the instance .
- Name of the key pair to use with the instance .
- If I ( state = present ) and a key_pair_name is not provided , the default keypair from the region will be used .
type : str
type : str
wait :
wait :
description :
description :
- Wait for the instance to be in state ' running ' before returning .
- Wait for the instance to be in state ' running ' before returning .
- If I ( wait = false ) an ip_address may not be returned .
- If I ( wait = false ) an ip_address may not be returned .
- Has no effect when I ( state = rebooted ) or I ( state = absent ) .
type : bool
type : bool
default : true
default : true
wait_timeout :
wait_timeout :
@ -67,7 +72,6 @@ options:
type : int
type : int
requirements :
requirements :
- " python >= 2.6 "
- boto3
- boto3
extends_documentation_fragment :
extends_documentation_fragment :
@ -77,30 +81,23 @@ extends_documentation_fragment:
EXAMPLES = '''
EXAMPLES = '''
# Create a new Lightsail instance , register the instance details
# Create a new Lightsail instance
- lightsail :
- lightsail :
state : present
state : present
name : my instance
name : my _ instance
region : us - east - 1
region : us - east - 1
zone : us - east - 1 a
zone : us - east - 1 a
blueprint_id : ubuntu_16_04
blueprint_id : ubuntu_16_04
bundle_id : nano_1_0
bundle_id : nano_1_0
key_pair_name : id_rsa
key_pair_name : id_rsa
user_data : " echo ' hello world ' > /home/ubuntu/test.txt "
user_data : " echo ' hello world ' > /home/ubuntu/test.txt "
wait_timeout : 500
register : my_instance
register : my_instance
- debug :
# Delete an instance
msg : " Name is {{ my_instance.instance.name }} "
- debug :
msg : " IP is {{ my_instance.instance.public_ip_address }} "
# Delete an instance if present
- lightsail :
- lightsail :
state : absent
state : absent
region : us - east - 1
region : us - east - 1
name : my instance
name : my_instance
'''
'''
@ -159,321 +156,184 @@ instance:
'''
'''
import time
import time
import traceback
try :
try :
import botocore
import botocore
HAS_BOTOCORE = True
except ImportError :
HAS_BOTOCORE = False
try :
import boto3
except ImportError :
except ImportError :
# will be caught by imported HAS_BOTO3
# will be caught by AnsibleAWSModule
pass
pass
from ansible . module_utils . basic import AnsibleModule
from ansible . module_utils . aws . core import AnsibleAWSModule
from ansible . module_utils . ec2 import ( ec2_argument_spec , get_aws_connection_info , boto3_conn ,
from ansible . module_utils . ec2 import camel_dict_to_snake_dict
HAS_BOTO3 , camel_dict_to_snake_dict )
def create_instance ( module , client , instance_name ) :
"""
Create an instance
module : Ansible module object
client : authenticated lightsail connection object
instance_name : name of instance to delete
Returns a dictionary of instance information
about the new instance .
"""
def find_instance_info ( module , client , instance_name , fail_if_not_found = False ) :
changed = False
# Check if instance already exists
inst = None
try :
try :
inst = _find_instance_info ( client , instance_name )
res = client . get_instance ( instanceName = instance_name )
except botocore . exceptions . ClientError as e :
except botocore . exceptions . ClientError as e :
if e . response [ ' Error ' ] [ ' Code ' ] != ' NotFoundException ' :
if e . response [ ' Error ' ] [ ' Code ' ] == ' NotFoundException ' and not fail_if_not_found :
module . fail_json ( msg = ' Error finding instance {0} , error: {1} ' . format ( instance_name , e ) )
return None
module . fail_json_aws ( e )
return res [ ' instance ' ]
zone = module . params . get ( ' zone ' )
blueprint_id = module . params . get ( ' blueprint_id ' )
bundle_id = module . params . get ( ' bundle_id ' )
key_pair_name = module . params . get ( ' key_pair_name ' )
user_data = module . params . get ( ' user_data ' )
user_data = ' ' if user_data is None else user_data
resp = None
def wait_for_instance_state ( module , client , instance_name , states ) :
if inst is None :
"""
` states ` is a list of instance states that we are waiting for .
"""
wait_timeout = module . params . get ( ' wait_timeout ' )
wait_max = time . time ( ) + wait_timeout
while wait_max > time . time ( ) :
try :
try :
resp = client . create_instances (
instance = find_instance_info ( module , client , instance_name )
instanceNames = [
if instance [ ' state ' ] [ ' name ' ] in states :
instance_name
break
] ,
time . sleep ( 5 )
availabilityZone = zone ,
blueprintId = blueprint_id ,
bundleId = bundle_id ,
userData = user_data ,
keyPairName = key_pair_name ,
)
resp = resp [ ' operations ' ] [ 0 ]
except botocore . exceptions . ClientError as e :
except botocore . exceptions . ClientError as e :
module . fail_json ( msg = ' Unable to create instance {0} , error: {1} ' . format ( instance_name , e ) )
module . fail_json_aws ( e )
changed = True
else :
module . fail_json ( msg = ' Timed out waiting for instance " {0} " to get to one of the following states - '
' {1} ' . format ( instance_name , states ) )
inst = _find_instance_info ( client , instance_name )
return ( changed , inst )
def create_instance ( module , client , instance_name ) :
inst = find_instance_info ( module , client , instance_name )
if inst :
module . exit_json ( changed = False , instance = camel_dict_to_snake_dict ( inst ) )
else :
create_params = { ' instanceNames ' : [ instance_name ] ,
' availabilityZone ' : module . params . get ( ' zone ' ) ,
' blueprintId ' : module . params . get ( ' blueprint_id ' ) ,
' bundleId ' : module . params . get ( ' bundle_id ' ) ,
' userData ' : module . params . get ( ' user_data ' ) }
def delete_instance ( module , client , instance_name ) :
key_pair_name = module . params . get ( ' key_pair_name ' )
"""
if key_pair_name :
Terminates an instance
create_params [ ' keyPairName ' ] = key_pair_nam e
module : Ansible module object
try :
client : authenticated lightsail connection object
client . create_instances ( * * create_params )
instance_name : name of instance to delete
except botocore . exceptions . ClientError as e :
module . fail_json_aws ( e )
Returns a dictionary of instance information
wait = module . params . get ( ' wait ' )
about the instance deleted ( pre - deletion ) .
if wait :
desired_states = [ ' running ' ]
wait_for_instance_state ( module , client , instance_name , desired_states )
inst = find_instance_info ( module , client , instance_name , fail_if_not_found = True )
If the instance to be deleted is running
module . exit_json ( changed = True , instance = camel_dict_to_snake_dict ( inst ) )
" changed " will be set to False .
"""
# It looks like deleting removes the instance immediately, nothing to wait for
def delete_instance ( module , client , instance_name ) :
wait = module . params . get ( ' wait ' )
wait_timeout = int ( module . params . get ( ' wait_timeout ' ) )
wait_max = time . time ( ) + wait_timeout
changed = False
changed = False
inst = None
inst = find_instance_info ( module , client , instance_name )
if inst is None :
module . exit_json ( changed = changed , instance = { } )
# Wait for instance to exit transition state before deleting
desired_states = [ ' running ' , ' stopped ' ]
wait_for_instance_state ( module , client , instance_name , desired_states )
try :
try :
inst = _find_instance_info ( client , instance_name )
client . delete_instance ( instanceName = instance_name )
changed = True
except botocore . exceptions . ClientError as e :
except botocore . exceptions . ClientError as e :
if e . response [ ' Error ' ] [ ' Code ' ] != ' NotFoundException ' :
module . fail_json_aws ( e )
module . fail_json ( msg = ' Error finding instance {0} , error: {1} ' . format ( instance_name , e ) )
# If instance doesn't exist, then return with 'changed:false'
module . exit_json ( changed = changed , instance = camel_dict_to_snake_dict ( inst ) )
if not inst :
return changed , { }
# Wait for instance to exit transition state before deleting
if wait :
while wait_max > time . time ( ) and inst is not None and inst [ ' state ' ] [ ' name ' ] in ( ' pending ' , ' stopping ' ) :
try :
time . sleep ( 5 )
inst = _find_instance_info ( client , instance_name )
except botocore . exceptions . ClientError as e :
if e . response [ ' ResponseMetadata ' ] [ ' HTTPStatusCode ' ] == " 403 " :
module . fail_json ( msg = " Failed to delete instance {0} . Check that you have permissions to perform the operation. " . format ( instance_name ) ,
exception = traceback . format_exc ( ) )
elif e . response [ ' Error ' ] [ ' Code ' ] == " RequestExpired " :
module . fail_json ( msg = " RequestExpired: Failed to delete instance {0} . " . format ( instance_name ) , exception = traceback . format_exc ( ) )
# sleep and retry
time . sleep ( 10 )
# Attempt to delete
if inst is not None :
while not changed and ( ( wait and wait_max > time . time ( ) ) or ( not wait ) ) :
try :
client . delete_instance ( instanceName = instance_name )
changed = True
except botocore . exceptions . ClientError as e :
module . fail_json ( msg = ' Error deleting instance {0} , error: {1} ' . format ( instance_name , e ) )
# Timed out
if wait and not changed and wait_max < = time . time ( ) :
module . fail_json ( msg = " wait for instance delete timeout at %s " % time . asctime ( ) )
return ( changed , inst )
def restart_instance ( module , client , instance_name ) :
def restart_instance ( module , client , instance_name ) :
"""
"""
Reboot an existing instance
Reboot an existing instance
module : Ansible module object
client : authenticated lightsail connection object
instance_name : name of instance to reboot
Returns a dictionary of instance information
about the restarted instance
If the instance was not able to reboot ,
" changed " will be set to False .
Wait will not apply here as this is an OS - level operation
Wait will not apply here as this is an OS - level operation
"""
"""
wait = module . params . get ( ' wait ' )
wait_timeout = int ( module . params . get ( ' wait_timeout ' ) )
wait_max = time . time ( ) + wait_timeout
changed = False
changed = False
inst = None
inst = find_instance_info ( module , client , instance_name , fail_if_not_found = True )
try :
inst = _find_instance_info ( client , instance_name )
except botocore . exceptions . ClientError as e :
if e . response [ ' Error ' ] [ ' Code ' ] != ' NotFoundException ' :
module . fail_json ( msg = ' Error finding instance {0} , error: {1} ' . format ( instance_name , e ) )
# Wait for instance to exit transition state before state change
try :
if wait :
client . reboot_instance ( instanceName = instance_name )
while wait_max > time . time ( ) and inst is not None and inst [ ' state ' ] [ ' name ' ] in ( ' pending ' , ' stopping ' ) :
try :
time . sleep ( 5 )
inst = _find_instance_info ( client , instance_name )
except botocore . exceptions . ClientError as e :
if e . response [ ' ResponseMetadata ' ] [ ' HTTPStatusCode ' ] == " 403 " :
module . fail_json ( msg = " Failed to restart instance {0} . Check that you have permissions to perform the operation. " . format ( instance_name ) ,
exception = traceback . format_exc ( ) )
elif e . response [ ' Error ' ] [ ' Code ' ] == " RequestExpired " :
module . fail_json ( msg = " RequestExpired: Failed to restart instance {0} . " . format ( instance_name ) , exception = traceback . format_exc ( ) )
time . sleep ( 3 )
# send reboot
if inst is not None :
try :
client . reboot_instance ( instanceName = instance_name )
except botocore . exceptions . ClientError as e :
if e . response [ ' Error ' ] [ ' Code ' ] != ' NotFoundException ' :
module . fail_json ( msg = ' Unable to reboot instance {0} , error: {1} ' . format ( instance_name , e ) )
changed = True
changed = True
except botocore . exceptions . ClientError as e :
module . fail_json_aws ( e )
return ( changed , inst )
module . exit_json ( changed = changed , instance = camel_dict_to_snake_dict ( inst ) )
def start stop_instance( module , client , instance_name , state ) :
def start_or_stop_instance ( module , client , instance_name , state ) :
"""
"""
Starts or stops an existing instance
Start or stop an existing instance
module : Ansible module object
client : authenticated lightsail connection object
instance_name : name of instance to start / stop
state : Target state ( " running " or " stopped " )
Returns a dictionary of instance information
about the instance started / stopped
If the instance was not able to state change ,
" changed " will be set to False .
"""
"""
wait = module . params . get ( ' wait ' )
wait_timeout = int ( module . params . get ( ' wait_timeout ' ) )
wait_max = time . time ( ) + wait_timeout
changed = False
changed = False
inst = None
inst = find_instance_info ( module , client , instance_name , fail_if_not_found = True )
try :
inst = _find_instance_info ( client , instance_name )
except botocore . exceptions . ClientError as e :
if e . response [ ' Error ' ] [ ' Code ' ] != ' NotFoundException ' :
module . fail_json ( msg = ' Error finding instance {0} , error: {1} ' . format ( instance_name , e ) )
# Wait for instance to exit transition state before state change
# Wait for instance to exit transition state before state change
if wait :
desired_states = [ ' running ' , ' stopped ' ]
while wait_max > time . time ( ) and inst is not None and inst [ ' state ' ] [ ' name ' ] in ( ' pending ' , ' stopping ' ) :
wait_for_instance_state ( module , client , instance_name , desired_states )
try :
time . sleep ( 5 )
inst = _find_instance_info ( client , instance_name )
except botocore . exceptions . ClientError as e :
if e . response [ ' ResponseMetadata ' ] [ ' HTTPStatusCode ' ] == " 403 " :
module . fail_json ( msg = " Failed to start/stop instance {0} . Check that you have permissions to perform the operation " . format ( instance_name ) ,
exception = traceback . format_exc ( ) )
elif e . response [ ' Error ' ] [ ' Code ' ] == " RequestExpired " :
module . fail_json ( msg = " RequestExpired: Failed to start/stop instance {0} . " . format ( instance_name ) , exception = traceback . format_exc ( ) )
time . sleep ( 1 )
# Try state change
# Try state change
if inst is not None and inst [ ' state ' ] [ ' name ' ] != state :
if inst and inst [ ' state ' ] [ ' name ' ] != state :
try :
try :
if state == ' running ' :
if state == ' running ' :
client . start_instance ( instanceName = instance_name )
client . start_instance ( instanceName = instance_name )
else :
else :
client . stop_instance ( instanceName = instance_name )
client . stop_instance ( instanceName = instance_name )
except botocore . exceptions . ClientError as e :
except botocore . exceptions . ClientError as e :
module . fail_json ( msg = ' Unable to change state for instance {0} , error: {1} ' . format ( instance_name , e ) )
module . fail_json_aws ( e )
changed = True
changed = True
# Grab current instance info
# Grab current instance info
inst = _find_instance_info ( client , instance_name )
inst = find_instance_info ( module , client , instance_name )
return ( changed , inst )
def core ( module ) :
region , ec2_url , aws_connect_kwargs = get_aws_connection_info ( module , boto3 = True )
if not region :
module . fail_json ( msg = ' region must be specified ' )
client = None
wait = module . params . get ( ' wait ' )
try :
if wait :
client = boto3_conn ( module , conn_type = ' client ' , resource = ' lightsail ' ,
desired_states = [ state ]
region = region , endpoint = ec2_url , * * aws_connect_kwargs )
wait_for_instance_state ( module , client , instance_name , desired_states )
except ( botocore . exceptions . ClientError , botocore . exceptions . ValidationError ) as e :
inst = find_instance_info ( module , client , instance_name , fail_if_not_found = True )
module . fail_json ( msg = ' Failed while connecting to the lightsail service: %s ' % e , exception = traceback . format_exc ( ) )
changed = False
state = module . params [ ' state ' ]
name = module . params [ ' name ' ]
if state == ' absent ' :
changed , instance_dict = delete_instance ( module , client , name )
elif state in ( ' running ' , ' stopped ' ) :
changed , instance_dict = startstop_instance ( module , client , name , state )
elif state == ' restarted ' :
changed , instance_dict = restart_instance ( module , client , name )
elif state == ' present ' :
changed , instance_dict = create_instance ( module , client , name )
module . exit_json ( changed = changed , instance = camel_dict_to_snake_dict ( instance_dict ) )
module . exit_json ( changed = changed , instance = camel_dict_to_snake_dict ( inst ) )
def _find_instance_info ( client , instance_name ) :
''' handle exceptions where this function is called '''
inst = None
try :
inst = client . get_instance ( instanceName = instance_name )
except botocore . exceptions . ClientError as e :
raise
return inst [ ' instance ' ]
def main ( ) :
def main ( ) :
argument_spec = ec2_argument_spec ( )
argument_spec . update ( dict (
argument_spec = dict (
name = dict ( type = ' str ' , required = True ) ,
name = dict ( type = ' str ' , required = True ) ,
state = dict ( type = ' str ' , default = ' present ' , choices = [ ' present ' , ' absent ' , ' stopped ' , ' running ' , ' restarted ' ] ) ,
state = dict ( type = ' str ' , default = ' present ' , choices = [ ' present ' , ' absent ' , ' stopped ' , ' running ' , ' restarted ' ,
' rebooted ' ] ) ,
zone = dict ( type = ' str ' ) ,
zone = dict ( type = ' str ' ) ,
blueprint_id = dict ( type = ' str ' ) ,
blueprint_id = dict ( type = ' str ' ) ,
bundle_id = dict ( type = ' str ' ) ,
bundle_id = dict ( type = ' str ' ) ,
key_pair_name = dict ( type = ' str ' ) ,
key_pair_name = dict ( type = ' str ' ) ,
user_data = dict ( type = ' str ' ),
user_data = dict ( type = ' str ' , default = ' ' ) ,
wait = dict ( type = ' bool ' , default = True ) ,
wait = dict ( type = ' bool ' , default = True ) ,
wait_timeout = dict ( default = 300 , type = ' int ' ) ,
wait_timeout = dict ( default = 300 , type = ' int ' ) ,
) )
)
module = AnsibleModule ( argument_spec = argument_spec )
module = AnsibleAWSModule ( argument_spec = argument_spec ,
required_if = [ [ ' state ' , ' present ' , ( ' zone ' , ' blueprint_id ' , ' bundle_id ' ) ] ] )
if not HAS_BOTO3 :
client = module . client ( ' lightsail ' )
module . fail_json ( msg = ' Python module " boto3 " is missing, please install it ' )
if not HAS_BOTOCORE :
name = module . params . get ( ' name ' )
module . fail_json ( msg = ' Python module " botocore " is missing, please install it ' )
state = module . params . get ( ' state ' )
try :
if state == ' present ' :
core ( module )
create_instance ( module , client , name )
except ( botocore . exceptions . ClientError , Exception ) as e :
elif state == ' absent ' :
module . fail_json ( msg = str ( e ) , exception = traceback . format_exc ( ) )
delete_instance ( module , client , name )
elif state in ( ' running ' , ' stopped ' ) :
start_or_stop_instance ( module , client , name , state )
elif state in ( ' restarted ' , ' rebooted ' ) :
restart_instance ( module , client , name )
if __name__ == ' __main__ ' :
if __name__ == ' __main__ ' :