Skip to content

Commit

Permalink
feat(ecs): add support for custom domain name, importing ACM cert
Browse files Browse the repository at this point in the history
  • Loading branch information
briancaffey committed Jun 20, 2021
1 parent 311aedc commit 12c2a46
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 24 deletions.
3 changes: 2 additions & 1 deletion .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
# running `cdk deploy`
export DOMAIN_NAME=example.com
export AWS_DEFAULT_REGION=us-east-1
export AWS_ACCOUNT_ID=111111111111
export AWS_ACCOUNT_ID=111111111111
export CERTIFICATE_ARN=abc123
4 changes: 4 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ new DjangoEcs(scope: Construct, id: string, props: DjangoEcsProps)
* **props** (<code>[DjangoEcsProps](#django-cdk-djangoecsprops)</code>) *No description*
* **imageDirectory** (<code>string</code>) The location of the Dockerfile used to create the main application image.
* **bucketName** (<code>string</code>) Name of existing bucket to use for media files. __*Optional*__
* **certificateArn** (<code>string</code>) Certificate ARN. __*Optional*__
* **domainName** (<code>string</code>) Domain name for backend (including sub-domain). __*Optional*__
* **useCeleryBeat** (<code>boolean</code>) Used to enable the celery beat service. __*Default*__: false
* **vpc** (<code>[IVpc](#aws-cdk-aws-ec2-ivpc)</code>) The VPC to use for the application. It must contain PUBLIC, PRIVATE and ISOLATED subnets. __*Optional*__
* **webCommand** (<code>Array<string></code>) The command used to run the API web service. __*Optional*__
Expand Down Expand Up @@ -107,6 +109,8 @@ Name | Type | Description
-----|------|-------------
**imageDirectory** | <code>string</code> | The location of the Dockerfile used to create the main application image.
**bucketName**? | <code>string</code> | Name of existing bucket to use for media files.<br/>__*Optional*__
**certificateArn**? | <code>string</code> | Certificate ARN.<br/>__*Optional*__
**domainName**? | <code>string</code> | Domain name for backend (including sub-domain).<br/>__*Optional*__
**useCeleryBeat**? | <code>boolean</code> | Used to enable the celery beat service.<br/>__*Default*__: false
**vpc**? | <code>[IVpc](#aws-cdk-aws-ec2-ivpc)</code> | The VPC to use for the application. It must contain PUBLIC, PRIVATE and ISOLATED subnets.<br/>__*Optional*__
**webCommand**? | <code>Array<string></code> | The command used to run the API web service.<br/>__*Optional*__
Expand Down
95 changes: 72 additions & 23 deletions src/django-ecs.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import * as acm from '@aws-cdk/aws-certificatemanager';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as ecs from '@aws-cdk/aws-ecs';
import * as patterns from '@aws-cdk/aws-ecs-patterns';
import * as logs from '@aws-cdk/aws-logs';
import * as route53 from '@aws-cdk/aws-route53';
import * as route53targets from '@aws-cdk/aws-route53-targets';
import * as s3 from '@aws-cdk/aws-s3';
import * as cdk from '@aws-cdk/core';
import { RdsPostgresInstance } from './common/database';
Expand All @@ -17,6 +20,17 @@ import { managementCommandTask } from './ecs/tasks';
*/
export interface DjangoEcsProps {

/**
* Domain name for backend (including sub-domain)
*/
readonly domainName?: string;

/**
* Certificate ARN
*/
readonly certificateArn?: string;


/**
* Name of existing bucket to use for media files
*
Expand Down Expand Up @@ -220,41 +234,76 @@ export class DjangoEcs extends cdk.Construct {
});

/**
* Lookup Certificate from ARN or generate
* Deploy external-dns and related IAM resource if a domain name is included
*/
let certificate = undefined;
if (props.domainName) {

const hostedZone = route53.HostedZone.fromLookup(scope, 'hosted-zone', {
domainName: props.domainName,
});

/**
* Lookup or request ACM certificate depending on value of certificateArn
*/
if (props.certificateArn) {
// lookup ACM certificate from ACM certificate ARN
certificate = acm.Certificate.fromCertificateArn(scope, 'certificate', props.certificateArn);
} else {
// request a new certificate
certificate = new acm.Certificate(this, 'SSLCertificate', {
domainName: props.domainName,
validation: acm.CertificateValidation.fromDns(hostedZone),
});
}

/**
* ECS load-balanced fargate service
*/
const albfs = new patterns.ApplicationLoadBalancedFargateService(scope, 'AlbFargateService', {
cluster: this.cluster,
taskDefinition,
securityGroups: [appSecurityGroup],
desiredCount: 1,
assignPublicIp: true,
});
const albfs = new patterns.ApplicationLoadBalancedFargateService(scope, 'AlbFargateService', {
cluster: this.cluster,
taskDefinition,
securityGroups: [appSecurityGroup],
desiredCount: 1,
assignPublicIp: true,
redirectHTTP: true,
certificate: props.domainName ? certificate : undefined,
});

database.secret.grantRead(albfs.taskDefinition.taskRole);
database.secret.grantRead(albfs.taskDefinition.taskRole);

const albLogsBucket = new s3.Bucket(scope, `${id}-alb-logs`);
const albLogsBucket = new s3.Bucket(scope, `${id}-alb-logs`);

albfs.loadBalancer.logAccessLogs(albLogsBucket);
albfs.loadBalancer.logAccessLogs(albLogsBucket);

/**
/**
* Health check for the application load balancer
*/
albfs.targetGroup.configureHealthCheck({
path: '/api/health-check/',
});
albfs.targetGroup.configureHealthCheck({
path: '/api/health-check/',
});

/**
/**
* Allows the app security group to communicate with the RDS security group
*/
database.rdsSecurityGroup.addIngressRule(appSecurityGroup, ec2.Port.tcp(5432));
database.rdsSecurityGroup.addIngressRule(appSecurityGroup, ec2.Port.tcp(5432));

/**
/**
* Grant the task defintion read-write access to static files bucket
*/
staticFilesBucket.grantReadWrite(albfs.taskDefinition.taskRole);

new cdk.CfnOutput(this, 'bucketName', { value: staticFilesBucket.bucketName! });
new cdk.CfnOutput(this, 'apiUrl', { value: albfs.loadBalancer.loadBalancerFullName });

staticFilesBucket.grantReadWrite(albfs.taskDefinition.taskRole);

new cdk.CfnOutput(this, 'bucketName', { value: staticFilesBucket.bucketName! });
new cdk.CfnOutput(this, 'apiUrl', { value: albfs.loadBalancer.loadBalancerFullName });

if (props.domainName) {
new route53.ARecord(scope, 'ARecord', {
target: route53.RecordTarget.fromAlias(new route53targets.LoadBalancerTarget(albfs.loadBalancer)),
zone: hostedZone,
recordName: props.domainName,
});
}
}
}
}
}
2 changes: 2 additions & 0 deletions src/integ/integ.django-ecs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const construct = new DjangoEcs(stack, 'DjangoEcsSample', {
'./scripts/start_prod.sh',
],
useCeleryBeat: true,
domainName: process.env.DOMAIN_NAME,
// certificateArn: process.env.CERTIFICATE_ARN,
});

/**
Expand Down

0 comments on commit 12c2a46

Please sign in to comment.