# -*- coding: utf-8 -*- # Copyright (C) 2012 Anaconda, Inc # SPDX-License-Identifier: BSD-3-Clause from __future__ import absolute_import, division, print_function, unicode_literals import json from logging import LoggerAdapter, getLogger from tempfile import SpooledTemporaryFile have_boto3 = have_boto = False try: import boto3 have_boto3 = True except ImportError: try: import boto have_boto = True except ImportError: pass from .. import BaseAdapter, CaseInsensitiveDict, Response from ....common.compat import ensure_binary from ....common.url import url_to_s3_info log = getLogger(__name__) stderrlog = LoggerAdapter(getLogger('conda.stderrlog'), extra=dict(terminator="\n")) class S3Adapter(BaseAdapter): def __init__(self): super(S3Adapter, self).__init__() def send(self, request, stream=None, timeout=None, verify=None, cert=None, proxies=None): resp = Response() resp.status_code = 200 resp.url = request.url if have_boto3: return self._send_boto3(boto3, resp, request) elif have_boto: return self._send_boto(boto, resp, request) else: stderrlog.info('\nError: boto3 is required for S3 channels. ' 'Please install with `conda install boto3`\n' 'Make sure to run `source deactivate` if you ' 'are in a conda environment.\n') resp.status_code = 404 return resp def close(self): pass def _send_boto3(self, boto3, resp, request): from botocore.exceptions import BotoCoreError, ClientError bucket_name, key_string = url_to_s3_info(request.url) # https://github.com/conda/conda/issues/8993 # creating a separate boto3 session to make this thread safe session = boto3.session.Session() # create a resource client using this thread's session object s3 = session.resource('s3') # finally get the S3 object key = s3.Object(bucket_name, key_string[1:]) try: response = key.get() except (BotoCoreError, ClientError) as e: resp.status_code = 404 message = { "error": "error downloading file from s3", "path": request.url, "exception": repr(e), } resp.raw = self._write_tempfile(lambda x: x.write(ensure_binary(json.dumps(message)))) resp.close = resp.raw.close return resp key_headers = response['ResponseMetadata']['HTTPHeaders'] resp.headers = CaseInsensitiveDict({ "Content-Type": key_headers.get('content-type', "text/plain"), "Content-Length": key_headers['content-length'], "Last-Modified": key_headers['last-modified'], }) resp.raw = self._write_tempfile(key.download_fileobj) resp.close = resp.raw.close return resp def _send_boto(self, boto, resp, request): conn = boto.connect_s3() bucket_name, key_string = url_to_s3_info(request.url) bucket = conn.get_bucket(bucket_name, validate=False) try: key = bucket.get_key(key_string) except boto.exception.S3ResponseError as exc: resp.status_code = 404 resp.raw = exc return resp if key and key.exists: modified = key.last_modified content_type = key.content_type or "text/plain" resp.headers = CaseInsensitiveDict({ "Content-Type": content_type, "Content-Length": key.size, "Last-Modified": modified, }) resp.raw = self._write_tempfile(key.get_contents_to_file) resp.close = resp.raw.close else: resp.status_code = 404 return resp def _write_tempfile(self, writer_callable): fh = SpooledTemporaryFile() writer_callable(fh) fh.seek(0) return fh