Skip to content

Commit

Permalink
feat(refactor): big refactor of base and app constructs
Browse files Browse the repository at this point in the history
  • Loading branch information
briancaffey committed Jan 4, 2023
1 parent f38c130 commit 46d7a9a
Show file tree
Hide file tree
Showing 12 changed files with 1,132 additions and 179 deletions.
1 change: 1 addition & 0 deletions .gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .projenrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const project = new awscdk.AwsCdkConstructLibrary({
repositoryUrl: 'git@github.com:briancaffey/cdk-django.git',
// https://github.com/projen/projen/issues/1941
bundledDeps: ['@types/jest@27.4.1'],
gitignore: ['cdk.out', 'notes', 'app.yml', 'base.yml'],
gitignore: ['cdk.out', 'notes', 'app.yml', 'base.yml', 'cdk.context.json'],

// deps: [], /* Runtime dependencies of this module. */
// description: undefined, /* The description is just a string that helps people understand the purpose of the package. */
Expand Down
10 changes: 0 additions & 10 deletions cdk.context.json

This file was deleted.

12 changes: 5 additions & 7 deletions src/constructs/ad-hoc/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export interface AdHocAppProps {
readonly listener: ApplicationListener;

// application specific props
readonly backendVersion: string;
readonly frontendVersion: string;
readonly backendVersion?: string;
readonly frontendVersion?: string;
readonly djangoSettingsModule?: string;
}

Expand All @@ -38,8 +38,6 @@ export class AdHocApp extends Construct {
super(scope, id);

const stackName = Stack.of(this).stackName;
// const awsAccountId = Stack.of(this).account;
// const awsRegion = Stack.of(this).region;

// custom resource to get the highest available listener rule priority
// then take the next two highest priorities and use them for the frontend and backend listener rule priorities
Expand All @@ -48,7 +46,8 @@ export class AdHocApp extends Construct {
// const highestPriorityRule = new HighestPriorityRule(this, 'HighestPriorityRule', { listener: props.listener });

const backendEcrRepo = Repository.fromRepositoryName(this, 'BackendRepo', 'backend');
const backendImage = new EcrImage(backendEcrRepo, props.backendVersion);
const backendVersion = props.frontendVersion ?? 'latest';
const backendImage = new EcrImage(backendEcrRepo, backendVersion);

const frontendEcrRepo = Repository.fromRepositoryName(this, 'FrontendRepo', 'frontend');
const frontendVersion = props.frontendVersion ?? 'latest';
Expand Down Expand Up @@ -94,8 +93,7 @@ export class AdHocApp extends Construct {
taskRole: ecsRoles.ecsTaskRole,
executionRole: ecsRoles.taskExecutionRole,
image: ContainerImage.fromRegistry('redis:5.0.3-alpine'),
containerName: 'redis',
family: 'redis',
name: 'redis',
serviceDiscoveryNamespace: props.serviceDiscoveryNamespace,
});

Expand Down
160 changes: 23 additions & 137 deletions src/constructs/ad-hoc/base/index.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
import {
Duration,
Fn,
RemovalPolicy,
Stack,
} from 'aws-cdk-lib';
import { BastionHostLinux, CfnInstance, IVpc, Peer, Port, SecurityGroup, SubnetType, UserData } from 'aws-cdk-lib/aws-ec2';
import {
ApplicationProtocol,
ApplicationListener,
ApplicationLoadBalancer,
ApplicationTargetGroup,
ListenerCertificate,
TargetType,
ListenerAction,
} from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import { RemovalPolicy, Stack } from 'aws-cdk-lib';
import { IVpc, SecurityGroup } from 'aws-cdk-lib/aws-ec2';
import { ApplicationListener, ApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import { DatabaseInstance } from 'aws-cdk-lib/aws-rds';
import { Bucket } from 'aws-cdk-lib/aws-s3';
import { PrivateDnsNamespace } from 'aws-cdk-lib/aws-servicediscovery';
import { Construct } from 'constructs';
import { AlbResources } from '../../internal/alb';
import { BastionHostResources } from '../../internal/bastion';
import { RdsInstance } from '../../internal/rds';
import { SecurityGroupResources } from '../../internal/sg';
import { ApplicationVpc } from '../../internal/vpc';

// TODO: add props
export interface AdHocBaseProps {
readonly certificateArn: string;
readonly domainName: string;
Expand All @@ -32,6 +21,7 @@ export class AdHocBase extends Construct {
public vpc: IVpc;
public alb: ApplicationLoadBalancer;
public appSecurityGroup: SecurityGroup;
public albSecurityGroup: SecurityGroup;
public serviceDiscoveryNamespace: PrivateDnsNamespace;
public databaseInstance: DatabaseInstance;
public assetsBucket: Bucket;
Expand All @@ -41,108 +31,44 @@ export class AdHocBase extends Construct {
constructor(scope: Construct, id: string, props: AdHocBaseProps) {
super(scope, id);

// get the stack name
const stackName = Stack.of(this).stackName;
const foo = this.node.tryGetContext('config').extraEnvVars;

console.log(foo);

// the domain name to use for the ad hoc environments
const stackName = Stack.of(this).stackName;
this.domainName = props.domainName;

// vpc
const vpc = new ApplicationVpc(scope, 'Vpc');
this.vpc = vpc.vpc;
const applicationVpc = new ApplicationVpc(scope, 'Vpc');
this.vpc = applicationVpc.vpc;

// one bucket for all environments
const assetsBucket = new Bucket(scope, 'AssetsBucket', {
bucketName: `${props.domainName.replace('.', '-')}-${stackName}-assets-bucket`,
removalPolicy: RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});
this.assetsBucket = assetsBucket;

// security group for the ALB
const albSecurityGroup = new SecurityGroup(scope, 'AlbSecurityGroup', {
vpc: this.vpc,
});

// allow internet traffic from port 80 and 443 to the ALB for HTTP and HTTPS
albSecurityGroup.addIngressRule(Peer.anyIpv4(), Port.tcp(443), 'HTTPS');
albSecurityGroup.addIngressRule(Peer.anyIpv4(), Port.tcp(80), 'HTTP');

// create application security group
const appSecurityGroup = new SecurityGroup(scope, 'AppSecurityGroup', {
const { albSecurityGroup, appSecurityGroup } = new SecurityGroupResources(this, 'SecurityGroupResources', {
vpc: this.vpc,
});
appSecurityGroup.connections.allowFrom(appSecurityGroup, Port.allTcp());
appSecurityGroup.connections.allowTo(appSecurityGroup, Port.allTcp());

this.albSecurityGroup = albSecurityGroup;
this.appSecurityGroup = appSecurityGroup;

// allow traffic from ALB security group to the application security group
appSecurityGroup.addIngressRule(albSecurityGroup, Port.allTcp(), 'ALB');

// load balancer
const loadBalancer = new ApplicationLoadBalancer(scope, 'LoadBalancer', {
vpc: this.vpc,
securityGroup: albSecurityGroup,
internetFacing: true,
vpcSubnets: {
subnetType: SubnetType.PUBLIC,
},
});
this.alb = loadBalancer;

// application target group
// Target group with duration-based stickiness with load-balancer generated cookie
// const defaultTargetGroup =
new ApplicationTargetGroup(this, 'default-target-group', {
targetType: TargetType.INSTANCE,
port: 80,
stickinessCookieDuration: Duration.minutes(5),
const { alb, listener } = new AlbResources(this, 'AlbResources', {
albSecurityGroup,
vpc: this.vpc,
healthCheck: {
path: '/api/health-check/', // TODO parametrize this
interval: Duration.minutes(5),
timeout: Duration.minutes(2),
healthyThresholdCount: 2,
unhealthyThresholdCount: 3,
port: '80', // TODO parametrize this
},
certificateArn: props.certificateArn,
});
this.alb = alb;
this.listener = listener;

// listener - HTTP
new ApplicationListener(this, 'http-listener', {
loadBalancer: loadBalancer,
port: 80,
protocol: ApplicationProtocol.HTTP,
defaultAction: ListenerAction.redirect({
protocol: ApplicationProtocol.HTTPS,
port: '443',
permanent: false,
}),
});

// listener - HTTPS
const httpsListener = new ApplicationListener(this, 'https-listener', {
loadBalancer: loadBalancer,
port: 443,
protocol: ApplicationProtocol.HTTPS,
certificates: [ListenerCertificate.fromArn(props.certificateArn)],
defaultAction: ListenerAction.fixedResponse(200, {
contentType: 'text/plain',
messageBody: 'Fixed content response',
}),
});
this.listener = httpsListener;

// Service Discovery namespace
const serviceDiscoveryPrivateDnsNamespace = new PrivateDnsNamespace(this, 'ServiceDiscoveryNamespace', {
vpc: this.vpc,
// TODO: add stack name as part of the name
name: `${stackName}-sd-ns`,
});
this.serviceDiscoveryNamespace = serviceDiscoveryPrivateDnsNamespace;

// RDS
const rdsInstance = new RdsInstance(this, 'RdsInstance', {
vpc: this.vpc,
appSecurityGroup: appSecurityGroup,
Expand All @@ -151,50 +77,10 @@ export class AdHocBase extends Construct {
this.databaseInstance = rdsInstance.rdsInstance;
const { dbInstanceEndpointAddress } = rdsInstance.rdsInstance;

const socatForwarderString = `
package_upgrade: true
packages:
- postgresql
- socat
write_files:
- content: |
# /etc/systemd/system/socat-forwarder.service
[Unit]
Description=socat forwarder service
After=socat-forwarder.service
Requires=socat-forwarder.service
[Service]
Type=simple
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=socat-forwarder
ExecStart=/usr/bin/socat -d -d TCP4-LISTEN:5432,fork TCP4:${dbInstanceEndpointAddress}:5432
Restart=always
[Install]
WantedBy=multi-user.target
path: /etc/systemd/system/socat-forwarder.service
runcmd:
- [ systemctl, daemon-reload ]
- [ systemctl, enable, socat-forwarder.service ]
# https://dustymabe.com/2015/08/03/installingstarting-systemd-services-using-cloud-init/
- [ systemctl, start, --no-block, socat-forwarder.service ]
`;

const bastionHost = new BastionHostLinux(this, 'BastionHost', {
new BastionHostResources(this, 'BastionHostResources', {
appSecurityGroup,
vpc: this.vpc,
securityGroup: appSecurityGroup,
rdsAddress: dbInstanceEndpointAddress,
});

const bastionHostUserData = UserData.forLinux({ shebang: '#cloud-config' });

bastionHostUserData.addCommands(socatForwarderString);

const cfnBastionHost = bastionHost.instance.node.defaultChild as CfnInstance;

cfnBastionHost.addPropertyOverride('UserData', Fn.base64(bastionHostUserData.render()));
}
}
76 changes: 76 additions & 0 deletions src/constructs/internal/alb/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// import { Stack } from 'aws-cdk-lib';
import { Duration } from 'aws-cdk-lib';
import { IVpc, SecurityGroup, SubnetType } from 'aws-cdk-lib/aws-ec2';
import { ApplicationListener, ApplicationLoadBalancer, ApplicationProtocol, ApplicationTargetGroup, ListenerAction, ListenerCertificate, TargetType } from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import { Construct } from 'constructs';


interface AlbResourcesProps {
readonly vpc: IVpc;
readonly albSecurityGroup: SecurityGroup;
readonly certificateArn: string;
}

export class AlbResources extends Construct {
// public rdsInstance: DatabaseInstance;
public readonly alb: ApplicationLoadBalancer;
public readonly listener: ApplicationListener;

constructor(scope: Construct, id: string, props: AlbResourcesProps) {
super(scope, id);

const loadBalancer = new ApplicationLoadBalancer(scope, 'LoadBalancer', {
vpc: props.vpc,
securityGroup: props.albSecurityGroup,
internetFacing: true,
vpcSubnets: {
subnetType: SubnetType.PUBLIC,
},
});
this.alb = loadBalancer;

// application target group
// Target group with duration-based stickiness with load-balancer generated cookie
// const defaultTargetGroup =
new ApplicationTargetGroup(this, 'default-target-group', {
targetType: TargetType.INSTANCE,
port: 80,
stickinessCookieDuration: Duration.minutes(5),
vpc: props.vpc,
healthCheck: {
path: '/api/health-check/', // TODO parametrize this
interval: Duration.minutes(5),
timeout: Duration.minutes(2),
healthyThresholdCount: 2,
unhealthyThresholdCount: 3,
port: '80', // TODO parametrize this
},
});

// listener - HTTP
new ApplicationListener(this, 'http-listener', {
loadBalancer: loadBalancer,
port: 80,
protocol: ApplicationProtocol.HTTP,
defaultAction: ListenerAction.redirect({
protocol: ApplicationProtocol.HTTPS,
port: '443',
permanent: false,
}),
});

// listener - HTTPS
const httpsListener = new ApplicationListener(this, 'https-listener', {
loadBalancer: loadBalancer,
port: 443,
protocol: ApplicationProtocol.HTTPS,
certificates: [ListenerCertificate.fromArn(props.certificateArn)],
defaultAction: ListenerAction.fixedResponse(200, {
contentType: 'text/plain',
messageBody: 'Fixed content response',
}),
});
this.listener = httpsListener;

}
}
Loading

0 comments on commit 46d7a9a

Please sign in to comment.