137 lines
4.7 KiB
TypeScript
137 lines
4.7 KiB
TypeScript
import * as cdk from "aws-cdk-lib/core";
|
|
import { Construct } from "constructs";
|
|
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
|
|
import * as ec2 from "aws-cdk-lib/aws-ec2";
|
|
import * as ecs from "aws-cdk-lib/aws-ecs";
|
|
import * as ssm from "aws-cdk-lib/aws-ssm"
|
|
import * as acm from "aws-cdk-lib/aws-certificatemanager"
|
|
import * as route53 from "aws-cdk-lib/aws-route53"
|
|
import * as route53Targets from "aws-cdk-lib/aws-route53-targets"
|
|
import * as elbv2 from "aws-cdk-lib/aws-elasticloadbalancingv2"
|
|
import * as path from "path";
|
|
|
|
interface StackProps extends cdk.StackProps {
|
|
environment: string
|
|
}
|
|
|
|
export class GhostV2Stack extends cdk.Stack {
|
|
constructor(scope: Construct, id: string, props: StackProps) {
|
|
super(scope, id, props);
|
|
|
|
const table = new dynamodb.TableV2(this, "ghostv2-table", {
|
|
partitionKey: { name: "pk", type: dynamodb.AttributeType.STRING },
|
|
sortKey: { name: "sk", type: dynamodb.AttributeType.STRING },
|
|
billing: dynamodb.Billing.onDemand(),
|
|
});
|
|
table.addGlobalSecondaryIndex({
|
|
indexName: "gsi1",
|
|
partitionKey: { name: "gsi1pk", type: dynamodb.AttributeType.STRING },
|
|
sortKey: { name: "gsi1sk", type: dynamodb.AttributeType.STRING },
|
|
});
|
|
if (props.environment !== "prod") {
|
|
return;
|
|
}
|
|
|
|
const vpc = new ec2.Vpc(this, "ghostv2 vpc", {
|
|
maxAzs: 2,
|
|
natGateways: 0,
|
|
subnetConfiguration: [
|
|
{
|
|
name: "Public",
|
|
subnetType: ec2.SubnetType.PUBLIC,
|
|
cidrMask: 24,
|
|
},
|
|
],
|
|
});
|
|
|
|
const ecsSecurityGroup = new ec2.SecurityGroup(this, "ghostv2 security group", {
|
|
vpc,
|
|
description: "Security group for ECS Fargate tasks",
|
|
allowAllOutbound: true,
|
|
});
|
|
ecsSecurityGroup.addIngressRule(
|
|
ec2.Peer.anyIpv4(),
|
|
ec2.Port.tcp(80),
|
|
"Allow HTTP"
|
|
);
|
|
ecsSecurityGroup.addIngressRule(
|
|
ec2.Peer.anyIpv4(),
|
|
ec2.Port.tcp(443),
|
|
"Allow HTTPS"
|
|
);
|
|
ecsSecurityGroup.addIngressRule(
|
|
ec2.Peer.anyIpv4(),
|
|
ec2.Port.tcp(8080),
|
|
"Allow API"
|
|
);
|
|
|
|
const cluster = new ecs.Cluster(this, "ghostv2 cluster", {
|
|
vpc,
|
|
});
|
|
const taskDefinition = new ecs.FargateTaskDefinition(
|
|
this,
|
|
"ghostv2 api",
|
|
{
|
|
memoryLimitMiB: 512,
|
|
cpu: 256,
|
|
runtimePlatform: {
|
|
cpuArchitecture: ecs.CpuArchitecture.X86_64,
|
|
operatingSystemFamily: ecs.OperatingSystemFamily.LINUX,
|
|
},
|
|
}
|
|
);
|
|
const secretImports = ["DATABASE_URL", "GH_CLIENT_ID", "GH_CLIENT_SECRET", "REPOS_TABLE_NAME", "SENTRY_DSN", "FRONTEND_BASE_URL"];
|
|
const secrets = secretImports.reduce<Record<string, ecs.Secret>>((acc, name) => {
|
|
acc[name] = ecs.Secret.fromSsmParameter(ssm.StringParameter.fromSecureStringParameterAttributes(this, `param${name}`, {
|
|
parameterName: `/ghostv2/${props.environment}/${name}`
|
|
}))
|
|
return acc
|
|
}, {})
|
|
taskDefinition.addContainer("api", {
|
|
image: ecs.ContainerImage.fromAsset(path.join(__dirname, "../../api")),
|
|
environment: {
|
|
RUST_LOG: "info",
|
|
},
|
|
portMappings: [
|
|
{
|
|
containerPort: 8080,
|
|
protocol: ecs.Protocol.TCP,
|
|
},
|
|
],
|
|
secrets
|
|
});
|
|
table.grantReadWriteData(taskDefinition.taskRole);
|
|
const service = new ecs.FargateService(this, "ghostv2 service", {
|
|
cluster,
|
|
taskDefinition,
|
|
desiredCount: 1,
|
|
assignPublicIp: true,
|
|
vpcSubnets: {
|
|
subnetType: ec2.SubnetType.PUBLIC,
|
|
},
|
|
securityGroups: [ecsSecurityGroup],
|
|
capacityProviderStrategies: [
|
|
{
|
|
capacityProvider: "FARGATE_SPOT",
|
|
weight: 1,
|
|
},
|
|
],
|
|
});
|
|
const hostedZone = route53.HostedZone.fromLookup(this, "hosted zone", { domainName: "lucalise.ca" })
|
|
const certificate = new acm.Certificate(this, "ghostv2 api cert", {
|
|
domainName: "api.ghostv2.lucalise.ca",
|
|
validation: acm.CertificateValidation.fromDns(hostedZone)
|
|
})
|
|
const alb = new elbv2.ApplicationLoadBalancer(this, "ghostv2 alb", {
|
|
vpc,
|
|
internetFacing: true,
|
|
securityGroup: ecsSecurityGroup,
|
|
});
|
|
alb.setAttribute("idle_timeout.timeout_seconds", "120")
|
|
alb.addRedirect({ sourcePort: 80, sourceProtocol: elbv2.ApplicationProtocol.HTTP, targetPort: 443, targetProtocol: elbv2.ApplicationProtocol.HTTPS })
|
|
const listener = alb.addListener("https listener", { port: 443, open: true, certificates: [certificate] })
|
|
listener.addTargets("ECSTargets", { port: 8080, protocol: elbv2.ApplicationProtocol.HTTP, targets: [service], healthCheck: { path: "/", port: "8080" } })
|
|
new route53.ARecord(this, "alb record", { zone: hostedZone, recordName: "api.ghostv2.lucalise.ca", target: route53.RecordTarget.fromAlias(new route53Targets.LoadBalancerTarget(alb)) })
|
|
}
|
|
}
|