import json import logging import os import sys import boto3 import dateutil OBJECT_DELETED_SINCE = os.getenv('OBJECT_DELETED_SINCE') OBJECT_DELETED_TILL = os.getenv('OBJECT_DELETED_TILL') CLOUDWATCH_NAMESPACE = os.getenv('CLOUDWATCH_NAMESPACE', 's3restore') AWS_DEFAULT_REGION = os.environ.get('AWS_DEFAULT_REGION', 'us-east-1') AWS_REGION = os.environ.get('AWS_REGION', AWS_DEFAULT_REGION) LOG_LEVEL = os.getenv('LOG_LEVEL', logging.INFO) s3 = boto3.resource('s3', region_name=AWS_REGION) cloudwatch = boto3.resource('cloudwatch', region_name=AWS_REGION) object_deleted_since = None object_deleted_till = None restored_object_count = {} def init(): if len(logging.getLogger().handlers) > 0: logging.getLogger().setLevel(LOG_LEVEL) else: logging.basicConfig( format='%(asctime)s %(levelname)s %(message)s', level=LOG_LEVEL ) if not OBJECT_DELETED_SINCE: logging.error('You need to set "OBJECT_DELETED_SINCE" environment ' 'variable in the ISO 8601 format ' '(i.e. "2023-03-30T10:00:00.000Z").') sys.exit(1) if not OBJECT_DELETED_TILL: logging.error('You need to set "OBJECT_DELETED_TILL" environment ' 'variable in the ISO 8601 format ' '(i.e. "2023-03-31T10:00:00.000Z").') sys.exit(1) try: global object_deleted_since, object_deleted_till object_deleted_since = dateutil.parser.parse(OBJECT_DELETED_SINCE) object_deleted_till = dateutil.parser.parse(OBJECT_DELETED_TILL) except dateutil.parser._parser.ParserError: logging.error('Incorrect format specified for "OBJECT_DELETED_SINCE" ' 'or "OBJECT_DELETED_TILL" environment variable which ' 'should be in the ISO 8601.') sys.exit(1) def handler(event, context): global restored_object_count for message in event['Records']: logging.info('Processing message ID "%s".', message['messageId']) data = json.loads(message['body']) for object_key in data['object_keys']: restored_object_count[data['bucket_name']] = 0 process_object(data['bucket_name'], object_key) publish_metrics() def process_object(bucket_name, object_key): global object_deleted_since, object_deleted_till global restored_object_count bucket = s3.Bucket(bucket_name) response = s3.meta.client.list_object_versions( Bucket=bucket.name, Prefix=object_key, MaxKeys=1) if 'DeleteMarkers' in response: latest_version = response['DeleteMarkers'][0] if latest_version['IsLatest']: last_modified = latest_version['LastModified'] logging.info('Object "s3://%s/%s" was deleted on "%s".', bucket.name, object_key, last_modified) if last_modified <= object_deleted_since: logging.info('Object "s3://%s/%s" was deleted before the ' 'deletion time window. Restoration will NOT be ' 'initialized.', bucket.name, object_key) return if last_modified >= object_deleted_till: logging.info('Object "s3://%s/%s" was deleted after the ' 'deletion time window. Restoration will NOT be ' 'initialized.', bucket.name, object_key) return logging.info('Object "s3://%s/%s" was deleted during the deletion ' 'time window. Restoration will be initialized.', bucket.name, object_key) obj = bucket.Object(object_key) obj.Version(latest_version['VersionId']).delete() restored_object_count[bucket.name] += 1 logging.warning('Object "s3://%s/%s" will be restored soon.', bucket.name, object_key) else: logging.info('Object "s3://%s/%s" was updated after deletion as ' 'it does not have the latest version of the delete ' 'marker.', bucket.name, object_key) elif 'Versions' in response: logging.info('Object "s3://%s/%s" does not have a delete marker, ' 'nothing to do.', bucket.name, object_key) else: logging.error('Failed to get any version info for "s3://%s/%s" ' 'object.', bucket.name, object_key) def publish_metrics(): global restored_object_count # for bucket_name, value in restored_object_count.items(): # cloudwatch.meta.client.put_metric_data( # Namespace=CLOUDWATCH_NAMESPACE, # MetricData=[ # { # 'MetricName': 'objects-restored', # 'Dimensions': [ # {'Name': 'bucket_name', 'Value': bucket_name} # ], # 'Value': value, # 'Unit': 'Count' # } # ]) restored_object_count = {} init()