#! /usr/bin/python
################################################################################
## instance-action
## Written by N2W Software
## Date: May 2014
## License: You can use/modify/circulate or do whatever you want.
##          Just note that this script is given "As Is" without any warranty
##
## Usage: see README file
##
################################################################################
import sys
import time
import argparse
from boto import ec2
from boto.exception import EC2ResponseError

AWS_ACCESS_KEY = None
AWS_SECRET_KEY = None
AWS_REGION = u'us-east-1'

def confirm_action (action, instance, region):
    """
    Confirms action with user
    """
    
    if action=='start': msg = 'Confirm starting instance %s in region %s [yes/no] ' % (instance, region)
    elif action=='stop': msg = 'Confirm stopping instance %s in region %s [yes/no] ' % (instance, region)
    elif action=='terminate_with_vols': msg = 'Confirm terminating instance %s (region: %s) and deleting of all attached volumes [yes/no] ' % \
                                                (instance, region)
    elif action=='terminate': msg = 'Confirm terminating instance %s (region: %s)[yes/no] ' % (instance, region)
    else:
        sys.stderr.write ('Unknown action %s' % action)
        return False
    
    valid = {'yes':True, 'y':True,
             'no':False, 'n':False}
             
    while True:
        sys.stdout.write (msg)
        choice = raw_input().lower()
        if choice in valid.keys ():
            return valid[choice]
        sys.stdout.write ('Please respond with \'yes\' or \'no\' (or \'y\' or \'n\').\n')
        
def ec2_connect (access_key, secret_key, region):
    """
    Connects to EC2, returns a connection object
    """
    
    try:
        conn = ec2.connect_to_region (region, 
                                      aws_access_key_id=access_key, 
                                      aws_secret_access_key=secret_key)
    except Exception, e:
        sys.stderr.write ('Could not connect to region: %s. Exception: %s\n' % (region, e))
        conn = None
        
    return conn
    
def wait_for_state (instance, target_state):
    """
    Waits for instance to move to desired state
    """

    status = instance.update ()
    while status != target_state:
        sys.stdout.write ('Waiting for instance %s to be in \'%s\' state\n' % (instance.id, target_state))
        time.sleep (5)
        status = instance.update ()

        
def start_instance (conn, instance_id):
    """
    Gets a connection object and instance id
    Starts the instance and waits until it's in 'running' state
    """
    
    sys.stdout.write ('Starting instance %s\n' % instance_id)
    try:
        instance = conn.start_instances (instance_ids=[instance_id])[0]
    except EC2ResponseError, e:
        sys.stderr.write ('Could not start instance %s. Error: %s\n' % (instance_id, e.error_message))
        return False

    wait_for_state (instance, u'running')
        
    sys.stdout.write ('Successfully started instance %s\n' % instance_id)
    return True
    
def stop_instance (conn, instance_id):
    """
    Gets a connection object and instance id
    Stops the instance and waits until it's in 'stopped' state
    """

    sys.stdout.write ('Stopping instance %s\n' % instance_id)
    try:
        instance = conn.stop_instances (instance_ids=[instance_id])[0]
    except EC2ResponseError, e:
        sys.stderr.write ('Could not stop instance %s. Error: %s\n' % (instance_id, e.error_message))
        return False

    wait_for_state (instance, u'stopped')
        
    sys.stdout.write ('Successfully stopped instance %s\n' % instance_id)
    return True
    
def terminate_with_vols_instance (conn, instance_id):
    """
    Terminates instance with all attached volumes
    """
    return terminate_instance (conn, instance_id, True)
    
def terminate_instance (conn, instance_id, delete_vols=False):
    """
    Gets a connection object and instance id
    Kills an instance, meaning terminating it and, if asked for, making sure all attached volumes
    are deleted
    """
    if delete_vols:
        sys.stdout.write ('Killing instance %s and all its attached volumes\n' % instance_id)
    else:
        sys.stdout.write ('Terminating instance %s\n' % instance_id)
    # gets instance
    try:
        instance = conn.get_all_instances (instance_ids=[instance_id])[0].instances[0]
    except EC2ResponseError, e:
        sys.stderr.write ('Could not kill instance %s. Error: %s\n' % (instance_id, e.error_message))
        return False
        
    # find block devices
    if delete_vols:
        vols_to_delete = []
        for bd in instance.block_device_mapping.values():
            if bd.delete_on_termination == False:
                vols_to_delete.append (bd.volume_id)

    # terminate instance
    try:
        conn.terminate_instances (instance_ids=[instance_id])
    except EC2ResponseError, e:
        sys.stderr.write ('Could not kill instance %s. Error: %s\n' % (instance_id, e.error_message))
        return False
    
    wait_for_state (instance, u'terminated')
        
    # deletes extra volumes
    if delete_vols:
        first_vol_delete = True
        for vol in vols_to_delete:
            for try_num in 1,2:
                sys.stdout.write ('Deleting attached volume %s, try number %d\n' % (vol, try_num))
                try:
                    conn.delete_volume (vol)
                    break
                except EC2ResponseError, e:
                    sys.stderr.write ('Could not delete attached volume %s. Error: %s\n' % (vol, e.error_message))
                    if try_num == 1:
                        time.sleep (10)

        
    if delete_vols:
        sys.stdout.write ('Successfully terminated instance %s with all attached volumes\n' % instance_id)    
    else:
        sys.stdout.write ('Successfully terminated instance %s\n' % instance_id)    
    return True

    
if __name__ == '__main__':

    # Define command line argument parser
    parser = argparse.ArgumentParser(description='Performs actions on an EC2 instance.')
    parser.add_argument('action', choices=['start', 'stop', 'terminate', 'terminate_with_vols'])
    parser.add_argument('--instance', required=True, help='EC2 instance ID to perform the action on')
    parser.add_argument('--region', default = AWS_REGION, help='AWS region the instance is in. If missing default is used')
    parser.add_argument('--access_key', default = AWS_ACCESS_KEY, help='AWS API access key.  If missing default is used')
    parser.add_argument('--secret_key', default = AWS_SECRET_KEY, help='AWS API secret key.  If missing default is used')
    parser.add_argument('--force', action='store_true', help='If set, this flag will prevent confirmation for the action')
    
    args = parser.parse_args ()

    if args.force:
        perform = True
    else:
        perform = confirm_action (args.action, args.instance, args.region)
            
    if perform:
        conn = ec2_connect (args.access_key, args.secret_key, args.region)
        if conn == None: 
            sys.exit (1)
        # Calls the relevant function according to action
        retval = locals()[args.action+'_instance'](conn, args.instance)
    else:
        retval = True
        
    if retval:
        sys.exit (0)
    else:
        sys.exit (1)
        
        