Skip to content

Instantly share code, notes, and snippets.

@bkniffler
Last active October 23, 2021 06:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bkniffler/960467cde8a090266b9dbf830ef887e3 to your computer and use it in GitHub Desktop.
Save bkniffler/960467cde8a090266b9dbf830ef887e3 to your computer and use it in GitHub Desktop.
Hasura ECS cdk typescript example
import ec2 = require('@aws-cdk/aws-ec2');
import ecs = require('@aws-cdk/aws-ecs');
import targets = require('@aws-cdk/aws-route53-targets');
import route53 = require('@aws-cdk/aws-route53');
import acm = require('@aws-cdk/aws-certificatemanager');
import secrets = require('@aws-cdk/aws-secretsmanager');
import elb2 = require('@aws-cdk/aws-elasticloadbalancingv2');
import cdk = require('@aws-cdk/core');
export interface StaticSiteProps extends cdk.StackProps {
domainName: string;
dbSecret: string;
dbHost: string;
vpc: ec2.IVpc;
dbName: string;
dbUser: string;
subdomain: string;
}
export class HasuraStack extends cdk.Stack {
graphqlUri: string;
graphqlDns: string;
hasuraAdminSecret: secrets.Secret;
jwtSecret: secrets.Secret;
path: string;
constructor(app: cdk.App, id: string, props: StaticSiteProps) {
super(app, id, props);
const jwtSecret = new secrets.Secret(this, 'Jwt', {
generateSecretString: {
includeSpace: false,
passwordLength: 32,
excludePunctuation: true
}
});
const adminSecret = new secrets.Secret(this, 'HasuraAdminSecret', {
generateSecretString: {
includeSpace: false,
passwordLength: 32,
excludePunctuation: true
}
});
const taskDefinition = new ecs.TaskDefinition(this, 'TaskDefinition', {
compatibility: ecs.Compatibility.EC2
});
const containerDefinition = new ecs.ContainerDefinition(
this,
'ContainerDefinition',
{
logging: new ecs.AwsLogDriver({
streamPrefix: 'ecs'
}),
image: ecs.ContainerImage.fromRegistry('hasura/graphql-engine:v1.1.0'),
secrets: {
HASURA_GRAPHQL_ADMIN_SECRET: ecs.Secret.fromSecretsManager(
adminSecret
)
},
environment: {
HASURA_GRAPHQL_JWT_SECRET: `{"type": "HS256", "key": "${jwtSecret.secretValue.toString()}"}`
},
entryPoint: ['graphql-engine'],
command: [
`--host=${props.dbHost}`,
'--port=5432',
`--user=${props.dbUser}`,
`--dbname=${props.dbName}`,
`--password=${props.dbSecret}`,
'serve'
// '--enable-console'
// '--admin-secret=$(ADMIN_SECRET)'
],
taskDefinition,
memoryReservationMiB: 512
// memoryLimitMiB: 1024
}
);
containerDefinition.addPortMappings({
containerPort: 8080,
hostPort: 8080
});
const cluster = new ecs.Cluster(this, 'EcsCluster', {
vpc: props.vpc
});
cluster.addCapacity('Capacity', {
instanceType: new ec2.InstanceType('t2.micro'),
minCapacity: 0,
desiredCapacity: 1,
maxCapacity: 1
});
const fargate = new ecs.Ec2Service(this, 'FargateService', {
taskDefinition,
// vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC },
// securityGroup: securityGroup2,
// assignPublicIp: true,
cluster
});
const zone = route53.HostedZone.fromLookup(this, 'Zone', {
domainName: props.domainName
});
const loadBalancer = new elb2.ApplicationLoadBalancer(this, 'LB', {
vpc: props.vpc,
internetFacing: true
});
// loadBalancer.addTarget(autoScalingGroup);
const domainName = `${props.subdomain}.${props.domainName}`;
console.log(domainName);
const certficate = new acm.DnsValidatedCertificate(
this,
'DatabaseCertificate',
{
domainName,
hostedZone: zone
}
);
setupHttpsRedirect(loadBalancer);
forwardSslPort(loadBalancer, certficate, fargate);
new route53.ARecord(this, 'DatabaseAliasRecord', {
recordName: domainName,
target: route53.AddressRecordTarget.fromAlias(
new targets.LoadBalancerTarget(loadBalancer)
),
zone
});
const path = '/v1/graphql';
this.graphqlUri = `https://${domainName}${path}`;
this.graphqlDns = domainName;
this.hasuraAdminSecret = adminSecret;
this.jwtSecret = jwtSecret;
this.path = path;
}
}
function setupHttpsRedirect(loadBalancer: elb2.ApplicationLoadBalancer) {
const listener = loadBalancer.addListener('Listener', {
port: 80,
open: true
});
listener.addRedirectResponse('RedirectHTTPS', {
protocol: elb2.Protocol.HTTPS,
statusCode: 'HTTP_301',
port: '443',
host: '#{host}',
path: '/#{path}',
query: '#{query}'
});
}
function forwardSslPort(
loadBalancer: elb2.ApplicationLoadBalancer,
certficate: acm.DnsValidatedCertificate,
target: elb2.IApplicationLoadBalancerTarget
) {
const listener2 = loadBalancer.addListener('Listener2', {
port: 443,
open: true,
certificateArns: [certficate.certificateArn]
});
listener2.addTargets('ECS2', {
port: 8080,
healthCheck: { path: '/healthz' },
targets: [target]
});
}
....
const pgStack = new PostgreStack(app, `PostgreStack`, {
env,
dbName: 'somedb',
dbUser: 'someuser'
});
new HasuraStack(app, `HasuraStack`, {
env,
vpc: pgStack.vpc,
dbHost: pgStack.dbHost,
dbSecret: pgStack.dbSecret,
dbName: pgStack.dbName,
dbUser: pgStack.dbUser,
domainName: 'somedomain.com',
subdomain: 'graphql'
});
....
import ec2 = require('@aws-cdk/aws-ec2');
import rds = require('@aws-cdk/aws-rds');
import secrets = require('@aws-cdk/aws-secretsmanager');
import cdk = require('@aws-cdk/core');
import { CfnOutput } from '@aws-cdk/core';
export interface Props extends cdk.StackProps {
dbName: string;
dbUser: string;
}
export class PostgreStack extends cdk.Stack {
dbHost: string;
dbSecret: string;
dbName: string;
dbUser: string;
vpc: ec2.Vpc;
constructor(app: cdk.App, id: string, props: Props) {
super(app, id, props);
const secret = new secrets.Secret(this, 'DatabaseSecret', {
generateSecretString: {
includeSpace: false,
passwordLength: 32,
excludePunctuation: true
}
});
const vpc = new ec2.Vpc(this, 'Vpc2', {
maxAzs: 2,
natGateways: 0,
cidr: '192.168.0.0/16',
subnetConfiguration: [
{ subnetType: ec2.SubnetType.PUBLIC, name: 'Public' }
]
});
const securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', {
allowAllOutbound: true,
vpc
});
securityGroup.addIngressRule(
ec2.Peer.ipv4(vpc.vpcCidrBlock),
ec2.Port.tcp(5432),
'PostgreSQL'
);
const database = new rds.DatabaseInstance(this, 'PostgreSQL', {
engine: rds.DatabaseInstanceEngine.POSTGRES,
masterUsername: props.dbUser,
masterUserPassword: secret.secretValue,
instanceClass: ec2.InstanceType.of(
ec2.InstanceClass.T2,
ec2.InstanceSize.MICRO
),
securityGroups: [securityGroup],
removalPolicy: cdk.RemovalPolicy.DESTROY,
deletionProtection: false,
databaseName: props.dbName,
multiAz: false,
vpcPlacement: { subnetType: ec2.SubnetType.PUBLIC },
vpc
});
new CfnOutput(this, 'DbAd', {
description: 'DB Ad',
value: database.dbInstanceEndpointAddress
});
this.vpc = vpc;
this.dbHost = database.dbInstanceEndpointAddress;
this.dbName = props.dbName;
this.dbSecret = secret.secretValue.toString();
this.dbUser = props.dbUser;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment