diff --git a/docs/_quickstart.rst b/docs/_quickstart.rst index 9d990e9..ddb9883 100644 --- a/docs/_quickstart.rst +++ b/docs/_quickstart.rst @@ -4,6 +4,13 @@ See ``terrarium --help`` for a complete list of options and commands. +.. note:: + + The following documentation assumes you are familiar with `virtualenv + `_. If not, run through `this + tutorial `_. We'll + wait. + Creating a new environment ########################## @@ -12,7 +19,7 @@ includes the packages defined in ``requirements.txt`` .. code-block:: shell-session - $ terrarium --target env install requirements.txt + $ terrarium --target path/to/env install requirements.txt Replacing an existing environment ################################# @@ -22,8 +29,8 @@ existing activated virtual environment with a different set of packages. .. code-block:: shell-session - $ terrarium --target env install requirements.txt - $ source env/bin/activate + $ terrarium --target path/to/env install requirements.txt + $ source path/to/env/bin/activate $ terrarium install other_requirements.txt .. note:: diff --git a/requirements/testing.txt b/requirements/testing.txt index f3c7e8e..87a4388 100644 --- a/requirements/testing.txt +++ b/requirements/testing.txt @@ -1 +1,2 @@ nose +requests==1.0.0 diff --git a/setup.py b/setup.py index e872013..8a45ae4 100644 --- a/setup.py +++ b/setup.py @@ -43,6 +43,7 @@ def main(): python_version = sys.version_info[:2] install_requires = [ 'virtualenv>=1.7.2,<=1.9.1', + 'requests>=1.0.0', ] if python_version < (2, 7) or (3, 0) <= python_version <= (3, 1): install_requires += ['argparse'] diff --git a/terrarium/terrarium.py b/terrarium/terrarium.py index 269c6f2..d355d0b 100755 --- a/terrarium/terrarium.py +++ b/terrarium/terrarium.py @@ -8,6 +8,7 @@ import tempfile import shutil import logging +import requests from logging import getLogger, StreamHandler @@ -393,6 +394,50 @@ def _get_s3_bucket(self): pass return boto.s3.bucket.Bucket(conn, name=self.args.s3_bucket) + def _attempt_s3_download(self, target): + # attempt to download from public S3 url + base_s3_url = "https://%(bucket_name)s.s3.amazonaws.com/%(key)s" + remote_key = self.make_remote_key() + bucket_name = self.args.s3_bucket + s3_url = base_s3_url % {"bucket_name": bucket_name, "key": remote_key} + r = requests.get(s3_url, stream=True) + if r.ok: + fd, archive = tempfile.mkstemp() + with open(archive, 'wb') as f: + for chunk in r.iter_content(1024): + f.write(chunk) + + self.extract(archive, target) + os.close(fd) + os.unlink(archive) + return True + # Check if permissions denied + elif not self.args.s3_access_key and 'AccessDenied' in r.content: + print ( + 'Access was denied to s3 bucket hash %s ' + 'and no key was provided. ' + 'Make sure the s3 bucket hash is public ' + 'readable.' % (remote_key) + ) + + if not boto: + return False + bucket = self._get_s3_bucket() + if bucket: + key = bucket.get_key(remote_key) + if key: + logger.info( + 'Downloading %s/%s from S3 ' + '(this may take time) ...' + % (self.args.s3_bucket, remote_key) + ) + fd, archive = tempfile.mkstemp() + key.get_contents_to_filename(archive) + self.extract(archive, target) + os.close(fd) + os.unlink(archive) + return True + def download(self, target): if self.args.storage_dir: remote_archive = os.path.join( @@ -413,23 +458,10 @@ def download(self, target): os.unlink(local_archive) return True logger.error('Download archive failed') - if boto and self.args.s3_bucket: - bucket = self._get_s3_bucket() - if bucket: - remote_key = self.make_remote_key() - key = bucket.get_key(remote_key) - if key: - logger.info( - 'Downloading %s/%s from S3 ' - '(this may take time) ...' - % (self.args.s3_bucket, remote_key) - ) - fd, archive = tempfile.mkstemp() - key.get_contents_to_filename(archive) - self.extract(archive, target) - os.close(fd) - os.unlink(archive) - return True + + if self.args.s3_bucket: + download_successful = self._attempt_s3_download(target) + return download_successful def make_remote_key(self): import platform @@ -493,8 +525,14 @@ def upload(self, target): target, self.args.storage_dir, ) - if boto and self.args.s3_bucket: - self.upload_to_s3(target) + if self.args.s3_bucket: + if boto: + self.upload_to_s3(target) + else: + logger.critical( + "--upload combined with --s3-bucket requires that you " + "have boto installed, which does not appear to be the case" + ) def create_bootstrap(self, dest): extra_text = ( @@ -784,12 +822,6 @@ def parse_args(): args = ap.parse_args() args.add_sensitive_arguments(*sensitive_arguments) - if not boto and args.s3_bucket is not None: - ap.error( - '--s3-bucket requires that you have boto installed, ' - 'which does not appear to be the case' - ) - return args diff --git a/tests/tests.py b/tests/tests.py index f1ba95e..a0b0f02 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -500,18 +500,13 @@ def test_logging_output(self): 'Terrarium is finished\n' )) - def test_boto_required_to_use_s3_bucket(self): + def test_boto_not_required_to_use_s3_bucket(self): self._add_test_requirement() - output = self.assertInstall( - return_code=2, + self.assertInstall( + return_code=0, s3_bucket='bucket', ) - self.assertTrue( - 'error: --s3-bucket requires that you have boto installed, ' - 'which does not appear to be the case' - in output[1] - ) def test_sensitive_arguments_are_sensitive(self): command = 'hash %s' % (