import * as path from "path" ; import * as crypto from "crypto" ; import { Construct } from 'constructs' ; import * as cdk from "aws-cdk-lib" ; import * as iam from "aws-cdk-lib/aws-iam" ; import * as lambda from "aws-cdk-lib/aws-lambda" ; export function getOrCreateBucket ( scope ) { const providerId = "EdgeLambdaBucketProvider" ; const resId = "EdgeLambdaBucket" ; const stack = cdk . Stack . of ( scope ) ; const existingResource = stack . node . tryFindChild ( resId ) as cdk . CustomResource ; if ( existingResource ) { return existingResource ; } const provider = new lambda . Function ( stack , providerId , { code : lambda . Code . fromAsset ( path . join ( __dirname , "custom-resource" ) ) , handler : "s3-bucket.handler" , runtime : lambda . Runtime . NODEJS_12_X , timeout : cdk . Duration . minutes ( 15 ) , memorySize : 1024 , initialPolicy : [ new iam . PolicyStatement ( { effect : iam . Effect . ALLOW , actions : [ "s3:*" ] , resources : [ "*" ] , } ) , ] , } ) ; const resource = new cdk . CustomResource ( stack , resId , { serviceToken : provider . functionArn , resourceType : "Custom::SSTEdgeLambdaBucket" , properties : { BucketNamePrefix : `${ stack . stackName }-${ resId }` , } , } ) ; return resource ; } export function createFunction ( scope , name , role , bucketName , functionParams ) { const providerId = "EdgeLambdaProvider" ; const resId = `${ name }EdgeLambda` ; const stack = cdk . Stack . of ( scope ) ; let provider = stack . node . tryFindChild ( providerId ) as lambda . Function ; if ( ! provider ) { provider = new lambda . Function ( stack , providerId , { code : lambda . Code . fromAsset ( path . join ( __dirname , "custom-resource" ) ) , handler : "edge-lambda.handler" , runtime : lambda . Runtime . NODEJS_12_X , timeout : cdk . Duration . minutes ( 15 ) , memorySize : 1024 , initialPolicy : [ new iam . PolicyStatement ( { effect : iam . Effect . ALLOW , actions : [ "lambda:*" , "s3:*" ] , resources : [ "*" ] , } ) , ] , } ) ; if ( provider . role ) { role . grantPassRole ( provider . role ) ; } } const resource = new cdk . CustomResource ( scope , resId , { serviceToken : provider . functionArn , resourceType : "Custom::SSTEdgeLambda" , properties : { FunctionNamePrefix : `${ cdk . Stack . of ( scope ) . stackName }-${ resId }` , FunctionBucket : bucketName , FunctionParams : functionParams , } , } ) ; return resource ; } export function createVersion ( scope , name , functionArn ) { const providerId = "EdgeLambdaVersionProvider" ; const resId = `${ name }EdgeLambdaVersion` ; const stack = cdk . Stack . of ( scope ) ; let provider = stack . node . tryFindChild ( providerId ) as lambda . Function ; if ( ! provider ) { provider = new lambda . Function ( stack , providerId , { code : lambda . Code . fromAsset ( path . join ( __dirname , "custom-resource" ) ) , handler : "edge-lambda-version.handler" , runtime : lambda . Runtime . NODEJS_12_X , timeout : cdk . Duration . minutes ( 15 ) , memorySize : 1024 , initialPolicy : [ new iam . PolicyStatement ( { effect : iam . Effect . ALLOW , actions : [ "lambda:*" ] , resources : [ "*" ] , } ) , ] , } ) ; } return new cdk . CustomResource ( scope , resId , { serviceToken : provider . functionArn , resourceType : "Custom::SSTEdgeLambdaVersion" , properties : { FunctionArn : functionArn , } , } ) ; } export function updateVersionLogicalId ( functionCR , versionCR ) { const cfn = versionCR . node . defaultChild as cdk . CfnResource ; const originalLogicalId = cdk . Stack . of ( versionCR ) . resolve ( cfn . logicalId ) as string ; cfn . overrideLogicalId ( cdk . Lazy . uncachedString ( { produce : ( ) => { const hash = calculateHash ( functionCR ) ; const logicalId = trimFromStart ( originalLogicalId , 255 - 32 ) ; return `${ logicalId }${ hash }` ; } , } ) ) ; } function trimFromStart ( s , maxLength ) { const desiredLength = Math . min ( maxLength , s . length ) ; const newStart = s . length - desiredLength ; return s . substring ( newStart ) ; } function calculateHash ( resource ) { const cfnResource = resource . node . defaultChild as cdk . CfnResource ; const config = cdk . Stack . of ( resource ) . resolve ( ( cfnResource as any ) . _toCloudFormation ( ) ) ; const resources = config . Resources ; const resourceKeys = Object . keys ( resources ) ; if ( resourceKeys . length !== 1 ) { throw new Error ( `Expected one rendered CloudFormation resource but found ${ resourceKeys . length }` ) ; } const logicalId = resourceKeys [ 0 ] ; const properties = resources [ logicalId ] . Properties . FunctionParams ; const hash = crypto . createHash ( "md5" ) ; hash . update ( JSON . stringify ( properties ) ) ; return hash . digest ( "hex" ) ; }