make gs_utils.py work without a .boto credential file This is needed so developers without a .boto file can still run rebaseline_server. BUG=skia:2618 R=jcgregorio@google.com, rmistry@google.com Review URL: https://codereview.chromium.org/390133002
diff --git a/py/utils/gs_utils.py b/py/utils/gs_utils.py index afc3748..031a0c3 100755 --- a/py/utils/gs_utils.py +++ b/py/utils/gs_utils.py
@@ -37,9 +37,11 @@ # imported versions are favored over others that might be in the path. sys.path.insert(0, import_dirpath) from boto.gs import acl +from boto.gs.bucket import Bucket from boto.gs.connection import GSConnection from boto.gs.key import Key from boto.s3.bucketlistresultset import BucketListResultSet +from boto.s3.connection import SubdomainCallingFormat from boto.s3.prefix import Prefix # Permissions that may be set on each file in Google Storage. @@ -67,24 +69,46 @@ } +class AnonymousGSConnection(GSConnection): + """GSConnection class that allows anonymous connections. + + The GSConnection class constructor in + https://github.com/boto/boto/blob/develop/boto/gs/connection.py doesn't allow + for anonymous connections (connections without credentials), so we have to + override it. + """ + def __init__(self): + super(GSConnection, self).__init__( + # This is the important bit we need to add... + anon=True, + # ...and these are just copied in from GSConnection.__init__() + bucket_class=Bucket, + calling_format=SubdomainCallingFormat(), + host=GSConnection.DefaultHost, + provider='google') + + class GSUtils(object): """Utilities for accessing Google Cloud Storage, using the boto library.""" - def __init__(self, boto_file_path=os.path.join('~','.boto')): + def __init__(self, boto_file_path=None): """Constructor. Params: boto_file_path: full path (local-OS-style) on local disk where .boto - credentials file can be found. An exception is thrown if this file - is missing. - TODO(epoger): Change missing-file behavior: allow the caller to - operate on public files in Google Storage. + credentials file can be found. If None, then the GSUtils object + created will be able to access only public files in Google Storage. + + Raises an exception if no file is found at boto_file_path, or if the file + found there is malformed. """ - boto_file_path = os.path.expanduser(boto_file_path) - print 'Reading boto file from %s' % boto_file_path - boto_dict = _config_file_as_dict(filepath=boto_file_path) - self._gs_access_key_id = boto_dict['gs_access_key_id'] - self._gs_secret_access_key = boto_dict['gs_secret_access_key'] + self._gs_access_key_id = None + self._gs_secret_access_key = None + if boto_file_path: + print 'Reading boto file from %s' % boto_file_path + boto_dict = _config_file_as_dict(filepath=boto_file_path) + self._gs_access_key_id = boto_dict['gs_access_key_id'] + self._gs_secret_access_key = boto_dict['gs_secret_access_key'] def delete_file(self, bucket, path): """Delete a single file within a GS bucket. @@ -258,10 +282,12 @@ def _create_connection(self): """Returns a GSConnection object we can use to access Google Storage.""" - return GSConnection( - gs_access_key_id=self._gs_access_key_id, - gs_secret_access_key=self._gs_secret_access_key) - + if self._gs_access_key_id: + return GSConnection( + gs_access_key_id=self._gs_access_key_id, + gs_secret_access_key=self._gs_secret_access_key) + else: + return AnonymousGSConnection() def _config_file_as_dict(filepath): """Reads a boto-style config file into a dict. @@ -301,12 +327,27 @@ raise -def _run_self_test(): +def _test_public_read(): + """Make sure we can read from public files without .boto file credentials.""" + gs = GSUtils() + gs.list_bucket_contents(bucket='chromium-skia-gm-summaries', subdir=None) + + +def _test_authenticated_round_trip(): + try: + gs = GSUtils(boto_file_path=os.path.expanduser(os.path.join('~','.boto'))) + except: + print """ +Failed to instantiate GSUtils object with default .boto file path. +Do you have a ~/.boto file that provides the credentials needed to read +and write gs://chromium-skia-gm ? +""" + raise + bucket = 'chromium-skia-gm' remote_dir = 'gs_utils_test/%d' % random.randint(0, sys.maxint) subdir = 'subdir' filenames_to_upload = ['file1', 'file2'] - gs = GSUtils() # Upload test files to Google Storage. local_src_dir = tempfile.mkdtemp() @@ -393,11 +434,10 @@ assert files == [], '%s == []' % files -# TODO(epoger): How should we exercise this self-test? -# I avoided using the standard unittest framework, because these Google Storage -# operations are expensive and require .boto permissions. -# -# How can we automatically test this code without wasting too many resources -# or needing .boto permissions? +# TODO(epoger): How should we exercise these self-tests? +# See http://skbug.com/2751 if __name__ == '__main__': - _run_self_test() + _test_public_read() + _test_authenticated_round_trip() + # TODO(epoger): Add _test_unauthenticated_access() to make sure we raise + # an exception when we try to access without needed credentials.