import { Construct , Duration , Aws , CfnMapping , CfnOutput , CustomResource , Stack } from '@aws-cdk/core' ; import * as events from '@aws-cdk/aws-events' ; import * as targets from '@aws-cdk/aws-events-targets' ; import * as ecs from '@aws-cdk/aws-ecs' ; import * as ec2 from '@aws-cdk/aws-ec2' ; import * as lambda from '@aws-cdk/aws-lambda' ; import * as iam from '@aws-cdk/aws-iam' ; import * as cr from "@aws-cdk/custom-resources" ; import { CfnLogGroup , RetentionDays , LogGroup } from '@aws-cdk/aws-logs' ; import * as path from 'path' ; import { addCfnNagSuppressRules } from "./main-stack" ; export interface Env { [ key ] : any ; } export interface EcsTaskProps { readonly env : Env ; readonly vpc : ec2 . IVpc ; readonly ecsSubnetIds : string [ ] ; readonly ecsClusterName : string ; readonly cpu ? : number ; readonly memory ? : number ; readonly cliRelease : string ; } export class EcsStack extends Construct { readonly taskDefinition : ecs . TaskDefinition ; readonly securityGroup : ec2 . SecurityGroup ; constructor ( scope , id , props ) { super ( scope , id ) ; const ecrRegistry = process . env . PUBLIC_ECR_REGISTRY || 'public.ecr.aws/aws-gcr-solutions' ; const ecrImageName = 'data-transfer-hub-cli' ; const ecrImageTag = process . env . PUBLIC_ECR_TAG || props . cliRelease ; const ecrImageUrl = `${ ecrRegistry }/${ ecrImageName }:${ ecrImageTag }` ; const ecsLG = new LogGroup ( this , 'FinderLogGroup' , { retention : RetentionDays . TWO_WEEKS , } ) ; const cfnEcsLG = ecsLG . node . defaultChild as CfnLogGroup ; addCfnNagSuppressRules ( cfnEcsLG , [ { id : 'W84' , reason : 'log group is encrypted with the default master key' } ] ) ; this . taskDefinition = new ecs . FargateTaskDefinition ( this , 'JobFinderTaskDef' , { cpu : props . cpu ? props . cpu : 1024 * 4 , memoryLimitMiB : props . memory ? props . memory : 1024 * 8 , family : `${ Aws . STACK_NAME }-DTHFinderTask` , } ) ; this . taskDefinition . addContainer ( 'DefaultContainer' , { image : ecs . ContainerImage . fromRegistry ( ecrImageUrl ) , environment : props . env , logging : ecs . LogDrivers . awsLogs ( { streamPrefix : 'ecsJobSender' , logGroup : ecsLG , } ) } ) ; const cluster = ecs . Cluster . fromClusterAttributes ( this , 'ECSCluster' , { clusterName : props . ecsClusterName , vpc : props . vpc , securityGroups : [ ] } ) ; this . securityGroup = new ec2 . SecurityGroup ( this , 'S3RepECSSG' , { vpc : props . vpc , description : `Security Group for running ${ Aws . STACK_NAME }-DTHFinderTask` , allowAllOutbound : true } ) ; const cfnSG = this . securityGroup . node . defaultChild as ec2 . CfnSecurityGroup ; addCfnNagSuppressRules ( cfnSG , [ { id : 'W5' , reason : 'Open egress rule is required to access public network' } , { id : 'W40' , reason : 'Open egress rule is required to access public network' } , ] ) ; const trigger = new events . Rule ( this , 'DTHFinderSchedule' , { schedule : events . Schedule . rate ( Duration . hours ( 1 ) ) , } ) ; trigger . addTarget ( new targets . EcsTask ( { cluster : cluster , taskDefinition : this . taskDefinition , taskCount : 1 , subnetSelection : { subnetType : ec2 . SubnetType . PUBLIC , } , securityGroups : [ this . securityGroup ] } ) ) ; const onEventHandler = new lambda . Function ( this , 'EventHandler' , { runtime : lambda . Runtime . PYTHON_3_8 , code : lambda . Code . fromAsset ( path . join ( __dirname , '../lambda' ) ) , handler : 'lambda_event_handler.lambda_handler' , memorySize : 256 , timeout : Duration . minutes ( 15 ) , } ) ; const cfnFn = onEventHandler . node . defaultChild as lambda . CfnFunction ; addCfnNagSuppressRules ( cfnFn , [ { id : 'W58' , reason : 'False alarm: The Lambda function does have the permission to write CloudWatch Logs.' } , { id : 'W92' , reason : 'No concurrencies required for this function' } , { id : 'W89' , reason : 'This function does not need to be deployed in a VPC' } ] ) ; onEventHandler . node . addDependency ( this . taskDefinition ) ; const taskDefArnNoVersion = Stack . of ( this ) . formatArn ( { service : 'ecs' , resource : 'task-definition' , resourceName : this . taskDefinition . family } ) ; const ecsTaskPolicy = new iam . Policy ( this , 'ECSTaskPolicy' , { statements : [ new iam . PolicyStatement ( { actions : [ 'ecs:ListTasks' ] , effect : iam . Effect . ALLOW , resources : [ '*' ] } ) , new iam . PolicyStatement ( { actions : [ 'ecs:RunTask' ] , effect : iam . Effect . ALLOW , resources : [ taskDefArnNoVersion ] } ) ] } ) ; const cfnEcsTaskPolicy = ecsTaskPolicy . node . defaultChild as iam . CfnPolicy ; addCfnNagSuppressRules ( cfnEcsTaskPolicy , [ { id : 'W12' , reason : 'List Task Action requires any resources' } , ] ) ; onEventHandler . role ?. attachInlinePolicy ( ecsTaskPolicy ) ; this . taskDefinition . taskRole . grantPassRole ( onEventHandler . grantPrincipal ) ; this . taskDefinition . executionRole ?. grantPassRole ( onEventHandler . grantPrincipal ) ; const lambdaProvider = new cr . Provider ( this , 'Provider' , { onEventHandler : onEventHandler , } ) ; lambdaProvider . node . addDependency ( this . taskDefinition ) ; const ecsCr = new CustomResource ( this , 'DRHS3CustomResource' , { serviceToken : lambdaProvider . serviceToken , properties : { 'cluster_name' : props . ecsClusterName , 'family' : this . taskDefinition . family , 'subnets' : props . ecsSubnetIds , 'security_group' : this . securityGroup . securityGroupName , 'stack_name' : Aws . STACK_NAME , } } ) ; ecsCr . node . addDependency ( lambdaProvider , this . taskDefinition ) ; new CfnOutput ( this , 'TaskDefinitionName' , { value : this . taskDefinition . family , description : 'Task Definition Name' } ) ; } }