String GITHUB_REPOSITORY = 'lambda-abacus'
String ECR_ACCOUNT_ID = '086679231553'
List<String> AWS_REGIONS = ['us-east-1']
String SLACK_NOTIFICATIONS_CHANNEL = '#abacus-devs'
String INTEGRATION_TESTS_ROLE = 'qa-lambda-abacus-integration-test-role'
// TODO: Migrate payoneer_payments_payout Dockerfile to docker-parent-images:lambda-python313
// with multi-stage build (like file_upload_complete) to properly fix these base image CVEs.
List<String> VULNERABILITIES_TO_IGNORE = [
    'CVE-2026-23949', // pkg:pypi/jaraco.context@5.3.0 -> 6.1.0
    // Base image OS-level CVEs (amazon/aws-lambda-python — kernel-headers, libxml2, openssl, glib2, etc.)
    'CVE-2021-43396',
    'CVE-2022-46908',
    'CVE-2022-49043',
    'CVE-2023-45896',
    'CVE-2023-52425',
    'CVE-2023-52751',
    'CVE-2023-52926',
    'CVE-2023-52927',
    'CVE-2023-53292',
    'CVE-2024-26739',
    'CVE-2024-26982',
    'CVE-2024-27407',
    'CVE-2024-33599',
    'CVE-2024-33601',
    'CVE-2024-33602',
    'CVE-2024-34459',
    'CVE-2024-35866',
    'CVE-2024-35963',
    'CVE-2024-36899',
    'CVE-2024-36913',
    'CVE-2024-38541',
    'CVE-2024-41013',
    'CVE-2024-41014',
    'CVE-2024-45490',
    'CVE-2024-47682',
    'CVE-2024-47685',
    'CVE-2024-47696',
    'CVE-2024-47701',
    'CVE-2024-47742',
    'CVE-2024-47745',
    'CVE-2024-49855',
    'CVE-2024-49860',
    'CVE-2024-49861',
    'CVE-2024-49882',
    'CVE-2024-49883',
    'CVE-2024-49884',
    'CVE-2024-49889',
    'CVE-2024-49936',
    'CVE-2024-49960',
    'CVE-2024-49983',
    'CVE-2024-49996',
    'CVE-2024-50036',
    'CVE-2024-50047',
    'CVE-2024-50055',
    'CVE-2024-50063',
    'CVE-2024-50067',
    'CVE-2024-50083',
    'CVE-2024-50088',
    'CVE-2024-50115',
    'CVE-2024-50121',
    'CVE-2024-50127',
    'CVE-2024-50128',
    'CVE-2024-50131',
    'CVE-2024-50143',
    'CVE-2024-50150',
    'CVE-2024-50151',
    'CVE-2024-50154',
    'CVE-2024-50155',
    'CVE-2024-50164',
    'CVE-2024-50186',
    'CVE-2024-50242',
    'CVE-2024-50246',
    'CVE-2024-50247',
    'CVE-2024-50257',
    'CVE-2024-50262',
    'CVE-2024-50264',
    'CVE-2024-50275',
    'CVE-2024-50278',
    'CVE-2024-50279',
    'CVE-2024-50280',
    'CVE-2024-50301',
    'CVE-2024-52332',
    'CVE-2024-53057',
    'CVE-2024-53082',
    'CVE-2024-53095',
    'CVE-2024-53096',
    'CVE-2024-53099',
    'CVE-2024-53103',
    'CVE-2024-53106',
    'CVE-2024-53141',
    'CVE-2024-53142',
    'CVE-2024-53166',
    'CVE-2024-53170',
    'CVE-2024-53173',
    'CVE-2024-53174',
    'CVE-2024-53179',
    'CVE-2024-53194',
    'CVE-2024-53206',
    'CVE-2024-53214',
    'CVE-2024-56171',
    'CVE-2024-56558',
    'CVE-2024-56570',
    'CVE-2024-56581',
    'CVE-2024-56582',
    'CVE-2024-56600',
    'CVE-2024-56601',
    'CVE-2024-56603',
    'CVE-2024-56606',
    'CVE-2024-56614',
    'CVE-2024-56615',
    'CVE-2024-56616',
    'CVE-2024-56631',
    'CVE-2024-56633',
    'CVE-2024-56642',
    'CVE-2024-56658',
    'CVE-2024-56664',
    'CVE-2024-56672',
    'CVE-2024-56675',
    'CVE-2024-56693',
    'CVE-2024-56759',
    'CVE-2024-57798',
    'CVE-2024-57876',
    'CVE-2024-57896',
    'CVE-2024-57900',
    'CVE-2024-57917',
    'CVE-2024-57929',
    'CVE-2024-57951',
    'CVE-2024-57979',
    'CVE-2024-58083',
    'CVE-2024-58093',
    'CVE-2024-58240',
    'CVE-2025-0840',
    'CVE-2025-1352',
    'CVE-2025-1372',
    'CVE-2025-1390',
    'CVE-2025-13151',
    'CVE-2025-13601',
    'CVE-2025-14087',
    'CVE-2025-15467',
    'CVE-2025-15468',
    'CVE-2025-21631',
    'CVE-2025-21647',
    'CVE-2025-21671',
    'CVE-2025-21692',
    'CVE-2025-21700',
    'CVE-2025-21702',
    'CVE-2025-21703',
    'CVE-2025-21704',
    'CVE-2025-21719',
    'CVE-2025-21724',
    'CVE-2025-21726',
    'CVE-2025-21727',
    'CVE-2025-21731',
    'CVE-2025-21753',
    'CVE-2025-21756',
    'CVE-2025-21759',
    'CVE-2025-21760',
    'CVE-2025-21761',
    'CVE-2025-21762',
    'CVE-2025-21763',
    'CVE-2025-21764',
    'CVE-2025-21785',
    'CVE-2025-21791',
    'CVE-2025-21796',
    'CVE-2025-21858',
    'CVE-2025-21867',
    'CVE-2025-21887',
    'CVE-2025-21919',
    'CVE-2025-21920',
    'CVE-2025-21928',
    'CVE-2025-21999',
    'CVE-2025-22035',
    'CVE-2025-22055',
    'CVE-2025-22056',
    'CVE-2025-22058',
    'CVE-2025-22126',
    'CVE-2025-22871',
    'CVE-2025-22874',
    'CVE-2025-23142',
    'CVE-2025-23150',
    'CVE-2025-24528',
    'CVE-2025-24928',
    'CVE-2025-27113',
    'CVE-2025-32414',
    'CVE-2025-32415',
    'CVE-2025-37738',
    'CVE-2025-37750',
    'CVE-2025-37752',
    'CVE-2025-37780',
    'CVE-2025-37785',
    'CVE-2025-37789',
    'CVE-2025-37797',
    'CVE-2025-37798',
    'CVE-2025-37807',
    'CVE-2025-37819',
    'CVE-2025-37823',
    'CVE-2025-37839',
    'CVE-2025-37849',
    'CVE-2025-37885',
    'CVE-2025-37890',
    'CVE-2025-37913',
    'CVE-2025-37915',
    'CVE-2025-37920',
    'CVE-2025-37921',
    'CVE-2025-37923',
    'CVE-2025-37927',
    'CVE-2025-37928',
    'CVE-2025-37932',
    'CVE-2025-37936',
    'CVE-2025-37938',
    'CVE-2025-37940',
    'CVE-2025-37945',
    'CVE-2025-37954',
    'CVE-2025-37964',
    'CVE-2025-37995',
    'CVE-2025-37997',
    'CVE-2025-38000',
    'CVE-2025-38001',
    'CVE-2025-38003',
    'CVE-2025-38004',
    'CVE-2025-38018',
    'CVE-2025-38020',
    'CVE-2025-38022',
    'CVE-2025-38023',
    'CVE-2025-38037',
    'CVE-2025-38040',
    'CVE-2025-38048',
    'CVE-2025-38051',
    'CVE-2025-38058',
    'CVE-2025-38062',
    'CVE-2025-38064',
    'CVE-2025-38066',
    'CVE-2025-38068',
    'CVE-2025-38071',
    'CVE-2025-38072',
    'CVE-2025-38075',
    'CVE-2025-38079',
    'CVE-2025-38083',
    'CVE-2025-38084',
    'CVE-2025-38095',
    'CVE-2025-38102',
    'CVE-2025-38103',
    'CVE-2025-38108',
    'CVE-2025-38111',
    'CVE-2025-38112',
    'CVE-2025-38113',
    'CVE-2025-38115',
    'CVE-2025-38124',
    'CVE-2025-38146',
    'CVE-2025-38147',
    'CVE-2025-38154',
    'CVE-2025-38161',
    'CVE-2025-38162',
    'CVE-2025-38177',
    'CVE-2025-38181',
    'CVE-2025-38193',
    'CVE-2025-38198',
    'CVE-2025-38201',
    'CVE-2025-38206'
] + [
    'CVE-2025-38211',
    'CVE-2025-38212',
    'CVE-2025-38215',
    'CVE-2025-38231',
    'CVE-2025-38236',
    'CVE-2025-38244',
    'CVE-2025-38248',
    'CVE-2025-38280',
    'CVE-2025-38298',
    'CVE-2025-38305',
    'CVE-2025-38310',
    'CVE-2025-38312',
    'CVE-2025-38324',
    'CVE-2025-38342',
    'CVE-2025-38346',
    'CVE-2025-38350',
    'CVE-2025-38352',
    'CVE-2025-38375',
    'CVE-2025-38386',
    'CVE-2025-38389',
    'CVE-2025-38396',
    'CVE-2025-38399',
    'CVE-2025-38403',
    'CVE-2025-38415',
    'CVE-2025-38424',
    'CVE-2025-38443',
    'CVE-2025-38445',
    'CVE-2025-38457',
    'CVE-2025-38461',
    'CVE-2025-38462',
    'CVE-2025-38466',
    'CVE-2025-38471',
    'CVE-2025-38472',
    'CVE-2025-38476',
    'CVE-2025-38477',
    'CVE-2025-38488',
    'CVE-2025-38491',
    'CVE-2025-38494',
    'CVE-2025-38495',
    'CVE-2025-38498',
    'CVE-2025-38499',
    'CVE-2025-38500',
    'CVE-2025-38502',
    'CVE-2025-38527',
    'CVE-2025-38550',
    'CVE-2025-38552',
    'CVE-2025-38556',
    'CVE-2025-38563',
    'CVE-2025-38565',
    'CVE-2025-38572',
    'CVE-2025-38587',
    'CVE-2025-38588',
    'CVE-2025-38590',
    'CVE-2025-38617',
    'CVE-2025-38618',
    'CVE-2025-38622',
    'CVE-2025-38645',
    'CVE-2025-38653',
    'CVE-2025-38665',
    'CVE-2025-38670',
    'CVE-2025-38676',
    'CVE-2025-38683',
    'CVE-2025-38684',
    'CVE-2025-38685',
    'CVE-2025-38700',
    'CVE-2025-38701',
    'CVE-2025-38702',
    'CVE-2025-38707',
    'CVE-2025-38708',
    'CVE-2025-38724',
    'CVE-2025-38728',
    'CVE-2025-38732',
    'CVE-2025-39673',
    'CVE-2025-39677',
    'CVE-2025-39682',
    'CVE-2025-39683',
    'CVE-2025-39689',
    'CVE-2025-39691',
    'CVE-2025-39697',
    'CVE-2025-39702',
    'CVE-2025-39730',
    'CVE-2025-39738',
    'CVE-2025-39749',
    'CVE-2025-39759',
    'CVE-2025-39760',
    'CVE-2025-39766',
    'CVE-2025-39770',
    'CVE-2025-39773',
    'CVE-2025-39783',
    'CVE-2025-39806',
    'CVE-2025-39817',
    'CVE-2025-39823',
    'CVE-2025-39824',
    'CVE-2025-39825',
    'CVE-2025-39835',
    'CVE-2025-39838',
    'CVE-2025-39839',
    'CVE-2025-39843',
    'CVE-2025-39849',
    'CVE-2025-39866',
    'CVE-2025-39877',
    'CVE-2025-39880',
    'CVE-2025-39881',
    'CVE-2025-39883',
    'CVE-2025-39894',
    'CVE-2025-39902',
    'CVE-2025-39913',
    'CVE-2025-39914',
    'CVE-2025-39916',
    'CVE-2025-39931',
    'CVE-2025-39946',
    'CVE-2025-39955',
    'CVE-2025-39977',
    'CVE-2025-39980',
    'CVE-2025-39998',
    'CVE-2025-40040',
    'CVE-2025-40044',
    'CVE-2025-40070',
    'CVE-2025-40080',
    'CVE-2025-40081',
    'CVE-2025-40104',
    'CVE-2025-40153',
    'CVE-2025-40167',
    'CVE-2025-40173',
    'CVE-2025-40176',
    'CVE-2025-40179',
    'CVE-2025-40186',
    'CVE-2025-40187',
    'CVE-2025-40190',
    'CVE-2025-40194',
    'CVE-2025-40197',
    'CVE-2025-40201',
    'CVE-2025-40204',
    'CVE-2025-40211',
    'CVE-2025-40214',
    'CVE-2025-40220',
    'CVE-2025-40240',
    'CVE-2025-40248',
    'CVE-2025-40254',
    'CVE-2025-40258',
    'CVE-2025-40259',
    'CVE-2025-40264',
    'CVE-2025-40271',
    'CVE-2025-40272',
    'CVE-2025-40273',
    'CVE-2025-40277',
    'CVE-2025-40279',
    'CVE-2025-40281',
    'CVE-2025-40292',
    'CVE-2025-40293',
    'CVE-2025-40297',
    'CVE-2025-40304',
    'CVE-2025-40322',
    'CVE-2025-40323',
    'CVE-2025-40324',
    'CVE-2025-40331',
    'CVE-2025-40363',
    'CVE-2025-40364',
    'CVE-2025-47907',
    'CVE-2025-47912',
    'CVE-2025-4802',
    'CVE-2025-49794',
    'CVE-2025-49795',
    'CVE-2025-49796',
    'CVE-2025-58187',
    'CVE-2025-58188',
    'CVE-2025-5914',
    'CVE-2025-59375',
    'CVE-2025-6020',
    'CVE-2025-6021',
    'CVE-2025-6052',
    'CVE-2025-61723',
    'CVE-2025-61725',
    'CVE-2025-61726',
    'CVE-2025-61729',
    'CVE-2025-61731',
    'CVE-2025-68119',
    'CVE-2025-68121',
    'CVE-2025-68185',
    'CVE-2025-68191',
    'CVE-2025-68229',
    'CVE-2025-68231',
    'CVE-2025-68241',
    'CVE-2025-68244',
    'CVE-2025-68283',
    'CVE-2025-68284',
    'CVE-2025-68285',
    'CVE-2025-68287',
    'CVE-2025-68331',
    'CVE-2025-68973',
    'CVE-2025-69419',
    'CVE-2025-69420',
    'CVE-2025-69421',
    'CVE-2025-6965',
    'CVE-2025-7425',
    'CVE-2025-7546',
    'CVE-2025-8941',
    'CVE-2025-9086',
    'CVE-2025-9230',
    'CVE-2026-22796',
    'CVE-2026-24882',
    'CVE-2025-39967'
] + [
    'CVE-2023-53421', // pkg:rpm/amazon/kernel-headers@6.1.112-124.190.amzn2023
    'CVE-2023-54285',
    'CVE-2025-38129',
    'CVE-2025-38718',
    'CVE-2025-40149',
    'CVE-2025-40215',
    'CVE-2025-40251',
    'CVE-2025-40328',
    'CVE-2025-68340',
    'CVE-2025-68349',
    'CVE-2025-68803',
    'CVE-2025-71085',
    'CVE-2025-71089',
    'CVE-2025-71091',
    'CVE-2025-71093',
    'CVE-2025-71097',
    'CVE-2025-71116',
    'CVE-2025-71123',
    'CVE-2025-71238',
    'CVE-2026-22980',
    'CVE-2026-22984',
    'CVE-2026-22990',
    'CVE-2026-22999',
    'CVE-2026-23001',
    'CVE-2026-23010',
    'CVE-2026-23025',
    'CVE-2026-23038',
    'CVE-2026-23047',
    'CVE-2026-23054',
    'CVE-2026-23060',
    'CVE-2026-23071',
    'CVE-2026-23074',
    'CVE-2026-23083',
    'CVE-2026-23095',
    'CVE-2026-23097',
    'CVE-2026-23099',
    'CVE-2026-23102',
    'CVE-2026-23103',
    'CVE-2026-23105',
    'CVE-2026-23107',
    'CVE-2026-23111',
    'CVE-2026-23124',
    'CVE-2026-23125',
    'CVE-2026-23126',
    'CVE-2026-23128',
    'CVE-2026-23140',
    'CVE-2026-23144',
    'CVE-2026-23156',
    'CVE-2026-23168',
    'CVE-2026-23169',
    'CVE-2026-23193',
    'CVE-2026-23198',
    'CVE-2026-23209',
    'CVE-2026-23216',
    'CVE-2026-23221',
    'CVE-2026-23222',
    'CVE-2026-23229',
    'CVE-2026-23230',
    'CVE-2026-23231',
    'CVE-2026-23243'
] + [
    'CVE-2026-25210', // pkg:rpm/amazon/expat
    'CVE-2026-25679', // pkg:generic/aws-lambda-rie
    'CVE-2026-2673', // pkg:rpm/amazon/openssl-snapsafe-libs
    'CVE-2026-32280', // pkg:generic/aws-lambda-rie
    'CVE-2025-45768' // pkg:pypi/pyjwt — no fix available
] + [
    // Base image CVEs surfaced by build #742 (2026-06-01) - amzn2023 base packages + aws-lambda-rie
    'CVE-2026-23455',  // pkg:rpm/amazon/kernel-headers@6.1.112-124.190.amzn2023
    'CVE-2026-31402',
    'CVE-2026-31414',
    'CVE-2026-31448',
    'CVE-2026-31533',
    'CVE-2026-31668',
    'CVE-2026-31669',
    'CVE-2026-31682',
    'CVE-2026-43037',
    'CVE-2026-43038',
    'CVE-2026-43186',
    'CVE-2026-43304',
    'CVE-2026-43341',
    'CVE-2026-43383',
    'CVE-2026-43406',
    'CVE-2026-43407',
    'CVE-2025-38704',
    'CVE-2025-40135',
    'CVE-2025-71269',
    'CVE-2026-22991',
    'CVE-2026-22992',
    'CVE-2026-23003',
    'CVE-2026-23113',
    'CVE-2026-23154',
    'CVE-2026-23157',
    'CVE-2026-23204',
    'CVE-2026-23270',
    'CVE-2026-23271',
    'CVE-2026-23273',
    'CVE-2026-23274',
    'CVE-2026-23293',
    'CVE-2026-23300',
    'CVE-2026-23304',
    'CVE-2026-23317',
    'CVE-2026-23319',
    'CVE-2026-23340',
    'CVE-2026-23343',
    'CVE-2026-23351',
    'CVE-2026-23359',
    'CVE-2026-23362',
    'CVE-2026-23381',
    'CVE-2026-23388',
    'CVE-2026-23391',
    'CVE-2026-23392',
    'CVE-2026-23397',
    'CVE-2026-23414',
    'CVE-2026-23449',
    'CVE-2026-23452',
    'CVE-2026-23456',
    'CVE-2026-23457',
    'CVE-2026-23458',
    'CVE-2026-31392',
    'CVE-2026-31399',
    'CVE-2026-31403',
    'CVE-2026-31418',
    'CVE-2026-31424',
    'CVE-2026-31426',
    'CVE-2026-31427',
    'CVE-2026-31431',
    'CVE-2026-31446',
    'CVE-2026-31447',
    'CVE-2026-31449',
    'CVE-2026-31450',
    'CVE-2026-31452',
    'CVE-2026-31453',
    'CVE-2026-31454',
    'CVE-2026-31455',
    'CVE-2026-31467',
    'CVE-2026-31469',
    'CVE-2026-31473',
    'CVE-2026-31480',
    'CVE-2026-31492',
    'CVE-2026-31504',
    'CVE-2026-31508',
    'CVE-2026-31515',
    'CVE-2026-31519',
    'CVE-2026-31521',
    'CVE-2026-31523',
    'CVE-2026-31555',
    'CVE-2026-31570',
    'CVE-2026-31628',
    'CVE-2026-31656',
    'CVE-2026-31662',
    'CVE-2026-31665',
    'CVE-2026-31667',
    'CVE-2026-31674',
    'CVE-2026-31678',
    'CVE-2026-31679',
    'CVE-2026-31680',
    'CVE-2026-31695',
    'CVE-2026-31738',
    'CVE-2026-31759',
    'CVE-2026-31781',
    'CVE-2026-31786',
    'CVE-2026-31787',
    'CVE-2026-31788',
    'CVE-2026-43025',
    'CVE-2026-43027',
    'CVE-2026-43028',
    'CVE-2026-43030',
    'CVE-2026-43033',
    'CVE-2026-43040',
    'CVE-2026-43043',
    'CVE-2026-43047',
    'CVE-2026-43054',
    'CVE-2026-43057',
    'CVE-2026-43060',
    'CVE-2026-43077',
    'CVE-2026-43078',
    'CVE-2026-43124',
    'CVE-2026-43128',
    'CVE-2026-43133',
    'CVE-2026-43139',
    'CVE-2026-43143',
    'CVE-2026-43150',
    'CVE-2026-43158',
    'CVE-2026-43163',
    'CVE-2026-43171',
    'CVE-2026-43187',
    'CVE-2026-43190',
    'CVE-2026-43194',
    'CVE-2026-43211',
    'CVE-2026-43214',
    'CVE-2026-43226',
    'CVE-2026-43230',
    'CVE-2026-43233',
    'CVE-2026-43238',
    'CVE-2026-43261',
    'CVE-2026-43266',
    'CVE-2026-43273',
    'CVE-2026-43278',
    'CVE-2026-43281',
    'CVE-2026-43284',
    'CVE-2026-43328',
    'CVE-2026-43329',
    'CVE-2026-43336',
    'CVE-2026-43339',
    'CVE-2026-43361',
    'CVE-2026-43365',
    'CVE-2026-43366',
    'CVE-2026-43405',
    'CVE-2026-43427',
    'CVE-2026-43441',
    'CVE-2026-43449',
    'CVE-2026-43450',
    'CVE-2026-43452',
    'CVE-2026-43453',
    'CVE-2026-43466',
    'CVE-2026-46333',
    'CVE-2026-27135',  // pkg:rpm/amazon/libnghttp2
    'CVE-2026-28387',  // pkg:rpm/amazon/openssl-snapsafe-libs
    'CVE-2026-28388',
    'CVE-2026-28389',
    'CVE-2026-28390',
    'CVE-2026-31790',
    'CVE-2026-32281',  // pkg:generic/aws-lambda-rie
    'CVE-2026-32283',
    'CVE-2026-33811',
    'CVE-2026-33814',
    'CVE-2026-39820',
    'CVE-2026-39836',
    'CVE-2026-42499',
    'CVE-2026-3805',  // pkg:rpm/amazon/libcurl-minimal
    'CVE-2026-4046'  // pkg:rpm/amazon/glibc-gconv-extra
]

LAMBDA_ALIAS_MAP = [
    'adjustment_file_complete': '"\$LATEST"',
    'adjustment_file_import': 'provisioned',
    'adjustment_file_initialize': '"\$LATEST"',

    'adjustment_file_process_batch': '"\$LATEST"',
    'adjustment_file_validation': 'provisioned',
    'adjustments_json_import': 'provisioned',
    'adjustments_json_validation': 'provisioned',
    'adjustments_apply': 'provisioned',
    'commit_ledger_account_contract': 'provisioned',
    'commit_mechanicals': 'provisioned',
    'commit_royalties': 'provisioned',
    'contract_lifecycle_automation': 'provisioned',
    'correction_apply': 'provisioned',
    'correction_stage': 'provisioned',
    'file_upload_complete': '"\$LATEST"',
    'file_upload_initialize': '"\$LATEST"',
    'generate_flowthrough_adjustments': 'provisioned',
    'json_contract_file_import': 'provisioned',
    'ledger_accounting_run_balance': 'provisioned',
    'payment_allocation': '"\$LATEST"',
    'payments_generate': 'provisioned',
    'payoneer_payments_payout': 'provisioned',
    'outbox_process': '"\$LATEST"',
    'reserves_release': 'provisioned',
    'reserves_schedule': 'provisioned',
    'reserves_take': 'provisioned',
    'royalty_collection_territory_backfill': 'provisioned',
    'schedule_auto_add': 'provisioned',
    'snapshot_contracts': 'provisioned',
    'sync_account': 'provisioned',
    'sync_contract': 'provisioned',
    'sync_contract_sap': 'provisioned',
    'earnings_transfer': '"\$LATEST"'
]

def deploymentTargets = [
    'prod': [
        'accountId': '437795906767',
        'deploymentRole': 'prod-jenkins-aws-pipeline-agent'
    ],
    'qa_accounting': [
        'accountId': '989790945997',
        'deploymentRole': 'qa-jenkins-pipeline-deploy-role'
    ],
    'uat_accounting': [
        'accountId': '989790945997',
        'deploymentRole': 'uat-jenkins-pipeline-deploy-role'
    ]
]

def functionDeploymentMap = [
    'adjustment_file_complete': [
        'qa': ['qa_accounting'],
        'prod': [],  // TODO: Add to Prod
        'uat': [] // TODO: Add to UAT
    ],
    'adjustment_file_import': [
        'qa': ['qa_accounting'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'adjustment_file_initialize': [
        'qa': ['qa_accounting'],
        'prod': ['prod'],
        'uat': [] // TODO: Add to UAT
    ],

    'adjustment_file_process_batch': [
        'qa': ['qa_accounting'],
        'prod': [],  // TODO: Add to Prod
        'uat': [] // TODO: Add to UAT
    ],
    'adjustment_file_validation': [
        'qa': ['qa_accounting'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'adjustments_apply': [
        'qa': ['qa_accounting'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'commit_ledger_account_contract': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'commit_mechanicals': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'commit_royalties': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'contract_lifecycle_automation': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'correction_apply': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'correction_stage': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'json_contract_file_import': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'earnings_transfer': [
        'qa': ['qa_accounting'],
        'prod': [],
        'uat': ['uat_accounting']
    ],
    'file_upload_complete': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': [] // TODO: Add to UAT
    ],
    'file_upload_initialize': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': [] // TODO: Add to UAT
    ],
    'ledger_accounting_run_balance': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'outbox_process': [
        'qa': ['qa_accounting'],
        'prod': ['prod'],
        'uat': [] // TODO: Add to UAT
    ],
    'payment_allocation': [
        'qa': ['qa_accounting'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'payments_generate': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'payoneer_payments_payout': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'reserves_release': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'reserves_schedule': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'reserves_take': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'schedule_auto_add': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'sync_account': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'sync_contract': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'sync_contract_sap': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'snapshot_contracts': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'royalty_collection_territory_backfill': [
        'qa': ['prod'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'generate_flowthrough_adjustments': [
        'qa': ['qa_accounting'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'adjustments_json_import': [
        'qa': ['qa_accounting'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
    'adjustments_json_validation': [
        'qa': ['qa_accounting'],
        'prod': ['prod'],
        'uat': ['uat_accounting']
    ],
]

@groovy.transform.Field
def monorepoUtilsInstance = null

@groovy.transform.Field
Map<String, Map> functionsIntegrationTestsConfig = [
    'adjustment_file_validation': [
        snowflakeTests: true,
        snowflakeDatabase: 'ORCHARD_APP_REPORTING_V2',
    ],
    'adjustments_json_validation': [
        snowflakeTests: true,
        snowflakeSchema: 'QA',
    ],
    'commit_royalties': [
        snowflakeTests: true,
    ],
    'commit_mechanicals': [
        snowflakeTests: true,
    ],
    'correction_stage': [
        snowflakeTests: true,
    ],
    'generate_flowthrough_adjustments': [
        snowflakeTests: true,
        snowflakeSchema: 'QA',
        snowflakeWarehouse: 'QA_ABACUS_WH', // Using this warehouse temporarily
    ],
    'ledger_accounting_run_balance': [
        snowflakeTests: true,
    ],
    'snapshot_contracts': [
        snowflakeTests: true,
    ],
    'reserves_take': [
        snowflakeTests: true,
    ]
]

pipeline {
    agent none

    options {
        ansiColor('xterm')
        disableConcurrentBuilds()
        timestamps()
    }

    parameters {
        booleanParam(name: 'DEPLOY_TO_PROD', defaultValue: true, description: 'Whether or not to deploy to prod.')
        booleanParam(name: 'DEPLOY_UAT', defaultValue: true, description: 'Whether or not to deploy to UAT.')
        booleanParam(name: 'SKIP_TESTS', defaultValue: false, description: 'Break glass: skip unit, integration, and SAST stages. Flip to true only for emergency deploys.')
        string(name: 'LAMBDA_FUNCTION_NAMES', defaultValue: '', description: 'Comma-separated list of names of Lambda functions to build and deploy')
        string(name: 'SHARED_LIBRARIES_VERSION', defaultValue: 'master', description: 'The version of the Jenkins shared libraries to use. Can be a branch, tag, Git revision or PR ref (e.g. pull/PR_NUMBER/merge).')
    }

    triggers {
        issueCommentTrigger('.*retest this please.*')
    }

    stages {
        stage('Load Shared Libraries') {
            steps {
                library "jenkins-global-libraries@${params.SHARED_LIBRARIES_VERSION}"
            }
        }

        stage('Compliance Checks') {
            steps {
                withModifiedFunctions(checkout: true) { functionName ->
                    echo "Running Compliance Checks for ${functionName}"
                    dir("lambda/${functionName}") {
                        complianceChecks()
                    }
                }
            }
        }

        stage('Validate Software Catalog Definition') {
            steps {
                withModifiedFunctions(checkout: true) { functionName ->
                    echo "Validating Software Catalog Definition for ${functionName}"
                    datadogSoftwareCatalogValidate(servicePath: "lambda/${functionName}")
                }
            }
        }

        stage('Unit Tests and Style Checks') {
            when { expression { return !params.SKIP_TESTS } }
            steps {
                withModifiedFunctions(checkout: true) { functionName ->
                    echo "Running Unit Tests and Style Checks for ${functionName}"
                    withEcr {
                        dir("lambda/${functionName}") {
                            sh 'docker compose run --rm --build lint-and-test'
                        }
                    }
                }
            }
        }

        stage('Static Application Security Tests') {
            when { expression { return !params.SKIP_TESTS } }
            steps {
                withModifiedFunctions(checkout: true) { functionName ->
                    echo "Running Static App Security Tests for ${functionName}"
                    sastTests(projectDir: "lambda/${functionName}")
                }
            }
        }

        stage('Sonar Scan and Analysis') {
            agent any
            when {
                branch 'master'
                expression { return !params.SKIP_TESTS }
            }
            steps {
                sonarScan project: GITHUB_REPOSITORY, language: 'py', exclusions: 'lambda/**/tests/**'
            }
        }

        stage('Create a Release') {
            when {
                anyOf {
                    branch 'master'
                    expression { env.GITHUB_COMMENT =~ 'build docker' }
                    expression { pullRequest.labels.contains('build docker') }
                }
            }
            steps {
                withModifiedFunctions(checkout: true) { functionName ->
                    echo "Building for ${functionName}"
                    dockerToEcr awsRegions: AWS_REGIONS,
                        ecrAccountId: ECR_ACCOUNT_ID,
                        imageName: "${GITHUB_REPOSITORY}-${functionName.replaceAll('_', '-')}",
                        imageTag: env.GIT_COMMIT,
                        dockerBuildContext: "lambda/${functionName}",
                        dockerBuildFile: "lambda/${functionName}/Dockerfile"
                }
            }
        }

        stage('Deploy to QA') {
            when {
                branch 'master'
            }
            steps {
                withModifiedFunctions { functionName ->
                    echo "Deploying to QA for ${functionName}"
                    dir("lambda/${functionName}") {
                        script {
                            functionDeploymentMap[functionName]['qa'].each { account ->
                                lambdaDeploy environment: 'qa',
                                    awsRegions: AWS_REGIONS,
                                    imageTag: env.GIT_COMMIT,
                                    imageName: "${GITHUB_REPOSITORY}-${functionName.replaceAll('_', '-')}",
                                    ecrRegistryAccountId: ECR_ACCOUNT_ID,
                                    awsDeploymentTargetAccountId: deploymentTargets[account].accountId,
                                    awsDeploymentRoleName: deploymentTargets[account].deploymentRole,
                                    aliasName: LAMBDA_ALIAS_MAP.get(functionName),
                                    publishVersion: LAMBDA_ALIAS_MAP.get(functionName) as Boolean
                            }
                        }
                    }
                }
            }
        }

        stage('Deploy to UAT') {
            when {
                allOf {
                    branch 'master'
                    expression { params.DEPLOY_UAT }
                }
            }
            steps {
                withModifiedFunctions { functionName ->
                    echo "Deploying to UAT for ${functionName}"
                    dir("lambda/${functionName}") {
                        script {
                            functionDeploymentMap[functionName]['uat'].each { account ->
                                lambdaDeploy environment: 'uat',
                                    awsRegions: AWS_REGIONS,
                                    imageTag: env.GIT_COMMIT,
                                    imageName: "${GITHUB_REPOSITORY}-${functionName.replaceAll('_', '-')}",
                                    ecrRegistryAccountId: ECR_ACCOUNT_ID,
                                    awsDeploymentTargetAccountId: deploymentTargets[account].accountId,
                                    awsDeploymentRoleName: deploymentTargets[account].deploymentRole,
                                    aliasName: LAMBDA_ALIAS_MAP.get(functionName),
                                    publishVersion: LAMBDA_ALIAS_MAP.get(functionName) as Boolean
                            }
                        }
                    }
                }
            }
        }

        stage('Scan Docker Images') {
            when {
                anyOf {
                    branch 'master'
                    expression { env.GITHUB_COMMENT =~ 'build docker' }
                    expression { pullRequest.labels.contains('build docker') }
                }
            }
            steps {
                withModifiedFunctions { functionName ->
                    echo "Scanning Docker Images for ${functionName}"
                    dir("lambda/${functionName}") {
                        dockerScan awsRegion: AWS_REGIONS[0],
                            ecrAccountId: ECR_ACCOUNT_ID,
                            imageName: "${GITHUB_REPOSITORY}-${functionName.replaceAll('_', '-')}",
                            imageTag: env.GIT_COMMIT,
                            vulnerabilitiesToIgnore: VULNERABILITIES_TO_IGNORE
                    }
                }
            }
        }

        stage('Integration Tests') {
            when {
                branch 'master'
                expression { return !params.SKIP_TESTS }
            }
            environment {
                ENVIRONMENT              = 'dev'
                PYTHONPATH               = '.'
                SNOWFLAKE_USER           = 'LAMBDA_ABACUS_INTEGRATION_TESTS'
                SNOWFLAKE_HOST           = 'delphi.us-east-1.snowflakecomputing.com'
                SNOWFLAKE_ROLE           = 'DEV_ENGINEERING'
                SNOWFLAKE_ACCOUNT        = 'delphi.us-east-1'
                SNOWFLAKE_BATCH_SIZE     = '10000'
                WORKSHEET_BATCH_SIZE     = '100'
                FILE_BATCH_SIZE          = '1000'
                AWS_DEFAULT_REGION       = 'us-east-1'
                S3_BUCKET_SALES          = 'qa-lambda-abacus-ci-cd'
                S3_BUCKET_TESTS          = 'qa-lambda-abacus-ci-cd'
                sap_auth_username        = 'WS_ORCHARD'
                MYSQL_USER               = 'automation_qa'
                MYSQL_BATCH_SIZE         = '2' // used by the adjustment_file_import lambda
                FIVETRAN_SCHEMA          = 'QA_ROYALTY_ACCOUNTING_ROYALTY_ACCOUNTING'
                GRAPHQL_GATEWAY_URL      = 'https://qa-graphql-gateway.theorchard.io/graphql'
                ORCHARD_PROFILE_ID       = '81590'
                ORCHARD_PROFILE_UUID     = '27fd51cb-ce1f-4e11-a414-5746aa9b190a'
                ORCHARD_IDENTITY_ID      = 'b1b2f276-7e6a-4fa6-abb8-a43a26d519c1'
                DOCKER_CLIENT_TIMEOUT    = '360'
                COMPOSE_HTTP_TIMEOUT     = '360'
                ABACUS_ADJUSTMENTS_STAGE = 'qa-abacus-adjustments-s3-stage' // used by adjustments_json_validation lambda
            }
            steps {
                withModifiedFunctions(checkout: true) { functionName ->
                    echo "Triggering Integration Tests for ${functionName}"
                    dir("lambda/${functionName}") {
                        withEcr {
                            script {
                                def INTEGRATION_TESTS_ROLE_ACCOUNT_NAME = functionDeploymentMap[functionName]['qa'][0] ?: 'prod'
                                def INTEGRATION_TESTS_ROLE_ACCOUNT_ID = deploymentTargets[INTEGRATION_TESTS_ROLE_ACCOUNT_NAME].accountId
                                def hasSnowflakeTests = functionsIntegrationTestsConfig[functionName]?.snowflakeTests ?: false
                                def snowflakeDatabase = functionsIntegrationTestsConfig[functionName]?.snowflakeDatabase ?: 'ROYALTY_ACCOUNTING'
                                def snowflakeSchema = functionsIntegrationTestsConfig[functionName]?.snowflakeSchema ?: 'TEST'
                                def snowflakeWarehouse = functionsIntegrationTestsConfig[functionName]?.snowflakeWarehouse ?: 'DEV_OWS_WAREHOUSE'

                                // these secrets exist in both the prod and accounting-qa accounts
                                def baseSecrets = [
                                    [id: "qa/lambda-abacus-integration-tests/MYSQL_PASS", environmentVariable: 'MYSQL_PASS'],
                                    [id: "qa/lambda-abacus-integration-tests/TEST_USER_AWS_ACCESS_KEY_ID", environmentVariable: 'TEST_USER_AWS_ACCESS_KEY_ID'],
                                    [id: "qa/lambda-abacus-integration-tests/TEST_USER_AWS_SECRET_ACCESS_KEY", environmentVariable: 'TEST_USER_AWS_SECRET_ACCESS_KEY'],
                                    [id: "qa/lambda-abacus-integration-tests/SAP_AUTH_PASSWORD", environmentVariable: 'sap_auth_password']
                                ]

                                def secretsList = baseSecrets

                                // not all functions have secrets with credentials for Snowflake integration tests, so only add these secrets for the functions that need them
                                if (hasSnowflakeTests) {
                                    def fnHyphen = functionName.replaceAll('_', '-')
                                    secretsList += [
                                        [id: "qa/lambda-abacus-${fnHyphen}/INTEGRATION_TEST_SNOWFLAKE_PRIVATE_KEY", environmentVariable: 'SNOWFLAKE_PRIVATE_KEY'],
                                        [id: "qa/lambda-abacus-${fnHyphen}/INTEGRATION_TEST_SNOWFLAKE_KEY_PASSPHRASE", environmentVariable: 'SNOWFLAKE_KEY_PASSPHRASE']
                                    ]
                                }

                                withSecrets(awsAccountId: INTEGRATION_TESTS_ROLE_ACCOUNT_ID,
                                            awsRole: INTEGRATION_TESTS_ROLE,
                                            secrets: secretsList) {
                                    if (hasSnowflakeTests) {
                                        writeFile(file: 'rsa_key.p8', text: env.SNOWFLAKE_PRIVATE_KEY, encoding: 'UTF-8')
                                    }
                                    withEnv([
                                        "SNOWFLAKE_DATABASE=${snowflakeDatabase}",
                                        "SNOWFLAKE_SCHEMA=${snowflakeSchema}",
                                        "SNOWFLAKE_WAREHOUSE=${snowflakeWarehouse}",
                                    ]) {
                                        sh '''
                                            touch rsa_key.p8
                                            export SNOWFLAKE_PRIVATE_KEY_PATH=$(realpath rsa_key.p8)
                                            export AWS_ACCESS_KEY_ID=${TEST_USER_AWS_ACCESS_KEY_ID}
                                            export AWS_SECRET_ACCESS_KEY=${TEST_USER_AWS_SECRET_ACCESS_KEY}
                                            unset AWS_SESSION_TOKEN
                                            if egrep '^test_integration' Makefile > /dev/null 2>&1;
                                            then
                                                make test_integration
                                            else
                                                echo "No integration tests defined, skipping."
                                            fi
                                        '''
                                    }
                                }
                            }
                        }
                    }
                }
            }
            post {
                always {
                    withModifiedFunctions(checkout: true) { functionName ->
                        dir("lambda/${functionName}") {
                            echo "Publishing Integration Test Report for ${functionName}"
                            publishHTML([
                                reportName: "Integration Tests Report for ${functionName}",
                                reportDir: '',
                                reportFiles: 'report.html',
                                keepAll: true,
                                alwaysLinkToLastBuild: false,
                                allowMissing: true
                            ])
                            echo "Cleaning up after Integration Tests"
                            sh 'rm -f rsa_key.p8'
                            sh 'docker compose down --volumes --remove-orphans'
                        }
                    }
                }
            }
        }

        // ACC-10332: Prod deploy limited to allowlisted functions while snowflake migration is tested in QA
        stage('Deploy to PROD') {
            when {
                allOf {
                    branch 'master'
                    expression { params.DEPLOY_TO_PROD }
                }
            }
            steps {
                withModifiedFunctions(checkout: true) { functionName ->
                    script {
                        echo "Deploying to PROD for ${functionName}"
                        dir("lambda/${functionName}") {
                            functionDeploymentMap[functionName]['prod'].each { account ->
                                lambdaDeploy environment: 'prod',
                                    awsRegions: AWS_REGIONS,
                                    imageTag: env.GIT_COMMIT,
                                    imageName: "${GITHUB_REPOSITORY}-${functionName.replaceAll('_', '-')}",
                                    ecrRegistryAccountId: ECR_ACCOUNT_ID,
                                    awsDeploymentTargetAccountId: deploymentTargets[account].accountId,
                                    awsDeploymentRoleName: deploymentTargets[account].deploymentRole,
                                    aliasName: LAMBDA_ALIAS_MAP.get(functionName),
                                    publishVersion: LAMBDA_ALIAS_MAP.get(functionName) as Boolean
                            }
                        }
                        datadogSoftwareCatalogPublish(servicePath: "lambda/${functionName}")
                    }
                }
            }
        }
    }

    post {
        regression {
            script {
                if (env.BRANCH_NAME == 'master') {
                    slackNotify channel: SLACK_NOTIFICATIONS_CHANNEL
                }
            }
        }
        fixed {
            script {
                if (env.BRANCH_NAME == 'master') {
                    slackNotify channel: SLACK_NOTIFICATIONS_CHANNEL
                }
            }
        }
    }
}

def withModifiedFunctions(Map args = [:], Closure steps) {
    getMonorepoUtils().withModifiedProjects(args, steps)
}

def getMonorepoUtils() {
    if (monorepoUtilsInstance == null) {
        monorepoUtilsInstance = library("jenkins-global-libraries@${params.SHARED_LIBRARIES_VERSION}").com.sonymusic.MonorepoUtils.getInstance(
            steps: this,
            projectBasePath: 'lambda',
            projectsToBuild: params.LAMBDA_FUNCTION_NAMES ? params.LAMBDA_FUNCTION_NAMES.split(',') : null
        )
    }
    return monorepoUtilsInstance
}
