<commit_before>"""
Build an AWS AMI for this service
"""
import os
import sys
import time
import subprocess
import operator
import pkg_resources
import json
from datetime import datetime
import random
import shlex
import tempfile

try:
    # boto library is not a hard requirement for drift.
    import boto.ec2
    import boto.iam
    import boto3
except ImportError:
    pass

from drift.management import get_app_version, get_app_name, create_deployment_manifest
from drift.management.gittools import get_branch, checkout
from drift.utils import get_tier_name
from drift import slackbot
from driftconfig.util import get_drift_config
from driftconfig.config import get_redis_cache_backend
from drift.flaskfactory import load_flask_config

# regions:
# eu-west-1 : ami-234ecc54
#             ready-made: ami-71196e06
# ap-southeast-1: ami-ca381398

UBUNTU_BASE_IMAGE_NAME = 'ubuntu-base-image'
UBUNTU_TRUSTY_IMAGE_NAME = 'ubuntu/images/hvm/ubuntu-trusty-14.04*'
UBUNTU_XENIAL_IMAGE_NAME = 'ubuntu/images/hvm-ssd/ubuntu-xenial-16.04*'
UBUNTU_RELEASE = UBUNTU_XENIAL_IMAGE_NAME

IAM_ROLE = "ec2"

# The 'Canonical' owner. This organization maintains the Ubuntu AMI's on AWS.
AMI_OWNER_CANONICAL = '099720109477'


def get_options(parser):

    subparsers = parser.add_subparsers(
        title="AWS AMI Management and Deployment",
        description="These sets of commands help you with configuring, baking, running and "
        "deploying AWS AMIs' for your tier.",
        dest="command",
    )

    # The 'bake' command
    p = subparsers.add_parser(
        'bake',
        help='Bake a new AMI for the current current service.',
    )
    p.add_argument(
        'tag', action='store', help='Git release tag to bake. (Run "git tag" to get available tags).',
                                    nargs='?', default=None)

    p.add_argument(
        "--ubuntu", action="store_true",
        help="Bake a base Ubuntu image. ",
    )
    p.add_argument(
        "--preview", help="Show arguments only", action="store_true"
    )
    p.add_argument(
        "--skipcopy", help="Do not copy image to all regions", action="store_true"
    )
    p.add_argument(
        "--debug", help="Run Packer in debug mode", action="store_true"
    )

    # The 'run' command
    p = subparsers.add_parser(
        'run',
        help='Launch an AMI for this service, or configure it for auto-scaling.',
    )
    p.add_argument(
        "--ami",
        help="An AMI built with the rest api service",
    )
    p.add_argument(
        "--instance_type",
        help="The EC2 instance type to use. Default is 't2.small'.",
        default="t2.small"
    )
    p.add_argument(
        "--launch", action="store_true",
        help="Launch the AMI. (Default unless \"autoscaling\" is configured for the tier and service.)",
    )
    p.add_argument(
        "--autoscale", action="store_true",
        help="Add the AMI to autoscaling group. (Default if \"autoscaling\" is configured for the tier and service.)",
    )
    p.add_argument(
        "--force", action="store_true",
        help="If --launch or --autoscale conflict with tier configuration, use --force to override.",
    )
    p.add_argument(
        "--preview", help="Show arguments only", action="store_true"
    )

    # The 'copy-image' command
    p = subparsers.add_parser(
        'copy-image',
        help='Copies AMI to all active regions.',
    )
    p.add_argument(
        "ami",
        help="The image id.",
    )


def run_command(args):
    fn = globals()["_{}_command".format(args.command.replace("-", "_"))]
    fn(args)


def fold_tags(tags, key_name=None, value_name=None):
    """Fold boto3 resource tags array into a dictionary."""
    return {tag['Key']: tag['Value'] for tag in tags}


def filterize(d):
    """
    Return dictionary 'd' as a boto3 "filters" object by unfolding it to a list of
    dict with 'Name' and 'Values' entries.
    """
    return [{'Name': k, 'Values': [v]} for k, v in d.items()]


def _bake_command(args):
    if args.ubuntu:
        name = UBUNTU_BASE_IMAGE_NAME
    else:
        name = get_app_name()

    name = get_app_name()
    tier_name = get_tier_name()
    conf = get_drift_config(
        tier_name=tier_name, deployable_name=name, drift_app=load_flask_config())

    domain = conf.domain.get()
    aws_region = domain['aws']['ami_baking_region']
    ec2 = boto3.resource('ec2', region_name=aws_region)

    print "DOMAIN:\n", json.dumps(domain, indent=4)
    if not args.ubuntu:
        print "DEPLOYABLE:", name
    print "AWS REGION:", aws_region

    # Create a list of all regions that are active
    if args.ubuntu:
        # Get all Ubuntu images from the appropriate region and pick the most recent one.
        # The 'Canonical' owner. This organization maintains the Ubuntu AMI's on AWS.
        print "Finding the latest AMI on AWS that matches", UBUNTU_RELEASE
        filters = [
            {'Name': 'name', 'Values': [UBUNTU_RELEASE]},
        ]
        amis = list(ec2.images.filter(Owners=[AMI_OWNER_CANONICAL], Filters=filters))
        if not amis:
            print "No AMI found matching '{}'. Not sure what to do now.".format(UBUNTU_RELEASE)
            sys.exit(1)
        ami = max(amis, key=operator.attrgetter("creation_date"))
    else:
        filters = [
            {'Name': 'tag:service-name', 'Values': [UBUNTU_BASE_IMAGE_NAME]},
            {'Name': 'tag:domain-name', 'Values': [domain['domain_name']]},
        ]
        amis = list(ec2.images.filter(Owners=['self'], Filters=filters))
        if not amis:
            criteria = {d['Name']: d['Values'][0] for d in filters}
            print "No '{}' AMI found using the search criteria {}.".format(UBUNTU_BASE_IMAGE_NAME, criteria)
            print "Bake one using this command: {} ami bake --ubuntu".format(sys.argv[0])

            sys.exit(1)
        ami = max(amis, key=operator.attrgetter("creation_date"))

    print "Using source AMI:"
    print "\tID:\t", ami.id
    print "\tName:\t", ami.name
    print "\tDate:\t", ami.creation_date

    if args.ubuntu:
        manifest = None
        packer_vars = {
            'setup_script': pkg_resources.resource_filename(__name__, "ubuntu-packer.sh"),
            'ubuntu_release': UBUNTU_RELEASE,
        }
    else:
        current_branch = get_branch()
        if not args.tag:
            args.tag = current_branch

        print "Using branch/tag", args.tag

        # Wrap git branch modification in RAII.
        checkout(args.tag)
        try:
            setup_script = ""
            setup_script_custom = ""
            with open(pkg_resources.resource_filename(__name__, "driftapp-packer.sh"), 'r') as f:
                setup_script = f.read()
            custom_script_name = os.path.join(conf.drift_app['app_root'], 'scripts', 'ami-bake.sh')
            if os.path.exists(custom_script_name):
                print "Using custom bake shell script", custom_script_name
                setup_script_custom = "echo Executing custom bake shell script from {}\n".format(custom_script_name)
                setup_script_custom += open(custom_script_name, 'r').read()
                setup_script_custom += "\necho Custom bake shell script completed\n"
            else:
                print "Note: No custom ami-bake.sh script found for this application."
            # custom setup needs to happen first because we might be installing some requirements for the regular setup
            setup_script = setup_script_custom + setup_script
            tf = tempfile.NamedTemporaryFile(delete=False)
            tf.write(setup_script)
            tf.close()
            setup_script_filename = tf.name
            manifest = create_deployment_manifest('ami', comment=None)
            packer_vars = {
                'version': get_app_version(),
                'setup_script': setup_script_filename,
            }

            if not args.preview:
                cmd = ['python', 'setup.py', 'sdist', '--formats=zip']
                ret = subprocess.call(cmd)
                if ret != 0:
                    print "Failed to execute build command:", cmd
                    sys.exit(ret)

                cmd = ["zip", "-r", "dist/aws.zip", "aws"]
                ret = subprocess.call(cmd)
                if ret != 0:
                    print "Failed to execute build command:", cmd
                    sys.exit(ret)
        finally:
            print "Reverting to ", current_branch
            checkout(current_branch)

    user = boto.iam.connect_to_region(aws_region).get_user()  # The current IAM user running this command

    packer_vars.update({
        "service": name,
        "region": aws_region,
        "source_ami": ami.id,
        "user_name": user.user_name,
        "domain_name": domain['domain_name'],
    })

    print "Packer variables:\n", pretty(packer_vars)

    # See if Packer is installed and generate sensible error code if something is off.
    # This will also write the Packer version to the terminal which is useful info.
    try:
        subprocess.call(['packer', 'version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    except Exception as e:
        print "Error:", e
        print "'packer version' command failed. Please install it if it's missing."
        sys.exit(127)

    cmd = "packer build "
    if args.debug:
        cmd += "-debug "

    cmd += "-only=amazon-ebs "
    for k, v in packer_vars.iteritems():
        cmd += "-var {}=\"{}\" ".format(k, v)

    # Use generic packer script if project doesn't specify one
    pkg_resources.cleanup_resources()
    if args.ubuntu:
        scriptfile = pkg_resources.resource_filename(__name__, "ubuntu-packer.json")
        cmd += scriptfile
    elif os.path.exists("config/packer.json"):
        cmd += "config/packer.json"
    else:
        scriptfile = pkg_resources.resource_filename(__name__, "driftapp-packer.json")
        cmd += scriptfile

    print "Baking AMI with: {}".format(cmd)
    if args.preview:
        print "Not building or packaging because --preview is on. Exiting now."
        return

    start_time = time.time()
    try:
        # Execute Packer command and parse the output to find the ami id.
        p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        while True:
            line = p.stdout.readline()
            print line,
            if line == '' and p.poll() is not None:
                break

            # The last lines from the packer execution look like this:
            # ==> Builds finished. The artifacts of successful builds are:
            # --> amazon-ebs: AMIs were created:
            #
            # eu-west-1: ami-0ee5eb68
            if 'ami-' in line:
                ami_id = line[line.rfind('ami-'):].strip()
                ami = ec2.Image(ami_id)
                print ""
                print "AMI ID: %s" % ami.id
                print ""
    finally:
        pkg_resources.cleanup_resources()

    if p.returncode != 0:
        print "Failed to execute packer command:", cmd
        sys.exit(p.returncode)

    duration = time.time() - start_time

    if manifest:
        print "Adding manifest tags to AMI:"
        pretty(manifest)
        prefix = "drift:manifest:"
        tags = []
        for k, v in manifest.iteritems():
            tag_name = "{}{}".format(prefix, k)
            tags.append({'Key': tag_name, 'Value': v or ''})
        ami.create_tags(DryRun=False, Tags=tags)

    if not args.skipcopy:
        _copy_image(ami.id)

    print "Done after %.0f seconds" % (duration)
    slackbot.post_message("Successfully baked a new AMI for '{}' in %.0f seconds"
                          .format(name, duration))


class MyEncoder(json.JSONEncoder):
    def default(self, o):
        return str(o)


def pretty(ob):
    """Returns a pretty representation of 'ob'."""
    return json.dumps(ob, cls=MyEncoder, indent=4)


def _find_latest_ami(service_name, release=None):
    name = get_app_name()
    tier_name = get_tier_name()
    conf = get_drift_config(tier_name=tier_name, deployable_name=name)
    domain = conf.domain.get()
    aws_region = conf.tier['aws']['region']

    ec2 = boto3.resource('ec2', region_name=aws_region)
    filters = [
        {'Name': 'tag:service-name', 'Values': [name]},
        {'Name': 'tag:domain-name', 'Values': [domain['domain_name']]},
    ]
    if release:
        filters.append({'Name': 'tag:git-release', 'Values': [release]},)

    amis = list(ec2.images.filter(Owners=['self'], Filters=filters))
    if not amis:
        criteria = {d['Name']: d['Values'][0] for d in filters}
        print "No '{}' AMI found using the search criteria {}.".format(UBUNTU_BASE_IMAGE_NAME, criteria)
        sys.exit(1)

    ami = max(amis, key=operator.attrgetter("creation_date"))
    return ami


def _run_command(args):
    # Always autoscale!
    args.autoscale = True

    if args.launch and args.autoscale:
        print "Error: Can't use --launch and --autoscale together."
        sys.exit(1)

    name = get_app_name()
    tier_name = get_tier_name()
    conf = get_drift_config(
        tier_name=tier_name, deployable_name=name, drift_app=load_flask_config())
    aws_region = conf.tier['aws']['region']


    print "AWS REGION:", aws_region
    print "DOMAIN:\n", json.dumps(conf.domain.get(), indent=4)
    print "DEPLOYABLE:\n", json.dumps(conf.deployable, indent=4)

    ec2_conn = boto.ec2.connect_to_region(aws_region)
    iam_conn = boto.iam.connect_to_region(aws_region)

    if conf.tier['is_live']:
        print "NOTE! This tier is marked as LIVE. Special restrictions may apply. Use --force to override."

    autoscaling = {
        "min": 1,
        "max": 1,
        "desired": 1,
        "instance_type": args.instance_type,
    }
    autoscaling.update(conf.deployable.get('autoscaling', {}))
    release = conf.deployable.get('release', '')<commit_msg>Adding support for AWS China ami baking. - Canonical has different user id in China. - Packer is sensitive regarding which version it is when baking in China. - Some configuration checks made as well.<commit_after>"""
Build an AWS AMI for this service
"""
import os
import sys
import time
import subprocess
import operator
import pkg_resources
import json
from datetime import datetime
import random
import shlex
import tempfile

try:
    # boto library is not a hard requirement for drift.
    import boto.ec2
    import boto.iam
    import boto3
except ImportError:
    pass

from drift.management import get_app_version, get_app_name, create_deployment_manifest
from drift.management.gittools import get_branch, checkout
from drift.utils import get_tier_name
from drift import slackbot
from driftconfig.util import get_drift_config
from driftconfig.config import get_redis_cache_backend
from drift.flaskfactory import load_flask_config

# regions:
# eu-west-1 : ami-234ecc54
#             ready-made: ami-71196e06
# ap-southeast-1: ami-ca381398

UBUNTU_BASE_IMAGE_NAME = 'ubuntu-base-image'
UBUNTU_TRUSTY_IMAGE_NAME = 'ubuntu/images/hvm/ubuntu-trusty-14.04*'
UBUNTU_XENIAL_IMAGE_NAME = 'ubuntu/images/hvm-ssd/ubuntu-xenial-16.04*'
UBUNTU_RELEASE = UBUNTU_XENIAL_IMAGE_NAME

IAM_ROLE = "ec2"


# The 'Canonical' owner. This organization maintains the Ubuntu AMI's on AWS.
def _get_canonical_owner_id(region_id):
    """Returns region specific owner id for Canonical which is the maintainer of Ubuntu images."""
    if region_id.startswith('cn-'):
        return '837727238323'
    else:
        return '099720109477'


def get_options(parser):

    subparsers = parser.add_subparsers(
        title="AWS AMI Management and Deployment",
        description="These sets of commands help you with configuring, baking, running and "
        "deploying AWS AMIs' for your tier.",
        dest="command",
    )

    # The 'bake' command
    p = subparsers.add_parser(
        'bake',
        help='Bake a new AMI for the current current service.',
    )
    p.add_argument(
        'tag', action='store', help='Git release tag to bake. (Run "git tag" to get available tags).',
                                    nargs='?', default=None)

    p.add_argument(
        "--ubuntu", action="store_true",
        help="Bake a base Ubuntu image. ",
    )
    p.add_argument(
        "--preview", help="Show arguments only", action="store_true"
    )
    p.add_argument(
        "--skipcopy", help="Do not copy image to all regions", action="store_true"
    )
    p.add_argument(
        "--debug", help="Run Packer in debug mode", action="store_true"
    )

    # The 'run' command
    p = subparsers.add_parser(
        'run',
        help='Launch an AMI for this service, or configure it for auto-scaling.',
    )
    p.add_argument(
        "--ami",
        help="An AMI built with the rest api service",
    )
    p.add_argument(
        "--instance_type",
        help="The EC2 instance type to use. Default is 't2.small'.",
        default="t2.small"
    )
    p.add_argument(
        "--launch", action="store_true",
        help="Launch the AMI. (Default unless \"autoscaling\" is configured for the tier and service.)",
    )
    p.add_argument(
        "--autoscale", action="store_true",
        help="Add the AMI to autoscaling group. (Default if \"autoscaling\" is configured for the tier and service.)",
    )
    p.add_argument(
        "--force", action="store_true",
        help="If --launch or --autoscale conflict with tier configuration, use --force to override.",
    )
    p.add_argument(
        "--preview", help="Show arguments only", action="store_true"
    )

    # The 'copy-image' command
    p = subparsers.add_parser(
        'copy-image',
        help='Copies AMI to all active regions.',
    )
    p.add_argument(
        "ami",
        help="The image id.",
    )


def run_command(args):
    fn = globals()["_{}_command".format(args.command.replace("-", "_"))]
    fn(args)


def fold_tags(tags, key_name=None, value_name=None):
    """Fold boto3 resource tags array into a dictionary."""
    return {tag['Key']: tag['Value'] for tag in tags}


def filterize(d):
    """
    Return dictionary 'd' as a boto3 "filters" object by unfolding it to a list of
    dict with 'Name' and 'Values' entries.
    """
    return [{'Name': k, 'Values': [v]} for k, v in d.items()]


def _bake_command(args):
    if args.ubuntu:
        name = UBUNTU_BASE_IMAGE_NAME
    else:
        name = get_app_name()

    conf = get_drift_config(deployable_name=name, drift_app=load_flask_config())

    domain = conf.domain.get()
    if 'aws' not in domain or 'ami_baking_region' not in domain['aws']:
        print "Missing configuration value in table 'domain'. Specify the AWS region in " \
            "'aws.ami_baking_region'."
        sys.exit(1)
    aws_region = domain['aws']['ami_baking_region']
    ec2 = boto3.resource('ec2', region_name=aws_region)

    # Do some compatibility checks
    if aws_region.startswith("cn-"):
        print "NOTE!!!!!!!!!!!!!!!!!!!!!!!!!!!"
        print "If this command fails with some AWS access crap, you may need to switch packer version."
        print "More info: https://github.com/hashicorp/packer/issues/5447"

    print "DOMAIN:\n", json.dumps(domain, indent=4)
    if not args.ubuntu:
        print "DEPLOYABLE:", name
    print "AWS REGION:", aws_region

    # Create a list of all regions that are active
    if args.ubuntu:
        # Get all Ubuntu images from the appropriate region and pick the most recent one.
        # The 'Canonical' owner. This organization maintains the Ubuntu AMI's on AWS.
        print "Finding the latest AMI on AWS that matches", UBUNTU_RELEASE
        filters = [
            {'Name': 'name', 'Values': [UBUNTU_RELEASE]},
        ]
        amis = list(ec2.images.filter(Owners=[_get_canonical_owner_id(aws_region)], Filters=filters))
        if not amis:
            print "No AMI found matching '{}'. Not sure what to do now.".format(UBUNTU_RELEASE)
            sys.exit(1)
        ami = max(amis, key=operator.attrgetter("creation_date"))
    else:
        filters = [
            {'Name': 'tag:service-name', 'Values': [UBUNTU_BASE_IMAGE_NAME]},
            {'Name': 'tag:domain-name', 'Values': [domain['domain_name']]},
        ]
        amis = list(ec2.images.filter(Owners=['self'], Filters=filters))
        if not amis:
            criteria = {d['Name']: d['Values'][0] for d in filters}
            print "No '{}' AMI found using the search criteria {}.".format(UBUNTU_BASE_IMAGE_NAME, criteria)
            print "Bake one using this command: {} ami bake --ubuntu".format(sys.argv[0])

            sys.exit(1)
        ami = max(amis, key=operator.attrgetter("creation_date"))

    print "Using source AMI:"
    print "\tID:\t", ami.id
    print "\tName:\t", ami.name
    print "\tDate:\t", ami.creation_date

    if args.ubuntu:
        manifest = None
        packer_vars = {
            'setup_script': pkg_resources.resource_filename(__name__, "ubuntu-packer.sh"),
            'ubuntu_release': UBUNTU_RELEASE,
        }
    else:
        current_branch = get_branch()
        if not args.tag:
            args.tag = current_branch

        print "Using branch/tag", args.tag

        # Wrap git branch modification in RAII.
        checkout(args.tag)
        try:
            setup_script = ""
            setup_script_custom = ""
            with open(pkg_resources.resource_filename(__name__, "driftapp-packer.sh"), 'r') as f:
                setup_script = f.read()
            custom_script_name = os.path.join(conf.drift_app['app_root'], 'scripts', 'ami-bake.sh')
            if os.path.exists(custom_script_name):
                print "Using custom bake shell script", custom_script_name
                setup_script_custom = "echo Executing custom bake shell script from {}\n".format(custom_script_name)
                setup_script_custom += open(custom_script_name, 'r').read()
                setup_script_custom += "\necho Custom bake shell script completed\n"
            else:
                print "Note: No custom ami-bake.sh script found for this application."
            # custom setup needs to happen first because we might be installing some requirements for the regular setup
            setup_script = setup_script_custom + setup_script
            tf = tempfile.NamedTemporaryFile(delete=False)
            tf.write(setup_script)
            tf.close()
            setup_script_filename = tf.name
            manifest = create_deployment_manifest('ami', comment=None)
            packer_vars = {
                'version': get_app_version(),
                'setup_script': setup_script_filename,
            }

            if not args.preview:
                cmd = ['python', 'setup.py', 'sdist', '--formats=zip']
                ret = subprocess.call(cmd)
                if ret != 0:
                    print "Failed to execute build command:", cmd
                    sys.exit(ret)

                cmd = ["zip", "-r", "dist/aws.zip", "aws"]
                ret = subprocess.call(cmd)
                if ret != 0:
                    print "Failed to execute build command:", cmd
                    sys.exit(ret)
        finally:
            print "Reverting to ", current_branch
            checkout(current_branch)

    user = boto.iam.connect_to_region(aws_region).get_user()  # The current IAM user running this command

    packer_vars.update({
        "service": name,
        "region": aws_region,
        "source_ami": ami.id,
        "user_name": user.user_name,
        "domain_name": domain['domain_name'],
    })

    print "Packer variables:\n", pretty(packer_vars)

    # See if Packer is installed and generate sensible error code if something is off.
    # This will also write the Packer version to the terminal which is useful info.
    try:
        subprocess.call(['packer', 'version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    except Exception as e:
        print "Error:", e
        print "'packer version' command failed. Please install it if it's missing."
        sys.exit(127)

    cmd = "packer build "
    if args.debug:
        cmd += "-debug "

    cmd += "-only=amazon-ebs "
    for k, v in packer_vars.iteritems():
        cmd += "-var {}=\"{}\" ".format(k, v)

    # Use generic packer script if project doesn't specify one
    pkg_resources.cleanup_resources()
    if args.ubuntu:
        scriptfile = pkg_resources.resource_filename(__name__, "ubuntu-packer.json")
        cmd += scriptfile
    elif os.path.exists("config/packer.json"):
        cmd += "config/packer.json"
    else:
        scriptfile = pkg_resources.resource_filename(__name__, "driftapp-packer.json")
        cmd += scriptfile

    print "Baking AMI with: {}".format(cmd)
    if args.preview:
        print "Not building or packaging because --preview is on. Exiting now."
        return

    start_time = time.time()
    try:
        # Execute Packer command and parse the output to find the ami id.
        p = subprocess.Popen(shlex.split(cmd), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        while True:
            line = p.stdout.readline()
            print line,
            if line == '' and p.poll() is not None:
                break

            # The last lines from the packer execution look like this:
            # ==> Builds finished. The artifacts of successful builds are:
            # --> amazon-ebs: AMIs were created:
            #
            # eu-west-1: ami-0ee5eb68
            if 'ami-' in line:
                ami_id = line[line.rfind('ami-'):].strip()
                ami = ec2.Image(ami_id)
                print ""
                print "AMI ID: %s" % ami.id
                print ""
    finally:
        pkg_resources.cleanup_resources()

    if p.returncode != 0:
        print "Failed to execute packer command:", cmd
        sys.exit(p.returncode)

    duration = time.time() - start_time

    if manifest:
        print "Adding manifest tags to AMI:"
        pretty(manifest)
        prefix = "drift:manifest:"
        tags = []
        for k, v in manifest.iteritems():
            tag_name = "{}{}".format(prefix, k)
            tags.append({'Key': tag_name, 'Value': v or ''})
        ami.create_tags(DryRun=False, Tags=tags)

    if not args.skipcopy:
        _copy_image(ami.id)

    print "Done after %.0f seconds" % (duration)
    slackbot.post_message("Successfully baked a new AMI for '{}' in %.0f seconds"
                          .format(name, duration))


class MyEncoder(json.JSONEncoder):
    def default(self, o):
        return str(o)


def pretty(ob):
    """Returns a pretty representation of 'ob'."""
    return json.dumps(ob, cls=MyEncoder, indent=4)


def _find_latest_ami(service_name, release=None):
    name = get_app_name()
    tier_name = get_tier_name()
    conf = get_drift_config(tier_name=tier_name, deployable_name=name)
    domain = conf.domain.get()
    aws_region = conf.tier['aws']['region']

    ec2 = boto3.resource('ec2', region_name=aws_region)
    filters = [
        {'Name': 'tag:service-name', 'Values': [name]},
        {'Name': 'tag:domain-name', 'Values': [domain['domain_name']]},
    ]
    if release:
        filters.append({'Name': 'tag:git-release', 'Values': [release]},)

    amis = list(ec2.images.filter(Owners=['self'], Filters=filters))
    if not amis:
        criteria = {d['Name']: d['Values'][0] for d in filters}
        print "No '{}' AMI found using the search criteria {}.".format(UBUNTU_BASE_IMAGE_NAME, criteria)
        sys.exit(1)

    ami = max(amis, key=operator.attrgetter("creation_date"))
    return ami


def _run_command(args):
    # Always autoscale!
    args.autoscale = True

    if args.launch and args.autoscale:
        print "Error: Can't use --launch and --autoscale together."
        sys.exit(1)

    name = get_app_name()
    tier_name = get_tier_name()
    conf = get_drift_config(
        tier_name=tier_name, deployable_name=name, drift_app=load_flask_config())

    if not conf.deployable:
        print "The deployable '{}' is not registered and/or assigned to tier {}.".format(name, tier_name)
        print "Run 'drift-admin register' to register this deployable."
        print "Run 'driftconfig assign-tier {}' to assign it to the tier.".format(name)
        sys.exit(1)

    aws_region = conf.tier['aws']['region']

    print "AWS REGION:", aws_region
    print "DOMAIN:\n", json.dumps(conf.domain.get(), indent=4)
    print "DEPLOYABLE:\n", json.dumps(conf.deployable, indent=4)

    ec2_conn = boto.ec2.connect_to_region(aws_region)
    iam_conn = boto.iam.connect_to_region(aws_region)

    if conf.tier['is_live']:
        print "NOTE! This tier is marked as LIVE. Special restrictions may apply. Use --force to override."

    autoscaling = {
        "min": 1,
        "max": 1,
        "desired": 1,
        "instance_type": args.instance_type,
    }
    autoscaling.update(conf.deployable.get('autoscaling', {}))
    release = conf.deployable.get('release', '')
{'fraction_non_alphanumeric': 0.11868268434134217, 'fraction_numerical': 0.006628003314001657, 'mean_word_length': 2.492344786015672, 'pattern_counts': {'":': 18, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '499834', 'n_tokens_mistral': 9203, 'n_tokens_neox': 8746, 'n_words': 2405}