import * as cdk from '@aws-cdk/core'; import * as dynamodb from '@aws-cdk/aws-dynamodb'; import * as s3 from '@aws-cdk/aws-s3'; import * as s3Deploy from '@aws-cdk/aws-s3-deployment'; import * as cloudfront from '@aws-cdk/aws-cloudfront'; import * as route53 from '@aws-cdk/aws-route53'; import * as acm from '@aws-cdk/aws-certificatemanager'; import * as targets from '@aws-cdk/aws-route53-targets/lib'; import { OriginAccessIdentity } from '@aws-cdk/aws-cloudfront'; import * as ecr from '@aws-cdk/aws-ecr'; import * as ecsPatterns from '@aws-cdk/aws-ecs-patterns' import * as ecs from '@aws-cdk/aws-ecs' import * as ec2 from '@aws-cdk/aws-ec2' import * as elb2 from '@aws-cdk/aws-elasticloadbalancingv2' import { StaticSite, StaticSiteProps } from './static-website/static-webiste'; export class CdkStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // 0. we need a NoSQL db to persist data // create dynamoDb table to store data const table = new dynamodb.Table(this, 'Questions', { partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING }, sortKey: {name: 'sk', type: dynamodb.AttributeType.STRING}, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, tableName: 'Questions' }); new cdk.CfnOutput(this, 'Dynamo', { value: table.tableName }); // 1. we need an angular app to fun the FE and connect to an app api // - S3 Bucket, Cloudfront Dist, Route 53 Alias // create S3 + CloudFront to host the FE const siteSubDomain = "app"; const domainName = "yourdomain.com"; const zone = route53.HostedZone.fromLookup(this, 'Zone', { domainName: domainName }); const siteDomain = siteSubDomain + '.' + domainName; new cdk.CfnOutput(this, 'Site', { value: 'https://' + siteDomain }); // TLS certificate const wildCartCert = new acm.DnsValidatedCertificate(this, 'SiteCertificate', { domainName: "*.yourdomain.com", hostedZone: zone, region: 'us-east-1', // Cloudfront only checks this region for certificates. }); const certificateArn = wildCartCert.certificateArn; new cdk.CfnOutput(this, 'Certificate', { value: certificateArn }); // S3 const bucket = new s3.Bucket(this, "MathDynastyAppBucket", { publicReadAccess: false, removalPolicy: cdk.RemovalPolicy.DESTROY, websiteIndexDocument: "index.html", bucketName: "math-dynasty-app-bucket" }); new cdk.CfnOutput(this, 'Bucket', { value: bucket.bucketName }); const oia = new OriginAccessIdentity(this, 'OIA', { comment: "Created by CDK" }); bucket.grantRead(oia); // Cloudfront const distribution = new cloudfront.CloudFrontWebDistribution(this, "CDKMDAAPPDistribution", { aliasConfiguration: { acmCertRef: certificateArn, names: [ siteDomain ], sslMethod: cloudfront.SSLMethod.SNI, securityPolicy: cloudfront.SecurityPolicyProtocol.TLS_V1_1_2016 }, originConfigs: [ { s3OriginSource: { s3BucketSource: bucket, originAccessIdentity: oia }, behaviors: [{isDefaultBehavior: true}] }, ] }); new cdk.CfnOutput(this, 'DistributionId', { value: distribution.distributionId }); // Route53 alias record for the CloudFront distribution new route53.ARecord(this, 'SiteAliasRecord', { recordName: siteDomain, target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)), zone }); // Deployment - deploy angular app const src = new s3Deploy.BucketDeployment(this, "DeployMDAWithInvalidation", { sources: [s3Deploy.Source.asset("./build")], destinationBucket: bucket, distribution, distributionPaths: ['/*'], }); // 2. we need several ECS Repo // angular FE, express BE, cdk // ECR const appDev = new ecr.Repository(this, 'md-app-dev', { imageScanOnPush: true, repositoryName: 'md-app-dev' }); new cdk.CfnOutput(this, 'App Repo', { value: appDev.repositoryUri }); const appApi = new ecr.Repository(this, 'md-app-api-dev', { imageScanOnPush: true, repositoryName: 'md-app-api-dev' }); new cdk.CfnOutput(this, 'App-API Repo', { value: appApi.repositoryUri }); const appCdk = new ecr.Repository(this, 'md-app-cdk', { imageScanOnPush: true, repositoryName: 'md-app-cdk' }); new cdk.CfnOutput(this, 'App-CDK Repo', { value: appCdk.repositoryUri }); const prodVpc = new ec2.Vpc(this, 'VPC'); // TODO create a prod security group const prodCluster = new ecs.Cluster(this, 'Cluster', { vpc: prodVpc, clusterName: 'prod', }); const prodLoadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'ProdService', { cluster: prodCluster, certificate: wildCartCert, desiredCount: 1, assignPublicIp: true, protocol: elb2.ApplicationProtocol.HTTPS, redirectHTTP: true, memoryLimitMiB: 1024, cpu: 512, taskImageOptions: { image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), containerName: 'app-api', containerPort: 80, enableLogging: true, family: 'prod-app-api', logDriver: ecs.LogDriver.awsLogs({ streamPrefix: "prod-app-api", }) }, domainName: 'app-api.mathdynasty.com', domainZone: zone, serviceName: 'app-api' }); prodLoadBalancedFargateService.targetGroup.configureHealthCheck({ path: "/custom-health-path", }); } }