Source code for exoctk.atmospheric_retrievals.aws_tools

#!/usr/bin/env python

"""This module contains various functions for interacting with AWS for
``exoctk`` atmospheric retrievals.

Authors
-------

    - Matthew Bourque

Use
---

    This script is inteneded to be imported and used by other modules,
    for example:

        from aws_tools import get_config
        get_config()

Dependencies
------------

    Dependent libraries include:

    - boto3
    - paramiko
    - scp

    Users must also have a ``aws_config.json`` file present within the
    ``atmospheric_retrievals`` subdirectory.  This file must be of
    a valid JSON format and contain two key/value pairs,
    ``ec2_id`` and ``ssh_file``, e.g.:

    {
    "ec2_id" : "lt-021de8b904bc2b728",
    "ssh_file" : "~/.ssh/my_ssh_key.pem"
    }

    where the ``ec2_id`` contains the ID for an EC2 launch template
    or an existing EC2 instance, and ``ssh_file`` points to the SSH
    public key used for logging into an AWS account.
"""

import json
import logging
import os
import time

import boto3
import paramiko
from scp import SCPClient


[docs]def build_environment(instance, key, client): """Builds an ``exoctk`` environment on the given AWS EC2 instance Parameters ---------- instance : obj A ``boto3`` AWS EC2 instance object. key : obj A ``paramiko.rsakey.RSAKey`` object. client : obj A ``paramiko.client.SSHClient`` object. """ logging.info('Building ExoCTK environment') # Connect to the EC2 instance and run commands connected = False iterations = 0 while not connected: if iterations == 12: logging.critical('Could not connect to {}'.format(instance.public_dns_name)) break try: client.connect(hostname=instance.public_dns_name, username='ec2-user', pkey=key) scp = SCPClient(client.get_transport()) scp.put('build-exoctk-env-cpu.sh', '~/build-exoctk-env-cpu.sh') stdin, stdout, stderr = client.exec_command('chmod 700 build-exoctk-env-cpu.sh && ./build-exoctk-env-cpu.sh') connected = True except: iterations += 1 time.sleep(5) output = stdout.read() log_output(output)
[docs]def get_config(): """Return a dictionary that holds the contents of the ``aws_config.json`` config file. Returns ------- settings : dict A dictionary that holds the contents of the config file. """ config_file_location = os.path.join(os.path.dirname(__file__), 'aws_config.json') if not os.path.isfile(config_file_location): raise FileNotFoundError('Missing AWS configuration file ("aws_config.json")') with open(config_file_location, 'r') as config_file: settings = json.load(config_file) return settings
[docs]def log_output(output): """Logs the given output of the EC2 instance. Parameters ---------- output : str The standard output of the EC2 instance """ output = output.decode("utf-8") output = output.replace('\t', ' ').replace('\r', '').replace("\'", "").split('\n') for line in output: logging.info(line)
[docs]def start_ec2(ssh_file, ec2_id): """Create a new EC2 instance or start an existing EC2 instance. A new EC2 instance will be created if the supplied ``ec2_id`` is an EC2 template ID. An existing EC2 instance will be started if the supplied ``ec2_id`` is an ID for an existing EC2 instance. Parameters ---------- ssh_file : str Relative path to SSH public key to be used by AWS (e.g. ``~/.ssh/exoctk.pem``). ec2_id : str The AWS EC2 template id (e.g. ``lt-021de8b904bc2b728``) or instance ID (e.g. ``i-0d0c8ca4ab324b260``). Returns ------- instance : obj A ``boto3`` AWS EC2 instance object. key : obj A ``paramiko.rsakey.RSAKey`` object. client : obj A ``paramiko.client.SSHClient`` object. """ ec2 = boto3.resource('ec2') # If the given ec2_id is for an EC2 template, then create the EC2 instance if ec2_id.split('-')[0] == 'lt': LaunchTemplate = {'LaunchTemplateId': ec2_id} instances = ec2.create_instances( LaunchTemplate=LaunchTemplate, MaxCount=1, MinCount=1) instance = instances[0] logging.info('Launched EC2 instance {}'.format(instance.id)) # If the given ec2_id is for an existing EC2 instance, then start it else: instance = ec2.Instance(ec2_id) instance.start() logging.info('Started EC2 instance {}'.format(ec2_id)) instance.wait_until_running() instance.load() # Establish SSH key and client key = paramiko.RSAKey.from_private_key_file(ssh_file) client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) return instance, key, client
[docs]def stop_ec2(ec2_id, instance): """Terminates or stops the given AWS EC2 instance. The instance is terminated if the supplied ``ec2_id`` is a EC2 template ID. The instance is stopped if the supplied ``ec2_id`` is an ID for a particular EC2 instance. Parameters ---------- ec2_id : str The AWS EC2 template id (e.g. ``lt-021de8b904bc2b728``) or instance ID (e.g. ``i-0d0c8ca4ab324b260``). instance : obj A ``boto3`` AWS EC2 instance object. """ ec2 = boto3.resource('ec2') # If the given ec2_id is for an EC2 template, then terminate the EC2 instance if ec2_id.split('-')[0] == 'lt': ec2.instances.filter(InstanceIds=[instance.id]).terminate() logging.info('Terminated EC2 instance {}'.format(instance.id)) # If the given ec2_id is for an existing EC2 instance, then stop it else: instance.stop() logging.info('Stopped EC2 instance {}'.format(ec2_id))
[docs]def transfer_from_ec2(instance, key, client, filename): """Copy files from EC2 user back to the user Parameters ---------- instance : obj A ``boto3`` AWS EC2 instance object. key : obj A ``paramiko.rsakey.RSAKey`` object. client : obj A ``paramiko.client.SSHClient`` object. filename : str The path to the file to transfer """ logging.info('Copying {} from EC2'.format(filename)) client.connect(hostname=instance.public_dns_name, username='ec2-user', pkey=key) scp = SCPClient(client.get_transport()) scp.get(filename)
[docs]def transfer_to_ec2(instance, key, client, filename): """Copy parameter file from user to EC2 instance Parameters ---------- instance : obj A ``boto3`` AWS EC2 instance object. key : obj A ``paramiko.rsakey.RSAKey`` object. client : obj A ``paramiko.client.SSHClient`` object. filename : str The path to the file to transfer """ logging.info('Copying {} to EC2'.format(filename)) connected = False iterations = 0 while not connected: if iterations >= 10: logging.critical('Could not connect to {}'.format(instance.public_dns_name)) break try: client.connect(hostname=instance.public_dns_name, username='ec2-user', pkey=key) scp = SCPClient(client.get_transport()) scp.put(filename) connected = True except: logging.warning('Could not connect to {}, retrying.'.format(instance.public_dns_name)) time.sleep(5) iterations += 1