#!/usr/bin/env python # We're using optparse because we need to support 2.6 # which doesn't have argparse. Given that argparse is # a dependency that eventually gets installed, we could # try to bootstrap, but using optparse is just easier. import optparse import os import platform import shutil import subprocess import sys import tarfile import tempfile from contextlib import contextmanager PACKAGES_DIR = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'packages') INSTALL_DIR = os.path.expanduser(os.path.join( '~', '.local', 'lib', 'aws')) class BadRCError(Exception): pass class MultipleBundlesError(Exception): pass @contextmanager def cd(dirname): original = os.getcwd() os.chdir(dirname) try: yield finally: os.chdir(original) def run(cmd): sys.stdout.write("Running cmd: %s\n" % cmd) p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() if p.returncode != 0: raise BadRCError("Bad rc (%s) for cmd '%s': %s" % ( p.returncode, cmd, stdout + stderr)) return stdout def bin_path(): """ Get the system's binary path, either `bin` on reasonable systems or `Scripts` on Windows. """ path = 'bin' if platform.system() == 'Windows': path = 'Scripts' return path def create_install_structure(working_dir, install_dir): if not os.path.isdir(install_dir): os.makedirs(install_dir) _create_virtualenv(location=install_dir, working_dir=working_dir) def _create_virtualenv(location, working_dir): # working_dir is used (generally somewhere in /tmp) so that we # don't modify the install/packages directories. with cd(PACKAGES_DIR): venv = [p for p in os.listdir('.') if p.startswith('virtualenv')][0] compressed = tarfile.open(venv) compressed.extractall(path=working_dir) compressed.close() with cd(working_dir): # We know that virtualenv is the only dir in this directory # so we can listdir()[0] it. with cd(os.listdir('.')[0]): run(('%s virtualenv.py --no-download ' '--python %s %s') % (sys.executable, sys.executable, location)) def create_working_dir(): d = tempfile.mkdtemp() return d def pip_install_packages(install_dir): cli_tarball = [p for p in os.listdir(PACKAGES_DIR) if p.startswith('awscli')] if len(cli_tarball) != 1: message = ( "Multiple versions of the CLI were found in %s. Please clear " "out this directory before proceeding." ) raise MultipleBundlesError(message % PACKAGES_DIR) cli_tarball = cli_tarball[0] pip_script = os.path.join(install_dir, bin_path(), 'pip') # Some packages declare `setup_requires`, which is a list of dependencies # to be used at setup time. These need to be installed before anything # else, and pip doesn't manage them. setup_requires_dir = os.path.join(PACKAGES_DIR, 'setup') with cd(setup_requires_dir): for package in os.listdir(setup_requires_dir): run('%s install --no-cache-dir --no-index --find-links file://%s %s' % ( pip_script, setup_requires_dir, package )) with cd(PACKAGES_DIR): run('%s install --no-cache-dir --no-index --find-links file://%s %s' % ( pip_script, PACKAGES_DIR, cli_tarball)) def create_symlink(real_location, symlink_name): if os.path.isfile(symlink_name): print("Symlink already exists: %s" % symlink_name) print("Removing symlink.") os.remove(symlink_name) symlink_dir_name = os.path.dirname(symlink_name) if not os.path.isdir(symlink_dir_name): os.makedirs(symlink_dir_name) os.symlink(real_location, symlink_name) return True def main(): parser = optparse.OptionParser() parser.add_option('-i', '--install-dir', help="The location to install " "the AWS CLI. The default value is ~/.local/lib/aws", default=INSTALL_DIR) parser.add_option('-b', '--bin-location', help="If this argument is " "provided, then a symlink will be created at this " "location that points to the aws executable. " "This argument is useful if you want to put the aws " "executable somewhere already on your path, e.g. " "-b /usr/local/bin/aws. This is an optional argument. " "If you do not provide this argument you will have to " "add INSTALL_DIR/bin to your PATH.") opts = parser.parse_args()[0] working_dir = create_working_dir() try: create_install_structure(working_dir, opts.install_dir) pip_install_packages(opts.install_dir) real_location = os.path.join(opts.install_dir, bin_path(), 'aws') if opts.bin_location and create_symlink(real_location, opts.bin_location): print("You can now run: %s --version" % opts.bin_location) else: print("You can now run: %s --version" % real_location) finally: shutil.rmtree(working_dir) if __name__ == '__main__': main()