| Index: py/utils/gs_utils.py
|
| diff --git a/py/utils/gs_utils.py b/py/utils/gs_utils.py
|
| old mode 100755
|
| new mode 100644
|
| index 43db0d7b5f3ab0c4b4eb1478e0f6853760b55521..6f08c1af8f7687c960813649a53d00fda3ec79a1
|
| --- a/py/utils/gs_utils.py
|
| +++ b/py/utils/gs_utils.py
|
| @@ -20,11 +20,8 @@ API/library references:
|
| import errno
|
| import os
|
| import posixpath
|
| -import random
|
| import re
|
| -import shutil
|
| import sys
|
| -import tempfile
|
|
|
| # Imports from third-party code
|
| TRUNK_DIRECTORY = os.path.abspath(os.path.join(
|
| @@ -45,43 +42,6 @@ from boto.s3.bucketlistresultset import BucketListResultSet
|
| from boto.s3.connection import SubdomainCallingFormat
|
| from boto.s3.prefix import Prefix
|
|
|
| -# Predefined (aka "canned") ACLs that provide a "base coat" of permissions for
|
| -# each file in Google Storage. See CannedACLStrings in
|
| -# https://github.com/boto/boto/blob/develop/boto/gs/acl.py
|
| -# Also see https://developers.google.com/storage/docs/accesscontrol
|
| -PREDEFINED_ACL_AUTHENTICATED_READ = 'authenticated-read'
|
| -PREDEFINED_ACL_BUCKET_OWNER_FULL_CONTROL = 'bucket-owner-full-control'
|
| -PREDEFINED_ACL_BUCKET_OWNER_READ = 'bucket-owner-read'
|
| -PREDEFINED_ACL_PRIVATE = 'private'
|
| -PREDEFINED_ACL_PROJECT_PRIVATE = 'project-private'
|
| -PREDEFINED_ACL_PUBLIC_READ = 'public-read'
|
| -PREDEFINED_ACL_PUBLIC_READ_WRITE = 'public-read-write'
|
| -
|
| -# "Fine-grained" permissions that may be set per user/group on each file in
|
| -# Google Storage. See SupportedPermissions in
|
| -# https://github.com/boto/boto/blob/develop/boto/gs/acl.py
|
| -# Also see https://developers.google.com/storage/docs/accesscontrol
|
| -PERMISSION_NONE = None
|
| -PERMISSION_OWNER = 'FULL_CONTROL'
|
| -PERMISSION_READ = 'READ'
|
| -PERMISSION_WRITE = 'WRITE'
|
| -
|
| -# Types of identifiers we can use to set "fine-grained" ACLs.
|
| -ID_TYPE_GROUP_BY_DOMAIN = acl.GROUP_BY_DOMAIN
|
| -ID_TYPE_GROUP_BY_EMAIL = acl.GROUP_BY_EMAIL
|
| -ID_TYPE_GROUP_BY_ID = acl.GROUP_BY_ID
|
| -ID_TYPE_USER_BY_EMAIL = acl.USER_BY_EMAIL
|
| -ID_TYPE_USER_BY_ID = acl.USER_BY_ID
|
| -
|
| -# Which field we get/set in ACL entries, depending on ID_TYPE.
|
| -FIELD_BY_ID_TYPE = {
|
| - ID_TYPE_GROUP_BY_DOMAIN: 'domain',
|
| - ID_TYPE_GROUP_BY_EMAIL: 'email_address',
|
| - ID_TYPE_GROUP_BY_ID: 'id',
|
| - ID_TYPE_USER_BY_EMAIL: 'email_address',
|
| - ID_TYPE_USER_BY_ID: 'id',
|
| -}
|
| -
|
|
|
| class AnonymousGSConnection(GSConnection):
|
| """GSConnection class that allows anonymous connections.
|
| @@ -105,6 +65,42 @@ class AnonymousGSConnection(GSConnection):
|
| class GSUtils(object):
|
| """Utilities for accessing Google Cloud Storage, using the boto library."""
|
|
|
| + class Permission:
|
| + """Fine-grained permissions that may be set per user/group on each file.
|
| +
|
| + See SupportedPermissions in
|
| + https://github.com/boto/boto/blob/develop/boto/gs/acl.py
|
| + Also see https://developers.google.com/storage/docs/accesscontrol
|
| + """
|
| + EMPTY = None
|
| + OWNER = 'FULL_CONTROL'
|
| + READ = 'READ'
|
| + WRITE = 'WRITE'
|
| +
|
| + class PredefinedACL:
|
| + """Canned ACLs that provide a "base coat" of permissions for each file.
|
| +
|
| + See CannedACLStrings in
|
| + https://github.com/boto/boto/blob/develop/boto/gs/acl.py
|
| + Also see https://developers.google.com/storage/docs/accesscontrol
|
| + """
|
| + AUTHENTICATED_READ = 'authenticated-read'
|
| + BUCKET_OWNER_FULL_CONTROL = 'bucket-owner-full-control'
|
| + BUCKET_OWNER_READ = 'bucket-owner-read'
|
| + PRIVATE = 'private'
|
| + PROJECT_PRIVATE = 'project-private'
|
| + PUBLIC_READ = 'public-read'
|
| + PUBLIC_READ_WRITE = 'public-read-write'
|
| +
|
| + class IdType:
|
| + """Types of identifiers we can use to set "fine-grained" ACLs."""
|
| + GROUP_BY_DOMAIN = acl.GROUP_BY_DOMAIN
|
| + GROUP_BY_EMAIL = acl.GROUP_BY_EMAIL
|
| + GROUP_BY_ID = acl.GROUP_BY_ID
|
| + USER_BY_EMAIL = acl.USER_BY_EMAIL
|
| + USER_BY_ID = acl.USER_BY_ID
|
| +
|
| +
|
| def __init__(self, boto_file_path=None):
|
| """Constructor.
|
|
|
| @@ -123,6 +119,14 @@ class GSUtils(object):
|
| 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']
|
| + # Which field we get/set in ACL entries, depending on IdType.
|
| + self._field_by_id_type = {
|
| + self.IdType.GROUP_BY_DOMAIN: 'domain',
|
| + self.IdType.GROUP_BY_EMAIL: 'email_address',
|
| + self.IdType.GROUP_BY_ID: 'id',
|
| + self.IdType.USER_BY_EMAIL: 'email_address',
|
| + self.IdType.USER_BY_ID: 'id',
|
| + }
|
|
|
| def delete_file(self, bucket, path):
|
| """Delete a single file within a GS bucket.
|
| @@ -157,7 +161,7 @@ class GSUtils(object):
|
| dest_bucket: GCS bucket to copy the file to
|
| dest_path: full path (Posix-style) within that bucket
|
| predefined_acl: which predefined ACL to apply to the file on Google
|
| - Storage; must be one of the PREDEFINED_ACL_* constants defined above.
|
| + Storage; must be one of the PredefinedACL values defined above.
|
| If None, inherits dest_bucket's default object ACL.
|
| TODO(epoger): add unittests for this param, although it seems to work
|
| in my manual testing
|
| @@ -195,7 +199,7 @@ class GSUtils(object):
|
| dest_dir: full path (Posix-style) within that bucket; write the files into
|
| this directory
|
| predefined_acl: which predefined ACL to apply to the files on Google
|
| - Storage; must be one of the PREDEFINED_ACL_* constants defined above.
|
| + Storage; must be one of the PredefinedACL values defined above.
|
| If None, inherits dest_bucket's default object ACL.
|
| TODO(epoger): add unittests for this param, although it seems to work
|
| in my manual testing
|
| @@ -326,15 +330,15 @@ class GSUtils(object):
|
| Params:
|
| bucket: GS bucket
|
| path: full path (Posix-style) to the file within that bucket
|
| - id_type: must be one of the ID_TYPE_* constants defined above
|
| + id_type: must be one of the IdType values defined above
|
| id_value: get permissions for users whose id_type field contains this
|
| value
|
|
|
| - Returns: the PERMISSION_* constant which has been set for users matching
|
| - this id_type/id_value, on this file; or PERMISSION_NONE if no such
|
| + Returns: the Permission value which has been set for users matching
|
| + this id_type/id_value, on this file; or Permission.EMPTY if no such
|
| permissions have been set.
|
| """
|
| - field = FIELD_BY_ID_TYPE[id_type]
|
| + field = self._field_by_id_type[id_type]
|
| b = self._connect_to_bucket(bucket_name=bucket)
|
| acls = b.get_acl(key_name=path)
|
| matching_entries = [entry for entry in acls.entries.entry_list
|
| @@ -344,7 +348,7 @@ class GSUtils(object):
|
| assert len(matching_entries) == 1, '%d == 1' % len(matching_entries)
|
| return matching_entries[0].permission
|
| else:
|
| - return PERMISSION_NONE
|
| + return self.Permission.EMPTY
|
|
|
| def set_acl(self, bucket, path, id_type, id_value, permission):
|
| """Set partial access permissions on a single file in Google Storage.
|
| @@ -364,11 +368,11 @@ class GSUtils(object):
|
| Params:
|
| bucket: GS bucket
|
| path: full path (Posix-style) to the file within that bucket
|
| - id_type: must be one of the ID_TYPE_* constants defined above
|
| + id_type: must be one of the IdType values defined above
|
| id_value: add permission for users whose id_type field contains this value
|
| permission: permission to add for users matching id_type/id_value;
|
| - must be one of the PERMISSION_* constants defined above.
|
| - If PERMISSION_NONE, then any permissions will be granted to this
|
| + must be one of the Permission values defined above.
|
| + If Permission.EMPTY, then any permissions will be granted to this
|
| particular id_type/id_value will be removed... but, given that
|
| permissions are additive, specific users may still have access rights
|
| based on permissions given to *other* id_type/id_value pairs.
|
| @@ -376,14 +380,14 @@ class GSUtils(object):
|
| Example Code:
|
| bucket = 'gs://bucket-name'
|
| path = 'path/to/file'
|
| - id_type = ID_TYPE_USER_BY_EMAIL
|
| + id_type = IdType.USER_BY_EMAIL
|
| id_value = 'epoger@google.com'
|
| - set_acl(bucket, path, id_type, id_value, PERMISSION_READ)
|
| - assert PERMISSION_READ == get_acl(bucket, path, id_type, id_value)
|
| - set_acl(bucket, path, id_type, id_value, PERMISSION_WRITE)
|
| - assert PERMISSION_WRITE == get_acl(bucket, path, id_type, id_value)
|
| + set_acl(bucket, path, id_type, id_value, Permission.READ)
|
| + assert Permission.READ == get_acl(bucket, path, id_type, id_value)
|
| + set_acl(bucket, path, id_type, id_value, Permission.WRITE)
|
| + assert Permission.WRITE == get_acl(bucket, path, id_type, id_value)
|
| """
|
| - field = FIELD_BY_ID_TYPE[id_type]
|
| + field = self._field_by_id_type[id_type]
|
| b = self._connect_to_bucket(bucket_name=bucket)
|
| acls = b.get_acl(key_name=path)
|
|
|
| @@ -397,7 +401,7 @@ class GSUtils(object):
|
| acls.entries.entry_list.remove(matching_entries[0])
|
|
|
| # Add a new entry to the ACLs.
|
| - if permission != PERMISSION_NONE:
|
| + if permission != self.Permission.EMPTY:
|
| args = {'type': id_type, 'permission': permission}
|
| args[field] = id_value
|
| new_entry = acl.Entry(**args)
|
| @@ -455,6 +459,7 @@ class GSUtils(object):
|
| else:
|
| return AnonymousGSConnection()
|
|
|
| +
|
| def _config_file_as_dict(filepath):
|
| """Reads a boto-style config file into a dict.
|
|
|
| @@ -491,200 +496,3 @@ def _makedirs_if_needed(path):
|
| except OSError as e:
|
| if e.errno != errno.EEXIST:
|
| raise
|
| -
|
| -
|
| -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']
|
| -
|
| - # Upload test files to Google Storage, checking that their fine-grained
|
| - # ACLs were set correctly.
|
| - id_type = ID_TYPE_GROUP_BY_DOMAIN
|
| - id_value = 'chromium.org'
|
| - set_permission = PERMISSION_READ
|
| - local_src_dir = tempfile.mkdtemp()
|
| - os.mkdir(os.path.join(local_src_dir, subdir))
|
| - try:
|
| - for filename in filenames_to_upload:
|
| - with open(os.path.join(local_src_dir, subdir, filename), 'w') as f:
|
| - f.write('contents of %s\n' % filename)
|
| - dest_path = posixpath.join(remote_dir, subdir, filename)
|
| - gs.upload_file(
|
| - source_path=os.path.join(local_src_dir, subdir, filename),
|
| - dest_bucket=bucket, dest_path=dest_path,
|
| - fine_grained_acl_list=[(id_type, id_value, set_permission)])
|
| - got_permission = gs.get_acl(bucket=bucket, path=dest_path,
|
| - id_type=id_type, id_value=id_value)
|
| - assert got_permission == set_permission, '%s == %s' % (
|
| - got_permission, set_permission)
|
| - finally:
|
| - shutil.rmtree(local_src_dir)
|
| -
|
| - # Get a list of the files we uploaded to Google Storage.
|
| - (dirs, files) = gs.list_bucket_contents(
|
| - bucket=bucket, subdir=remote_dir)
|
| - assert dirs == [subdir], '%s == [%s]' % (dirs, subdir)
|
| - assert files == [], '%s == []' % files
|
| - (dirs, files) = gs.list_bucket_contents(
|
| - bucket=bucket, subdir=posixpath.join(remote_dir, subdir))
|
| - assert dirs == [], '%s == []' % dirs
|
| - assert files == filenames_to_upload, '%s == %s' % (files, filenames_to_upload)
|
| -
|
| - # Manipulate ACLs on one of those files, and verify them.
|
| - # TODO(epoger): Test id_types other than ID_TYPE_GROUP_BY_DOMAIN ?
|
| - # TODO(epoger): Test setting multiple ACLs on the same file?
|
| - id_type = ID_TYPE_GROUP_BY_DOMAIN
|
| - id_value = 'google.com'
|
| - fullpath = posixpath.join(remote_dir, subdir, filenames_to_upload[0])
|
| - # Make sure ACL is empty to start with ...
|
| - gs.set_acl(bucket=bucket, path=fullpath,
|
| - id_type=id_type, id_value=id_value, permission=PERMISSION_NONE)
|
| - permission = gs.get_acl(bucket=bucket, path=fullpath,
|
| - id_type=id_type, id_value=id_value)
|
| - assert permission == PERMISSION_NONE, '%s == %s' % (
|
| - permission, PERMISSION_NONE)
|
| - # ... set it to OWNER ...
|
| - gs.set_acl(bucket=bucket, path=fullpath,
|
| - id_type=id_type, id_value=id_value, permission=PERMISSION_OWNER)
|
| - permission = gs.get_acl(bucket=bucket, path=fullpath,
|
| - id_type=id_type, id_value=id_value)
|
| - assert permission == PERMISSION_OWNER, '%s == %s' % (
|
| - permission, PERMISSION_OWNER)
|
| - # ... now set it to READ ...
|
| - gs.set_acl(bucket=bucket, path=fullpath,
|
| - id_type=id_type, id_value=id_value, permission=PERMISSION_READ)
|
| - permission = gs.get_acl(bucket=bucket, path=fullpath,
|
| - id_type=id_type, id_value=id_value)
|
| - assert permission == PERMISSION_READ, '%s == %s' % (
|
| - permission, PERMISSION_READ)
|
| - # ... and clear it again to finish.
|
| - gs.set_acl(bucket=bucket, path=fullpath,
|
| - id_type=id_type, id_value=id_value, permission=PERMISSION_NONE)
|
| - permission = gs.get_acl(bucket=bucket, path=fullpath,
|
| - id_type=id_type, id_value=id_value)
|
| - assert permission == PERMISSION_NONE, '%s == %s' % (
|
| - permission, PERMISSION_NONE)
|
| -
|
| - # Download the files we uploaded to Google Storage, and validate contents.
|
| - local_dest_dir = tempfile.mkdtemp()
|
| - try:
|
| - for filename in filenames_to_upload:
|
| - gs.download_file(source_bucket=bucket,
|
| - source_path=posixpath.join(remote_dir, subdir, filename),
|
| - dest_path=os.path.join(local_dest_dir, subdir, filename),
|
| - create_subdirs_if_needed=True)
|
| - with open(os.path.join(local_dest_dir, subdir, filename)) as f:
|
| - file_contents = f.read()
|
| - assert file_contents == 'contents of %s\n' % filename, (
|
| - '%s == "contents of %s\n"' % (file_contents, filename))
|
| - finally:
|
| - shutil.rmtree(local_dest_dir)
|
| -
|
| - # Delete all the files we uploaded to Google Storage.
|
| - for filename in filenames_to_upload:
|
| - gs.delete_file(bucket=bucket,
|
| - path=posixpath.join(remote_dir, subdir, filename))
|
| -
|
| - # Confirm that we deleted all the files we uploaded to Google Storage.
|
| - (dirs, files) = gs.list_bucket_contents(
|
| - bucket=bucket, subdir=posixpath.join(remote_dir, subdir))
|
| - assert dirs == [], '%s == []' % dirs
|
| - assert files == [], '%s == []' % files
|
| -
|
| -
|
| -def _test_dir_upload_and_download():
|
| - """Test upload_dir_contents() and download_dir_contents()."""
|
| - 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 = ['file1', 'file2']
|
| -
|
| - # Create directory tree on local disk and upload it.
|
| - id_type = ID_TYPE_GROUP_BY_DOMAIN
|
| - id_value = 'chromium.org'
|
| - set_permission = PERMISSION_READ
|
| - local_src_dir = tempfile.mkdtemp()
|
| - os.mkdir(os.path.join(local_src_dir, subdir))
|
| - try:
|
| - for filename in filenames:
|
| - with open(os.path.join(local_src_dir, subdir, filename), 'w') as f:
|
| - f.write('contents of %s\n' % filename)
|
| - gs.upload_dir_contents(
|
| - source_dir=local_src_dir, dest_bucket=bucket, dest_dir=remote_dir,
|
| - predefined_acl=PREDEFINED_ACL_PRIVATE,
|
| - fine_grained_acl_list=[(id_type, id_value, set_permission)])
|
| - finally:
|
| - shutil.rmtree(local_src_dir)
|
| -
|
| - # Validate the list of the files we uploaded to Google Storage.
|
| - (dirs, files) = gs.list_bucket_contents(
|
| - bucket=bucket, subdir=remote_dir)
|
| - assert dirs == [subdir], '%s == [%s]' % (dirs, subdir)
|
| - assert files == [], '%s == []' % files
|
| - (dirs, files) = gs.list_bucket_contents(
|
| - bucket=bucket, subdir=posixpath.join(remote_dir, subdir))
|
| - assert dirs == [], '%s == []' % dirs
|
| - assert files == filenames, '%s == %s' % (files, filenames)
|
| -
|
| - # Check the fine-grained ACLs we set in Google Storage.
|
| - for filename in filenames:
|
| - got_permission = gs.get_acl(
|
| - bucket=bucket, path=posixpath.join(remote_dir, subdir, filename),
|
| - id_type=id_type, id_value=id_value)
|
| - assert got_permission == set_permission, '%s == %s' % (
|
| - got_permission, set_permission)
|
| -
|
| - # Download the directory tree we just uploaded, make sure its contents
|
| - # are what we expect, and then delete the tree in Google Storage.
|
| - local_dest_dir = tempfile.mkdtemp()
|
| - try:
|
| - gs.download_dir_contents(source_bucket=bucket, source_dir=remote_dir,
|
| - dest_dir=local_dest_dir)
|
| - for filename in filenames:
|
| - with open(os.path.join(local_dest_dir, subdir, filename)) as f:
|
| - file_contents = f.read()
|
| - assert file_contents == 'contents of %s\n' % filename, (
|
| - '%s == "contents of %s\n"' % (file_contents, filename))
|
| - finally:
|
| - shutil.rmtree(local_dest_dir)
|
| - for filename in filenames:
|
| - gs.delete_file(bucket=bucket,
|
| - path=posixpath.join(remote_dir, subdir, filename))
|
| -
|
| -
|
| -# TODO(epoger): How should we exercise these self-tests?
|
| -# See http://skbug.com/2751
|
| -if __name__ == '__main__':
|
| - _test_public_read()
|
| - _test_authenticated_round_trip()
|
| - _test_dir_upload_and_download()
|
| - # TODO(epoger): Add _test_unauthenticated_access() to make sure we raise
|
| - # an exception when we try to access without needed credentials.
|
|
|