import { env } from 'process' ; import { Stream , PassThrough } from 'stream' ; import * as aws from 'aws-sdk' ; import { PutObjectRequest } from 'aws-sdk/clients/s3' ; import { Image } from '../image' ; import { getDockerImageTags } from './docker-adapter' ; import { getEcrImageTags } from './ecr-adapter' ; export interface ContainerImage { tag : string ; lastUpdated : string ; digest ? : string ; } export async function handler ( ) { const accountId = env . AWS_ACCOUNT_ID ! ; const region = env . REGION ! ; const images = JSON . parse ( env . IMAGES ?? '[]' ) ; const repoPrefix = env . REPO_PREFIX ?? '' ; let buildTriggerFile = '' ; await Promise . all ( images . map ( async ( image ) => { let ecrImageName = image . imageName ; if ( repoPrefix . length > 0 ) { ecrImageName = `${ repoPrefix }/${ ecrImageName }` ; } const ecrImageTags = await getEcrImageTags ( ecrImageName ) ; console . debug ( `ECR images for ${ image . imageName }: \n${ ecrImageTags . map ( t => `${ t . tag } (${ t . digest })` ) . join ( '\n' ) }` ) ; const dockerImageTags = await getDockerImageTags ( image . imageName ) ; console . debug ( `Docker images for ${ image . imageName }: ${ dockerImageTags . map ( t => `${ t . tag } (${ t . digest })` ) . join ( '\n' ) }` ) ; let missingImageTags = await filterTags ( dockerImageTags , ecrImageTags , image ) ; let orderedImageTags = await orderTags ( missingImageTags ) ; buildTriggerFile += await formatTriggerLines ( image , orderedImageTags , repoPrefix , accountId , region ) ; } ) ) ; console . info ( `Images to sync:\n${ buildTriggerFile }` ) ; if ( buildTriggerFile === '' ) return ; const stream = await zipToFileStream ( buildTriggerFile ) ; await uploadToS3 ( env . BUCKET_NAME ! , 'images.zip' , stream ) ; } export async function orderTags ( imageTags ) { return imageTags . sort ( ( a , b ) => a . lastUpdated . localeCompare ( b . lastUpdated ) ) ; } export async function formatTriggerLines ( image , missingImageTags , repoPrefix , accountId , region ) { let buildTriggerFile = '' ; missingImageTags . forEach ( t => { const ecrImageName = ( repoPrefix ) ? `${ repoPrefix }/${ image . imageName }` : image . imageName ; buildTriggerFile += `${ image . imageName },${ accountId }.dkr.ecr.${ region }.amazonaws.com/${ ecrImageName },${ t . tag }\n` ; } ) ; return buildTriggerFile ; } export async function filterTags ( dockerImageTags , ecrImageTags , image ) { let missingImageTags = dockerImageTags . filter ( dockerImage => { return ecrImageTags . filter ( ecrImage => ecrImage . tag === dockerImage . tag && ecrImage . digest === dockerImage . digest ) . length === 0 ; } ) ; return missingImageTags . filter ( t => { if ( image . includeTags === undefined && image . excludeTags === undefined ) { return true ; } if ( image . excludeTags !== undefined ) { if ( image . excludeTags . some ( excludeTag => t . tag . match ( excludeTag ) ) ) { return false ; } ; } if ( image . includeTags !== undefined ) { if ( image . includeTags . some ( includeTag => t . tag . match ( includeTag ) ) ) { return true ; } } if ( image . includeTags === undefined && image . excludeTags !== undefined ) { return true ; } return false ; } ) ; } async function uploadToS3 ( bucket , key , stream ) { let params = { ACL : 'private' , Body : stream , Bucket : bucket , ContentType : 'application/zip' , Key : key , StorageClass : 'STANDARD_IA' , } ; return new aws . S3 ( ) . upload ( params , ( error ) : void => { if ( error ) { console . error ( `Got error creating stream to s3 ${ error . name } ${ error . message } ${ error . stack }` ) ; throw error ; } } ) . promise ( ) ; } async function zipToFileStream ( content ) { var JSZip = require ( 'jszip' ) ; var zip = new JSZip ( ) ; zip . file ( 'images.csv' , content ) ; const streamPassThrough = new Stream . PassThrough ( ) ; await zip . generateNodeStream ( { type : 'nodebuffer' , streamFiles : true } ) . pipe ( streamPassThrough ) ; return streamPassThrough ; }