import * as cdk from 'aws-cdk-lib' ; import { aws_ec2 as ec2 , aws_ecs as ecs , aws_route53 as route53 , aws_certificatemanager as cm , aws_elasticloadbalancingv2 as elbv2 , } from 'aws-cdk-lib' ; import { Construct } from 'constructs' ; interface Tag { name : string ; value : string ; } interface DomainProperties { domainName : string ; subdomainName : string ; domainCertificateArn : string ; } export interface ContainerProperties { image : ecs . ContainerImage ; containerPort : number ; id : string ; environment : { [ key ] : string ; } ; conditions : elbv2 . ListenerCondition [ ] ; } const createHttpsRedirect = ( id , scope , loadBalancer ) => { const port = 80 ; loadBalancer . connections . allowFromAnyIpv4 ( ec2 . Port . tcp ( port ) ) ; const actionProperty = { type : 'redirect' , redirectConfig : { statusCode : 'HTTP_302' , protocol : 'HTTPS' , port : '443' , } , } ; const redirectProps = { defaultActions : [ actionProperty ] , loadBalancerArn : loadBalancer . loadBalancerArn , port , protocol : 'HTTP' , } ; return new elbv2 . CfnListener ( scope , `${ id }HttpRedirect` , redirectProps ) ; } ; const createTaskDefinition = ( id , stack , containerProperties , tags ) => { const taskDefinition = new ecs . FargateTaskDefinition ( stack , `${ id }TaskDefinition` ) ; taskDefinition . addContainer ( `${ id }Container` , { image : containerProperties . image , memoryLimitMiB : 256 , environment : containerProperties . environment , logging : new ecs . AwsLogDriver ( { streamPrefix : `${ id }` } ) , } ) . addPortMappings ( { containerPort : containerProperties . containerPort , protocol : ecs . Protocol . TCP , } ) ; tags . forEach ( ( tag ) => cdk . Tags . of ( taskDefinition ) . add ( tag . name , tag . value ) ) ; return taskDefinition ; } ; const configureClusterAndServices = ( id , stack , cluster , certificate , containerProperties , tags ) => { const services = containerProperties . map ( ( container ) => new ecs . FargateService ( stack , `${ container . id }FargateService` , { cluster , taskDefinition : createTaskDefinition ( `${ container . id }` , stack , container , tags ) , } ) ) ; const loadBalancer = new elbv2 . ApplicationLoadBalancer ( stack , `${ id }LoadBalancer` , { vpc : cluster . vpc , internetFacing : true , } ) ; createHttpsRedirect ( id , stack , loadBalancer ) ; const listener = loadBalancer . addListener ( `${ id }HttpsListener` , { port : 443 , certificates : [ elbv2 . ListenerCertificate . fromArn ( certificate . certificateArn ) ] , } ) ; services . forEach ( ( service , i ) => service . registerLoadBalancerTargets ( { containerName : `${ containerProperties [ i ] . id }Container` , containerPort : containerProperties [ i ] . containerPort , newTargetGroupId : `${ containerProperties [ i ] . id }TargetGroup` , listener : ecs . ListenerConfig . applicationListener ( listener , { protocol : elbv2 . ApplicationProtocol . HTTP , priority : 10 + i * 10 , conditions : containerProperties [ i ] . conditions , } ) } ) ) ; listener . addAction ( `${ id }FixedResponse` , { action : elbv2 . ListenerAction . fixedResponse ( 404 , { messageBody : 'Not Found' , } ) , } ) ; return { loadBalancer , services } ; } ; param scope param id param containerProperties param domainProperties param tags param props param vpc export const createStack = ( scope , id , containerProperties , domainProperties , tags , props , vpc ? ) => { const stack = new cdk . Stack ( scope , id , props ) ; tags . forEach ( ( tag ) => cdk . Tags . of ( stack ) . add ( tag . name , tag . value ) ) ; const certificate = cm . Certificate . fromCertificateArn ( stack , `${ id }Certificate` , domainProperties . domainCertificateArn ) ; const vpcInUse = vpc ? vpc : new ec2 . Vpc ( stack , `${ id }Vpc` , { maxAzs : 2 } ) ; const cluster = new ecs . Cluster ( stack , `${ id }Cluster` , { vpc : vpcInUse } ) ; const { loadBalancer } = configureClusterAndServices ( id , stack , cluster , certificate , containerProperties , tags ) ; const zone = route53 . HostedZone . fromLookup ( stack , `${ id }Zone` , { domainName : domainProperties . domainName } ) ; new route53 . CnameRecord ( stack , `${ id }Site` , { zone , recordName : domainProperties . subdomainName , domainName : loadBalancer . loadBalancerDnsName , } ) ; new cdk . CfnOutput ( stack , `${ id }DNS` , { value : loadBalancer . loadBalancerDnsName } ) ; new cdk . CfnOutput ( stack , `SiteDNS` , { value : `${ domainProperties . subdomainName }.${ domainProperties . domainName }` } ) ; return stack ; } ;