"""Lambda sr-delivery-meta function module.""" import json from datetime import datetime from datetime import timezone import random import sentry_sdk from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration from .common import logger from .common.connectors import s3_sound_recordings from .common.connectors import s3_assets from .common.connectors import s3_delivery_audit from .common.connectors import sftp from .ddex.generate import generate_ern_srr from src.utils.typedload import get_loader from soundrecording_utils.metadata.types import Asset from soundrecording_utils.metadata.types import OrchardSoundRecording import config # initialize sentry sentry_dsn = config.secrets_manager_client.get_cred('SENTRY_DSN') if sentry_dsn: logger.info('Initializing with sentry') sentry_sdk.init( sentry_dsn, integrations=[AwsLambdaIntegration()] ) else: logger.info('Initializing without sentry') META_SFTP_PKEY = config.secrets_manager_client.get_cred('META_SFTP_PKEY') # noqa:E501 class NoAssets(Exception): """No deliverable assets on an OrchardSoundRecording.""" pass def get_blob(sound_recording_id, version_id) -> OrchardSoundRecording: """Get the metadata blob from the event message.""" # retrieve sound recording version from s3 (raw_data, _) = s3_sound_recordings.get_sound_recording_version( sound_recording_id, version_id ) loader = get_loader() osr = loader.load(json.loads(raw_data), OrchardSoundRecording) return osr def handler(event, context): """Lambda entry point.""" try: upload = event['upload_asset'] assert isinstance(upload, bool) sound_recording_id = event['sound_recording']['id'] version_id = event['sound_recording']['version'] blob = get_blob( sound_recording_id, version_id ) batch_id = _generate_batch_id() execution_meta = { 'timestamp': datetime.now(timezone.utc), 'message_thread_id': 1, 'message_id': batch_id, } # select asset by metadata if upload required upload_asset = _select_upload_asset(blob) if upload else None # generate XML based on metadata and asset ern = generate_ern_srr(blob, upload_asset, execution_meta) # fetch actual audio asset asset_bytes = s3_assets.download_asset( upload_asset.filename, upload_asset.extension ) if upload_asset else None # list files to transfer sr_isrc = blob.isrc (batch_dir, isrc_dir, resources_dir) = _sftp_remote_dirnames(batch_id, sr_isrc) # noqa:E501 transfer_files = [ ( asset_bytes, f'{resources_dir}/{upload_asset.filename}.{upload_asset.extension}' # noqa:E501 ) ] if upload_asset else [] xml_files = [ ( bytes(ern.encode('utf-8')), f'{isrc_dir}/{sr_isrc}.xml' ), ( b'', f'{batch_dir}/BatchComplete_{batch_id}.xml' ) ] # upload XML file to S3 for future auditing s3_delivery_audit.write_sr_delivery_audit_xml( xml_files[0][0], xml_files[0][1], event['execution_name'] ) transfer_files += xml_files # establish sftp connection sftp_conn = sftp.Connection( **_sftp_conn_info() ) # upload files sftp_conn.upload_files(transfer_files) sftp_conn.close() return { 'details': { 'batch_id': batch_id, 'filenames': [x[1] for x in transfer_files] } } # suppress entry, fail lambda function except NoAssets as e: sentry_sdk.init() raise e # log and raise, fail lambda function except Exception as e: logger.exception(str(e)) raise e def _sftp_conn_info(): return { 'hostname': config.META_SFTP_HOST, 'port': config.META_SFTP_PORT, 'username': config.META_SFTP_USERNAME, 'pkey': META_SFTP_PKEY.replace('\\n', '\n') } def _sftp_remote_dirnames(batch_id, isrc): base_dirname = config.META_SFTP_BASE_DIRNAME.rstrip('/') resources_dirname = config.RESOURCES_DIRNAME.rstrip('/') batch_dir = base_dirname + '/' + batch_id if base_dirname else batch_id return ( batch_dir, batch_dir + '/' + isrc, batch_dir + '/' + isrc + '/' + resources_dirname, ) def _generate_batch_id() -> str: date_str = datetime.now().strftime('%Y%m%d%H%M%S') random_str = str(random.randint(0, 9999)).rjust(4, '0') return date_str + random_str def _select_upload_asset( sound_recording_metadata: OrchardSoundRecording ) -> Asset: """Select file to upload..""" for extension in config.ALLOWED_ASSET_TYPES: assets = [ x for x in sound_recording_metadata.assets if x.extension == extension ] if assets: return assets[0] raise NoAssets()