#!/bin/bash

# Script to automate creation and snapshotting of a Moby AMI.  Currently, it's
# intended to be invoked from an instance running in the same region as the
# target AMI will be in, since it directly mounts the created EBS volume as a
# device on this running instance.

set -e

TAG_KEY=moby-bake
INSTANCE_METADATA_API_ENDPOINT=http://169.254.169.254/latest/meta-data/

# TODO(nathanleclaire): This could be calculated dynamically to avoid conflicts.
EBS_DEVICE=/dev/xvdb

function arrowecho () {
    echo " --->" "$@"
}

function current_instance_az () {
    curl -s ${INSTANCE_METADATA_API_ENDPOINT}/placement/availability-zone
}

function current_instance_id () {
    curl -s ${INSTANCE_METADATA_API_ENDPOINT}/instance-id
}

# We tag resources created as part of the build to ensure that they can be
# cleaned up later.
function tag () {
    arrowecho "Tagging $1"
    aws ec2 create-tags --resources "$1" --tags Key=${TAG_KEY},Value= >/dev/null
}

function format_device () {
    arrowecho "Waiting for EBS device to appear in build container"

    while [[ ! -e ${EBS_DEVICE} ]]; do
        sleep 1
    done

    # This heredoc might be confusing at first glance, so here is a detailed
    # summary of what each line does:
    #
    # n  - create new partition
    # p  - make it a primary partition
    # 1  - it should be partition #1
    # \n - use default first cylinder
    # \n - use default last cylinder
    # a  - toggle a partition as bootable
    # 1  - do the 1st partition specifically
    # w  - write changes and exit
    arrowecho "Formatting boot partition"
    fdisk ${EBS_DEVICE} >/dev/null << EOF
n
p
1


a
1
w
EOF

    # To ensure everything went smoothly, print the resulting partition table.
    echo
    arrowecho "Printing device partition contents"
    fdisk -l ${EBS_DEVICE}

    ROOT_PARTITION="${EBS_DEVICE}1"
    ROOT_PARTITION_MOUNT="/mnt/moby"

    # Mount created root partition, format it as ext4, and copy over the needed
    # files for boot (syslinux configuration, kernel binary, and initrd.img)
    arrowecho "Making filesystem on partition 1"
    mke2fs -t ext4 ${ROOT_PARTITION}

    arrowecho "Mounting partition filesystem"
    mkdir ${ROOT_PARTITION_MOUNT}
    mount -t ext4 ${ROOT_PARTITION} ${ROOT_PARTITION_MOUNT}

    arrowecho "Copying image and kernel binary to partition"

    # Get files needed to boot in place.
    cp /mnt/syslinux.cfg ${ROOT_PARTITION_MOUNT}
    cp /mnt/kernel/vmlinuz64 ${ROOT_PARTITION_MOUNT}
    cp /mnt/initrd.img ${ROOT_PARTITION_MOUNT}

    # From http://www.syslinux.org/wiki/index.php?title=EXTLINUX:
    #
    # "Note that EXTLINUX installs in the filesystem partition like a
    # well-behaved bootloader :). Thus, it needs a master boot record in the
    # partition table; the mbr.bin shipped with SYSLINUX should work well."

    # Thus, this step installs syslinux on the mounted filesystem (partition
    # 1).
    arrowecho "Installing syslinux to partition"
    extlinux --install ${ROOT_PARTITION_MOUNT} 

    # Format master boot record in partition table on target device.
    arrowecho "Copying MBR to partition table in target device"
    dd if=/usr/share/syslinux/mbr.bin of=${EBS_DEVICE} bs=440 count=1

    umount ${ROOT_PARTITION_MOUNT}

    arrowecho "Checking device/partition sanity"
    fdisk -l ${EBS_DEVICE}
}

function bake_image () {
    # Create a new EBS volume.  We will format this volume to boot into Moby
    # initrd via syslinux in MBR.  That formatted drive can then be snapshotted
    # and turned into an AMI.
    VOLUME_ID=$(aws ec2 create-volume \
        --size 1 \
        --availability-zone $(current_instance_az) | jq -r .VolumeId)

    tag ${VOLUME_ID}

    aws ec2 wait volume-available --volume-ids ${VOLUME_ID}

    arrowecho "Attaching volume"
    aws ec2 attach-volume \
        --volume-id ${VOLUME_ID} \
        --device ${EBS_DEVICE} \
        --instance-id $(current_instance_id) >/dev/null

    aws ec2 wait volume-in-use --volume-ids ${VOLUME_ID}

    format_device

    arrowecho "Taking snapshot!"

    # Take a snapshot of the volume we wrote to.
    SNAPSHOT_ID=$(aws ec2 create-snapshot \
        --volume-id ${VOLUME_ID} \
        --description "Snapshot of Moby device for AMI baking" | jq -r .SnapshotId)

    tag ${SNAPSHOT_ID}

    arrowecho "Waiting for snapshot completion"

    aws ec2 wait snapshot-completed --snapshot-ids ${SNAPSHOT_ID}

    # Convert that snapshot into an AMI as the root device.
    IMAGE_ID=$(aws ec2 register-image \
        --name "Moby Linux" \
        --description "The best OS for running Docker" \
        --architecture x86_64 \
        --root-device-name "${EBS_DEVICE}" \
        --virtualization-type "hvm" \
        --block-device-mappings "[
            {
                \"DeviceName\": \"${EBS_DEVICE}\",
                \"Ebs\": {
                    \"SnapshotId\": \"${SNAPSHOT_ID}\"
                }
            }
        ]" | jq -r .ImageId)

    tag ${IMAGE_ID}

    # Boom, now you (should) have a Moby AMI.
    arrowecho "Created AMI: ${IMAGE_ID}"

    echo ${IMAGE_ID} >/mnt/aws/ami_id.out
}

function clean_tagged_resources () {
    if [[ -d /mnt/moby ]]; then
        rm -rf /mnt/moby
    fi

    VOLUME_ID=$(aws ec2 describe-volumes --filters "Name=tag-key,Values=$TAG_KEY" | jq -r .Volumes[0].VolumeId)
    if [[ ${VOLUME_ID} == "null" ]]; then
        arrowecho "No volume found, skipping"
    else
        arrowecho "Detaching volume"
        aws ec2 detach-volume --volume-id ${VOLUME_ID} >/dev/null
        aws ec2 wait volume-available --volume-ids ${VOLUME_ID}
        arrowecho "Deleting volume"
        aws ec2 delete-volume --volume-id ${VOLUME_ID} >/dev/null
    fi

    IMAGE_ID=$(aws ec2 describe-images --filters "Name=tag-key,Values=$TAG_KEY" | jq -r .Images[0].ImageId)
    if [[ ${IMAGE_ID} == "null" ]]; then
        arrowecho "No image found, skipping"
    else
        arrowecho "Deregistering previously baked AMI"

        # Sometimes describe-images does not return null even if the found
        # image cannot be deregistered 
        #
        # TODO(nathanleclaire): More elegant solution?
        aws ec2 deregister-image --image-id ${IMAGE_ID} >/dev/null || true
    fi

    SNAPSHOT_ID=$(aws ec2 describe-snapshots --filters "Name=tag-key,Values=$TAG_KEY" | jq -r .Snapshots[0].SnapshotId)
    if [[ ${SNAPSHOT_ID} == "null" ]]; then
        arrowecho "No snapshot found, skipping"
    else
        arrowecho "Deleting volume snapshot"
        aws ec2 delete-snapshot --snapshot-id ${SNAPSHOT_ID}
    fi
}

case "$1" in
    bake)
        bake_image
        ;;
    clean)
        clean_tagged_resources
        ;;
    regions)
        # TODO(nathanleclaire): Propogate created image (if existing) to every
        # possible region / AZ
        ;;
    *)
        arrowecho "Command $1 not found.  Usage: ./bake-ami.sh [bake|clean|regions]"
esac
