""" AWS Simple Storage Service (S3) =============================== All the tasks that are related to the AWS S3. """ import os import subprocess from boto.s3.connection import Bucket from boto.s3.connection import Key from boto.s3.connection import S3Connection from garcon import task from garcon.contrib.aws.utils import s3 as s3_utils import smart_open @task.decorate() def remove_files_from_path(activity, path, return_deleted_files): """Removes all the objects from a given bucket path. Deleted objects include anything that starts with the path. Args: activity (ActivityWorker): the swf activity worker. path (str): S3 URL formatted like 's3://bucket_name/folder1/folder2/' return_deleted_files (bool): whether return deleted file names or not Return: dict: One element dict with a list of deleted files. """ bucket_name, bucket_path = s3_utils.extract_bucket_path(path) b = Bucket(S3Connection(), bucket_name) file_keys = [key.name for key in b.list(prefix=bucket_path)] result = b.delete_keys(file_keys) if not return_deleted_files: return_deleted_files = False if len(result.errors) > 0: activity.logger.error( 'Error while deleting objects from {path}'.format(path=path)) raise Exception('Not all files in s3 have been removed.') if len(result.deleted) > 0: activity.logger.info( 'Deleted all S3 objects from {path}'.format(path=path)) if return_deleted_files is True: return { 's3.files_removed': file_keys } return {} @task.decorate() def create_object(activity, path, content): """Create file in path with content Create file in path with content. If file already exist, content will be replaced. Args: activity (ActivityWorker): the swf activity worker path (str): S3 URL formatted like 's3://bucket_name/folder1/file' content (str): content of s3 file """ assert not path[-1:] == '/', 'S3 path should point to a file' bucket_name, bucket_path = s3_utils.extract_bucket_path(path) file_bucket = Bucket(S3Connection(), bucket_name) key_object = Key(file_bucket) key_object.key = bucket_path key_object.set_contents_from_string(content) @task.decorate(timeout=7200) def join_s3_objects(activity, source_s3_path, destination_s3_path): """Join S3 object Merge S3 objects in a source bucket/key and write it directly to a destination S3 bucket/key. Following are the examples: source_s3_path: 's3://test_bucket/emr/soundscan/2015-04-27/CANADA_PRODUCT/' destination_s3_path is: 's3://test_bucket/emr/soundscan/2015-04-27/merged/CANADA_PRODUCT/CANADA_PRODUCT.txt.gz' Args: activity (ActivityWorker): the swf activity worker source_s3_path (str): source path to source s3 key. destination_s3_path (str): destination path to merged file Returns: command (str): command ran """ bucket_name, source_folder = s3_utils.extract_bucket_path(source_s3_path) bucket = Bucket(S3Connection(), bucket_name) file_objects = ( ' <(aws s3 cp {source_s3_path} -) '.format( source_s3_path='s3://{bucket_name}/{key}'.format( bucket_name=bucket_name, key=key.name)) for key in bucket.list(prefix=source_folder)) upload_command = 'aws s3 cp - {destination_s3_path}'.format( destination_s3_path=destination_s3_path) command = 'cat {copy} | {upload}'.format( copy=''.join(file_objects), upload=upload_command) subprocess.call(['bash', '-c', command]) @task.decorate(timeout=7200) def stream_join_s3_objects(activity, source_s3_path, destination_s3_path): """Join S3 objects into one object Args: activity (ActivityWorker): the activity worker. source_s3_path (str): folder where source data is located. destination_s3_path (str): folder where final report file is located. """ bucket_name, source_key = s3_utils.extract_bucket_path(source_s3_path) conn = S3Connection() bucket = conn.get_bucket(bucket_name) s3_objects = bucket.list(prefix=source_key.strip('/')) with smart_open.smart_open(destination_s3_path, 'wb') as fout: for obj in s3_objects: if os.path.basename(obj.name): for line in smart_open.smart_open( 's3://{}/{}'.format(bucket_name, obj.name)): fout.write(line)