Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(600)

Side by Side Diff: py/utils/gs_utils.py

Issue 390133002: make gs_utils.py work without a .boto credential file (Closed) Base URL: https://skia.googlesource.com/common.git@master
Patch Set: rebase Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 2
3 # pylint: disable=C0301 3 # pylint: disable=C0301
4 """ 4 """
5 Copyright 2014 Google Inc. 5 Copyright 2014 Google Inc.
6 6
7 Use of this source code is governed by a BSD-style license that can be 7 Use of this source code is governed by a BSD-style license that can be
8 found in the LICENSE file. 8 found in the LICENSE file.
9 9
10 Utilities for accessing Google Cloud Storage, using the boto library (wrapper 10 Utilities for accessing Google Cloud Storage, using the boto library (wrapper
(...skipping 19 matching lines...) Expand all
30 TRUNK_DIRECTORY = os.path.abspath(os.path.join( 30 TRUNK_DIRECTORY = os.path.abspath(os.path.join(
31 os.path.dirname(__file__), os.pardir, os.pardir)) 31 os.path.dirname(__file__), os.pardir, os.pardir))
32 for import_subdir in ['boto']: 32 for import_subdir in ['boto']:
33 import_dirpath = os.path.join( 33 import_dirpath = os.path.join(
34 TRUNK_DIRECTORY, 'third_party', 'externals', import_subdir) 34 TRUNK_DIRECTORY, 'third_party', 'externals', import_subdir)
35 if import_dirpath not in sys.path: 35 if import_dirpath not in sys.path:
36 # We need to insert at the beginning of the path, to make sure that our 36 # We need to insert at the beginning of the path, to make sure that our
37 # imported versions are favored over others that might be in the path. 37 # imported versions are favored over others that might be in the path.
38 sys.path.insert(0, import_dirpath) 38 sys.path.insert(0, import_dirpath)
39 from boto.gs import acl 39 from boto.gs import acl
40 from boto.gs.bucket import Bucket
40 from boto.gs.connection import GSConnection 41 from boto.gs.connection import GSConnection
41 from boto.gs.key import Key 42 from boto.gs.key import Key
42 from boto.s3.bucketlistresultset import BucketListResultSet 43 from boto.s3.bucketlistresultset import BucketListResultSet
44 from boto.s3.connection import SubdomainCallingFormat
43 from boto.s3.prefix import Prefix 45 from boto.s3.prefix import Prefix
44 46
45 # Permissions that may be set on each file in Google Storage. 47 # Permissions that may be set on each file in Google Storage.
46 # See SupportedPermissions in 48 # See SupportedPermissions in
47 # https://github.com/boto/boto/blob/develop/boto/gs/acl.py 49 # https://github.com/boto/boto/blob/develop/boto/gs/acl.py
48 PERMISSION_NONE = None 50 PERMISSION_NONE = None
49 PERMISSION_OWNER = 'FULL_CONTROL' 51 PERMISSION_OWNER = 'FULL_CONTROL'
50 PERMISSION_READ = 'READ' 52 PERMISSION_READ = 'READ'
51 PERMISSION_WRITE = 'WRITE' 53 PERMISSION_WRITE = 'WRITE'
52 54
53 # Types of identifiers we can use to set ACLs. 55 # Types of identifiers we can use to set ACLs.
54 ID_TYPE_GROUP_BY_DOMAIN = acl.GROUP_BY_DOMAIN 56 ID_TYPE_GROUP_BY_DOMAIN = acl.GROUP_BY_DOMAIN
55 ID_TYPE_GROUP_BY_EMAIL = acl.GROUP_BY_EMAIL 57 ID_TYPE_GROUP_BY_EMAIL = acl.GROUP_BY_EMAIL
56 ID_TYPE_GROUP_BY_ID = acl.GROUP_BY_ID 58 ID_TYPE_GROUP_BY_ID = acl.GROUP_BY_ID
57 ID_TYPE_USER_BY_EMAIL = acl.USER_BY_EMAIL 59 ID_TYPE_USER_BY_EMAIL = acl.USER_BY_EMAIL
58 ID_TYPE_USER_BY_ID = acl.USER_BY_ID 60 ID_TYPE_USER_BY_ID = acl.USER_BY_ID
59 61
60 # Which field we get/set in ACL entries, depending on ID_TYPE. 62 # Which field we get/set in ACL entries, depending on ID_TYPE.
61 FIELD_BY_ID_TYPE = { 63 FIELD_BY_ID_TYPE = {
62 ID_TYPE_GROUP_BY_DOMAIN: 'domain', 64 ID_TYPE_GROUP_BY_DOMAIN: 'domain',
63 ID_TYPE_GROUP_BY_EMAIL: 'email_address', 65 ID_TYPE_GROUP_BY_EMAIL: 'email_address',
64 ID_TYPE_GROUP_BY_ID: 'id', 66 ID_TYPE_GROUP_BY_ID: 'id',
65 ID_TYPE_USER_BY_EMAIL: 'email_address', 67 ID_TYPE_USER_BY_EMAIL: 'email_address',
66 ID_TYPE_USER_BY_ID: 'id', 68 ID_TYPE_USER_BY_ID: 'id',
67 } 69 }
68 70
69 71
72 class AnonymousGSConnection(GSConnection):
73 """The GSConnection class in the boto library doesn't allow for anonymous
74 connections (connection without credentials).
75 """
76 def __init__(self):
77 super(GSConnection, self).__init__(
78 anon=True, host=GSConnection.DefaultHost,
79 calling_format=SubdomainCallingFormat(), provider='google',
rmistry 2014/07/16 13:34:21 Could you explain the provider='google' ?
epoger 2014/07/16 13:47:42 Good question. I have tried to do so in patchset
80 bucket_class=Bucket)
81
82
70 class GSUtils(object): 83 class GSUtils(object):
71 """Utilities for accessing Google Cloud Storage, using the boto library.""" 84 """Utilities for accessing Google Cloud Storage, using the boto library."""
72 85
73 def __init__(self, boto_file_path=os.path.join('~','.boto')): 86 def __init__(self, boto_file_path=None):
74 """Constructor. 87 """Constructor.
75 88
76 Params: 89 Params:
77 boto_file_path: full path (local-OS-style) on local disk where .boto 90 boto_file_path: full path (local-OS-style) on local disk where .boto
78 credentials file can be found. An exception is thrown if this file 91 credentials file can be found. If None, then the GSUtils object
79 is missing. 92 created will be able to access only public files in Google Storage.
80 TODO(epoger): Change missing-file behavior: allow the caller to 93
81 operate on public files in Google Storage. 94 Raises an exception if no file is found at boto_file_path, or if the file
95 found there is malformed.
82 """ 96 """
83 boto_file_path = os.path.expanduser(boto_file_path) 97 self._gs_access_key_id = None
84 print 'Reading boto file from %s' % boto_file_path 98 self._gs_secret_access_key = None
85 boto_dict = _config_file_as_dict(filepath=boto_file_path) 99 if boto_file_path:
86 self._gs_access_key_id = boto_dict['gs_access_key_id'] 100 print 'Reading boto file from %s' % boto_file_path
87 self._gs_secret_access_key = boto_dict['gs_secret_access_key'] 101 boto_dict = _config_file_as_dict(filepath=boto_file_path)
102 self._gs_access_key_id = boto_dict['gs_access_key_id']
103 self._gs_secret_access_key = boto_dict['gs_secret_access_key']
88 104
89 def delete_file(self, bucket, path): 105 def delete_file(self, bucket, path):
90 """Delete a single file within a GS bucket. 106 """Delete a single file within a GS bucket.
91 107
92 TODO(epoger): what if bucket or path does not exist? Should probably raise 108 TODO(epoger): what if bucket or path does not exist? Should probably raise
93 an exception. Implement, and add a test to exercise this. 109 an exception. Implement, and add a test to exercise this.
94 110
95 Params: 111 Params:
96 bucket: GS bucket to delete a file from 112 bucket: GS bucket to delete a file from
97 path: full path (Posix-style) of the file within the bucket to delete 113 path: full path (Posix-style) of the file within the bucket to delete
(...skipping 153 matching lines...) Expand 10 before | Expand all | Expand 10 after
251 for item in lister: 267 for item in lister:
252 t = type(item) 268 t = type(item)
253 if t is Key: 269 if t is Key:
254 files.append(item.key[prefix_length:]) 270 files.append(item.key[prefix_length:])
255 elif t is Prefix: 271 elif t is Prefix:
256 dirs.append(item.name[prefix_length:-1]) 272 dirs.append(item.name[prefix_length:-1])
257 return (dirs, files) 273 return (dirs, files)
258 274
259 def _create_connection(self): 275 def _create_connection(self):
260 """Returns a GSConnection object we can use to access Google Storage.""" 276 """Returns a GSConnection object we can use to access Google Storage."""
261 return GSConnection( 277 if self._gs_access_key_id:
262 gs_access_key_id=self._gs_access_key_id, 278 return GSConnection(
263 gs_secret_access_key=self._gs_secret_access_key) 279 gs_access_key_id=self._gs_access_key_id,
264 280 gs_secret_access_key=self._gs_secret_access_key)
281 else:
282 return AnonymousGSConnection()
265 283
266 def _config_file_as_dict(filepath): 284 def _config_file_as_dict(filepath):
267 """Reads a boto-style config file into a dict. 285 """Reads a boto-style config file into a dict.
268 286
269 Parses all lines from the file of this form: key = value 287 Parses all lines from the file of this form: key = value
270 TODO(epoger): Create unittest. 288 TODO(epoger): Create unittest.
271 289
272 Params: 290 Params:
273 filepath: path to config file on local disk 291 filepath: path to config file on local disk
274 292
(...skipping 19 matching lines...) Expand all
294 Args: 312 Args:
295 path: full path of directory to create 313 path: full path of directory to create
296 """ 314 """
297 try: 315 try:
298 os.makedirs(path) 316 os.makedirs(path)
299 except OSError as e: 317 except OSError as e:
300 if e.errno != errno.EEXIST: 318 if e.errno != errno.EEXIST:
301 raise 319 raise
302 320
303 321
304 def _run_self_test(): 322 def _test_public_read():
323 """Make sure we can read from public files without .boto file credentials."""
324 gs = GSUtils()
325 gs.list_bucket_contents(bucket='chromium-skia-gm-summaries', subdir=None)
326
327
328 def _test_authenticated_round_trip():
329 try:
330 gs = GSUtils(boto_file_path=os.path.expanduser(os.path.join('~','.boto')))
331 except:
332 print """
333 Failed to instantiate GSUtils object with default .boto file path.
334 Do you have a ~/.boto file that provides the credentials needed to read
335 and write gs://chromium-skia-gm ?
336 """
337 raise
338
305 bucket = 'chromium-skia-gm' 339 bucket = 'chromium-skia-gm'
306 remote_dir = 'gs_utils_test/%d' % random.randint(0, sys.maxint) 340 remote_dir = 'gs_utils_test/%d' % random.randint(0, sys.maxint)
307 subdir = 'subdir' 341 subdir = 'subdir'
308 filenames_to_upload = ['file1', 'file2'] 342 filenames_to_upload = ['file1', 'file2']
309 gs = GSUtils()
310 343
311 # Upload test files to Google Storage. 344 # Upload test files to Google Storage.
312 local_src_dir = tempfile.mkdtemp() 345 local_src_dir = tempfile.mkdtemp()
313 os.mkdir(os.path.join(local_src_dir, subdir)) 346 os.mkdir(os.path.join(local_src_dir, subdir))
314 try: 347 try:
315 for filename in filenames_to_upload: 348 for filename in filenames_to_upload:
316 with open(os.path.join(local_src_dir, subdir, filename), 'w') as f: 349 with open(os.path.join(local_src_dir, subdir, filename), 'w') as f:
317 f.write('contents of %s\n' % filename) 350 f.write('contents of %s\n' % filename)
318 gs.upload_file(source_path=os.path.join(local_src_dir, subdir, filename), 351 gs.upload_file(source_path=os.path.join(local_src_dir, subdir, filename),
319 dest_bucket=bucket, 352 dest_bucket=bucket,
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
386 gs.delete_file(bucket=bucket, 419 gs.delete_file(bucket=bucket,
387 path=posixpath.join(remote_dir, subdir, filename)) 420 path=posixpath.join(remote_dir, subdir, filename))
388 421
389 # Confirm that we deleted all the files we uploaded to Google Storage. 422 # Confirm that we deleted all the files we uploaded to Google Storage.
390 (dirs, files) = gs.list_bucket_contents( 423 (dirs, files) = gs.list_bucket_contents(
391 bucket=bucket, subdir=posixpath.join(remote_dir, subdir)) 424 bucket=bucket, subdir=posixpath.join(remote_dir, subdir))
392 assert dirs == [], '%s == []' % dirs 425 assert dirs == [], '%s == []' % dirs
393 assert files == [], '%s == []' % files 426 assert files == [], '%s == []' % files
394 427
395 428
396 # TODO(epoger): How should we exercise this self-test? 429 # TODO(epoger): How should we exercise these self-tests?
397 # I avoided using the standard unittest framework, because these Google Storage 430 # See http://skbug.com/2751
398 # operations are expensive and require .boto permissions.
399 #
400 # How can we automatically test this code without wasting too many resources
401 # or needing .boto permissions?
402 if __name__ == '__main__': 431 if __name__ == '__main__':
403 _run_self_test() 432 _test_public_read()
433 _test_authenticated_round_trip()
434 # TODO(epoger): Add _test_unauthenticated_access() to make sure we raise
435 # an exception when we try to access without needed credentials.
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698