"""Lambda manage-backup-snapshots module.""" import json import boto3 import sentry_sdk from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration import config from common.logic import database # noqa:I202 from common.utils.logger import log as logger # noqa:I202 if config.SENTRY_DSN: sentry_sdk.init( dsn=config.SENTRY_DSN, environment=config.ENVIRONMENT, integrations=[AwsLambdaIntegration(timeout_warning=True)] ) def create_backup_snapshot( db_type, rds_client, source_account_id, source_snapshot_name): """ Create backup snapshot. Args: db_type (str): DB type rds_client (obj): boto3 RDS client source_account_id (str): source account id source_snapshot_name (str): name of source snapshot Returns: str: new snapshot name """ new_snapshot_name = f'{source_snapshot_name}-{source_account_id}' snapshot = database.copy_snapshot( source_snapshot_name, source_account_id, db_type, new_snapshot_name, 'automated_backup', config.BACKUP_KMS_KEY_ID, config.AWS_DEFAULT_REGION, client=rds_client, ) return snapshot def filter_automatic_snapshots_by_date(db_type, snapshots): """ Filter automatic snapshots and sort by oldest creation date. Args: db_type (str): DB type snapshots (list): List of snapshot objects Returns: dict: filtered snapshots in snapshot_name, creation_time format """ if db_type == 'cluster': matching_snapshots = { snapshot['DBClusterSnapshotIdentifier']: snapshot['SnapshotCreateTime'] # noqa:E501 for snapshot in snapshots if [ True for tag in snapshot['TagList'] if tag['Key'].lower() == 'snapshot_type' and tag['Value'] == 'automated_backup' # noqa:E501 ] } elif db_type == 'standalone': matching_snapshots = { snapshot['DBSnapshotIdentifier']: snapshot['SnapshotCreateTime'] for snapshot in snapshots if [True for tag in snapshot['TagList'] if ( (tag['Key'].lower() == 'snapshot_type' and tag['Value'] == 'automated_backup'))]} # noqa:E501 else: raise database.UnsupportedDatabaseType(f'db_type {db_type} not found') sorted_snapshots = {snapshot: date for snapshot, date in sorted( matching_snapshots.items(), key=lambda snap: snap[1])} return sorted_snapshots def manage_snapshot_lifecycle( db_type, rds_client, snapshots): """ Manage snapshot for a given database according to retention. Args: db_type (str): DB type rds_client (obj): boto3 RDS client snapshots (dict): filtered snapshots Returns: list: deleted snapshots """ while len(snapshots) > config.NUMBER_OF_SNAPSHOTS_TO_RETAIN: # Get next oldest snapshot snaphot_to_delete = next(iter(snapshots)) database.delete_snapshot( snaphot_to_delete, db_type, client=rds_client) print(f'Deleted snapshot {snaphot_to_delete}') del snapshots[snaphot_to_delete] def main(): """Task entry point.""" sfn_client = boto3.client( 'stepfunctions', region_name=config.AWS_DEFAULT_REGION) try: rds_client = boto3.client( 'rds', region_name=config.AWS_DEFAULT_REGION, ) backup_snapshot = create_backup_snapshot( config.DB_TYPE, rds_client, config.SOURCE_ACCOUNT_ID, config.SOURCE_SNAPSHOT_NAME, ) logger.info(f'{backup_snapshot} is now available') snapshots = database.find_matching_snapshots( config.DB_NAME, config.DB_TYPE) automated_snapshots = filter_automatic_snapshots_by_date( config.DB_TYPE, snapshots) manage_snapshot_lifecycle( config.DB_TYPE, rds_client, automated_snapshots) sfn_client.send_task_success( taskToken=config.TASK_TOKEN, output=json.dumps(backup_snapshot) ) except Exception as error: logger.exception(str(error)) sfn_client.send_task_failure( taskToken=config.TASK_TOKEN, error='500', cause=str(error), ) raise error if __name__ == '__main__': main()