27 August 2014

Ever forget the DNS name and the IP of the Amazon EC2 host you were wanting to login to? I know I have. The script below will help with that.

manage_ec2.py

This little script was based upon an idea I saw from a coworker at C2FO. His solution was in bash, but relied upon Java to be able to handle the communicate with EC2. I was hoping for something a little more elegant, unified, and fast. Thus this script was born. For your viewing pleasure (as always, latest on Github):

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
This script will list all hosts in an ec2 region and prompt you to connect
to them.

It expects the file .boto to exist in your home directory with contents
as follows:

[Credentials]
aws_access_key_id = <AWS_ACCESS_KEY_ID>
aws_secret_access_key = <AWS_SECRET_ACCESS_KEY>
"""

import boto.ec2
import os
import sys
from itertools import imap
from subprocess import call
import yaml
import datetime
import argparse


def parse_args():
    """
    Get arguments from the command like with argparse
    """
    parser = argparse.ArgumentParser(
        description='Generate a list of ec2 hosts and select one to connect to'
        )
    parser.add_argument(
        '--login_name', '-l', required=False, help='Login Name')
    parser.add_argument(
        '--port', '-p', required=False, help='SSH Port')
    parser.add_argument(
        '--identity_file', '-i', required=False, help='SSH Identity File')
    parser.add_argument(
        '--force_download', '-F', required=False,
        help='Force Download', default=False)
    parser.add_argument(
        '--file', '-f', required=False, help='Config File',
        default=os.path.expanduser('~') + '/.ec2_addr_cache')
    parser.add_argument(
        '--region', '-r', required=False, help='EC2 Region',
        default='us-west-2')
    parser.add_argument(
        '--perpage', '-P', required=False,
        help='Hosts to display per page (default 25, 0 to display all)',
        default=25, type=int)
    args = parser.parse_args()
    return args


def generate_hosts(filename, args):
    """
    Generate a list of hoosts in the ec2 region selected and save
    them to a yaml file
    """
    hosts = {}
    conn = boto.ec2.connect_to_region(args.region)
    reservations = conn.get_all_reservations()
    for reservation in reservations:
        instances = reservation.instances
        for instance in instances:
            if instance.state == 'running':
                if 'Name' in instance.tags:
                    hosts.update(
                        {instance.tags['Name']:
                            {'ip': instance.private_ip_address,
                                'key_name': instance.key_name}})
    with open(filename, 'w') as outfile:
        outfile.write(yaml.dump(hosts, default_flow_style=False))
    return hosts


def read_hosts(filename):
    """
    Load saved ec2 host file
    """
    hosts = yaml.load(file(filename, 'r'))
    return hosts


def hosts_dict(args):
    ec2_addr_cache_file = args.file
    if os.path.isfile(ec2_addr_cache_file) and not args.force_download:
        file_age = int(os.path.getmtime(ec2_addr_cache_file))
        now = int(datetime.datetime.now().strftime("%s"))
        if (file_age + 86400) < now:
            hosts = generate_hosts(ec2_addr_cache_file, args)
        else:
            hosts = read_hosts(ec2_addr_cache_file)
    else:
        hosts = generate_hosts(ec2_addr_cache_file, args)

    return hosts


def list_servers(args, startindex, perpage):
    """
    List the servers stored in the ec2 region file or regenerate the file if
    older than 24 hours or forced via the -F True flag
    """
    hosts = hosts_dict(args)
    length = max(imap(len, hosts))
    hostcount = len(hosts)
    if perpage <= 0:
        perpage = hostcount
    countwidth = len(str(hostcount))
    hostlist = sorted(hosts, key=lambda s: s.lower())
    if startindex < 0:
        startindex = 0
    elif startindex >= hostcount:
        startindex = hostcount - perpage
    for index in range(startindex, startindex + perpage):
        if index >= hostcount:
            break
        host = hostlist[index]
        # Start the list with 1 instead of 0 for humans
        printindex = index + 1
        print "%s) %s | %s | %s" % (
            str(printindex).rjust(countwidth),
            host.ljust(length), hosts[host]['ip'].ljust(15),
            hosts[host]['key_name'])
    print "Displaying hosts %i to %i of %i" % (
        startindex + 1, printindex, hostcount)
    selected_index = raw_input(
        "Please enter the host to connect to or (n)ext, (p)revious, (q)uit: ")
    if isinstance(selected_index, basestring) and selected_index == 'q':
        sys.exit(0)
    elif isinstance(selected_index, basestring) and selected_index == 'n':
        list_servers(args, startindex + perpage, perpage)
    elif isinstance(selected_index, basestring) and selected_index == 'p':
        list_servers(args, startindex - perpage, perpage)
    else:
        try:
            selected_index = int(selected_index) - 1
            # Indexes start at 0, so subtract one from here and length tests.
            if not (0 <= selected_index <= len(hostlist) - 1):
                raise TypeError('Range not valid!')
        except:
            print "Invalid entry, try again!"
            args.force_download = False
            list_servers(args)
        else:
            host_ip = hosts[hostlist[selected_index]]['ip']
            ssh = ['ssh']
            if 'port' in args and args.port is not None:
                ssh.extend(['-p', str(args.port)])
            if 'login_name' in args and args.login_name is not None:
                ssh.extend(['-l', args.login_name])
            if 'identity_file' in args and args.identity_file is not None:
                ssh.extend(['-i', args.identity_file])
            ssh.extend([host_ip])
            call(ssh)


if __name__ == '__main__':
    args = parse_args()
    list_servers(args, 0, args.perpage)

About the script

This script should work on Python 2.6 and 2.7. I have not tried it on 3.x, so YMMV there. Requirements that are not part of a standard Python install are boto and PyYAML. The script only polls AWS for the list of EC2 instances once per day (override with -F True), and stores the cached values in a YAML file in your home directory. The list generated will be the list of private IP addresses for your instances, the ‘Name’ tag that was setup at creation of the server, and the ‘Key’ name that was used at creation to allow access as the ec2-user.

Just enter the number next to the server in the list to try and ssh to it directly.

Please post below if you have any questions, otherwise I hope you found this useful!



blog comments powered by Disqus