Skip to content

Commit

Permalink
Update split the CDK stacks into back and front end
Browse files Browse the repository at this point in the history
  • Loading branch information
Donal Stewart committed Jan 10, 2021
1 parent 18a2db9 commit 7f9c544
Show file tree
Hide file tree
Showing 8 changed files with 3,898 additions and 1,047 deletions.
29 changes: 2 additions & 27 deletions cdk-stacks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,9 @@

This project handles the CDK creation of backend resources and frontend deployments of the Admin and Survey React applications.

## Prerequisites

- Register or use an existing AWS account
- Install the [AWS CDK](https://docs.aws.amazon.com/cdk/index.html), described here: https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html#getting_started_install

## Useful commands

- `cdk deploy [options]` deploy this stack to your default AWS account/region
- `cdk diff [options]` compare deployed stack with current state
- `cdk synth [options]` emits the synthesized CloudFormation template

## Command options
## Building and deploying

- `--profile PROFILENAME` (optional) deploy to the named AWS profile
- `--context env=ENVIRONMENT_NAME` (required) build the named environment (`dev`, `test`, `live`, etc)
See the [monorepo build and deploy instructions](../README.md)

## AWS Deployment architecture

Expand All @@ -34,16 +22,3 @@ The LTL Survey consists of:
- CloudFront distributions for each of the web interfaces S3 buckets

The DynamoDB table, the photos S3 bucket and the two user pools are set to retain on delete, with fixed resource names to avoid a CloudFormation update replacing them (as their contents are not easily replaced).

## Building and deploying

The web clients are held in [adminclient](../adminclient) and [surveyclient](../surveyclient). Note that the cdk project will need to be deployed at least once to obtain the correct environment property values used by the web clients for each deployment environment. To build and deploy for a particular environment for the first time:

1. create a production build of the [sharedmodel](../sharedmodel) (`npm install; npm run build`)
1. create production builds of the two web clients (`npm install; npm run build`)
1. run `cdk deploy` to the correct environment (as described in the command options above)
1. use the deploy output values to create an environment specific version of the web clients (by populating `.env.[ENVIRONMENT]` files in the web client directories)
1. recreate production builds of the web clients (`npm run build`)
1. finally repeat `cdk deploy` to deploy the web clients correctly wired to the backend.

Subsequent builds only require steps 5 and 6 (but remember to rebuild the clients prior to cdk deploying to a different environment to avoid deploying clients connected to the wrong backend).
26 changes: 20 additions & 6 deletions cdk-stacks/bin/cdk-stacks.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,32 @@

const cdk = require("@aws-cdk/core");
const { CdkBackendStack } = require("../lib/cdk-backend-stack");
const { CdkFrontendStack } = require("../lib/cdk-frontend-stack");

const app = new cdk.App();
const envStageName = app.node.tryGetContext("env");
const resourcePrefixName = app.node.tryGetContext("nameprefix");

if (!envStageName) {
if (!envStageName || !resourcePrefixName) {
throw new Error(
"run with parameter --context env=ENVIRONMENT_NAME (i.e. dev, test, live, etc.)"
"run with parameters --context nameprefix=AWS_RESOURCE_NAME_PREFIX --context env=ENVIRONMENT_NAME (i.e. dev, test, live, etc.)"
);
}

const stack = new CdkBackendStack(app, "LTLSurvey-" + envStageName, {
envStageName,
});
const backendStack = new CdkBackendStack(
app,
resourcePrefixName + "-Backend-" + envStageName,
{
environment: envStageName,
resourcePrefix: resourcePrefixName + "-" + envStageName,
}
);

cdk.Tags.of(stack).add("DeployEnvironment", envStageName);
const frontendStack = new CdkFrontendStack(
app,
resourcePrefixName + "-Frontend-" + envStageName,
{}
);

cdk.Tags.of(backendStack).add("DeployEnvironment", envStageName);
cdk.Tags.of(frontendStack).add("DeployEnvironment", envStageName);
2 changes: 1 addition & 1 deletion cdk-stacks/cdk.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"app": "node bin/cdk-backend.js",
"app": "node bin/cdk-stacks.js",
"context": {
"@aws-cdk/core:enableStackNameDuplicates": "true",
"aws-cdk:enableDiffNoFail": "true",
Expand Down
72 changes: 13 additions & 59 deletions cdk-stacks/lib/cdk-backend-stack.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ const lambda = require("@aws-cdk/aws-lambda");
const apigateway = require("@aws-cdk/aws-apigateway");
const cognito = require("@aws-cdk/aws-cognito");
const iam = require("@aws-cdk/aws-iam");
const cloudfront = require("@aws-cdk/aws-cloudfront");
const origins = require("@aws-cdk/aws-cloudfront-origins");
const s3deploy = require("@aws-cdk/aws-s3-deployment");

class CdkBackendStack extends cdk.Stack {
/**
Expand All @@ -23,18 +20,18 @@ class CdkBackendStack extends cdk.Stack {
// Common resources
const stack = cdk.Stack.of(this);
const region = stack.region;
const { envStageName } = props;
const { environment, resourcePrefix } = props;

//provisionedthroughput - cost of lower
//perhaps remove s3 abort

// The following are fixed resource names with environment stage suffixes
// These resources will not be replaced during updates
const SURVEY_RESOURCES_BUCKET_NAME =
stackId.toLowerCase() + "-surveyresources";
const SURVEY_RESPONSES_TABLE_NAME = stackId + "-SurveyResponses";
const SURVEY_CLIENT_USERPOOL_ID = stackId + "-SurveyUserPool";
const SURVEY_ADMIN_USERPOOL_ID = stackId + "-SurveyAdminPool";
resourcePrefix.toLowerCase() + "-surveyresources";
const SURVEY_RESPONSES_TABLE_NAME = resourcePrefix + "-SurveyResponses";
const SURVEY_CLIENT_USERPOOL_ID = resourcePrefix + "-SurveyUserPool";
const SURVEY_ADMIN_USERPOOL_ID = resourcePrefix + "-SurveyAdminPool";

const surveyResourcesBucket = new Bucket(this, "SurveyResources", {
bucketName: SURVEY_RESOURCES_BUCKET_NAME,
Expand Down Expand Up @@ -81,7 +78,8 @@ class CdkBackendStack extends cdk.Stack {
"uploadState",
"responderEmail",
],
readCapacity: 10,
// Only increase RTU for live site
readCapacity: environment === "live" ? 10 : 5,
});
new cdk.CfnOutput(this, "SurveyResponseSummaries index", {
value: "SummaryIndex",
Expand All @@ -91,7 +89,7 @@ class CdkBackendStack extends cdk.Stack {
// Survey client resources

const restApi = new apigateway.RestApi(this, "SurveyClientApi", {
restApiName: "LTL Survey Client Service (" + envStageName + ")",
restApiName: "LTL Survey Client Service (" + environment + ")",
description: "This service receives LTL Audit Survey reponses.",
defaultCorsPreflightOptions: {
allowOrigins: apigateway.Cors.ALL_ORIGINS,
Expand Down Expand Up @@ -167,6 +165,11 @@ class CdkBackendStack extends cdk.Stack {
SURVEY_RESOURCES_BUCKET: surveyResourcesBucket.bucketName,
},
timeout: cdk.Duration.seconds(30),
commandHooks: {
beforeBundling(inputDir) {
return [`cd ${inputDir} && npm install`];
},
},
});

surveyResponsesTable.grant(addSurveyLambda, "dynamodb:PutItem");
Expand Down Expand Up @@ -307,16 +310,6 @@ class CdkBackendStack extends cdk.Stack {
}
);

// adminClientAuthenticatedRole.role.addToPolicy(
// // IAM policy granting users permission to a specific folder in the S3 bucket
// new iam.PolicyStatement({
// actions: ["s3:*"],
// effect: iam.Effect.ALLOW,
// resources: [
// bucketArn + "/private/${cognito-identity.amazonaws.com:sub}/*",
// ],
// })
// );
surveyResponsesTable.grant(
adminClientAuthenticatedRole,
"dynamodb:BatchGetItem",
Expand All @@ -326,45 +319,6 @@ class CdkBackendStack extends cdk.Stack {

surveyResourcesBucket.grantRead(adminClientAuthenticatedRole); // TODO restrict to object move?

// React website hosting - survey client
addHostedWebsite(this, "SurveyWebClient", "../surveyclient/build");

// React website hosting - admin client
addHostedWebsite(this, "AdminWebClient", "../adminclient/build");

function addHostedWebsite(scope, name, pathToWebsiteContents) {
const BUCKET_NAME = name;
const DISTRIBUTION_NAME = name + "Distribution";
const DEPLOY_NAME = name + "DeployWithInvalidation";

const bucket = new Bucket(scope, BUCKET_NAME, {});

const distribution = new cloudfront.Distribution(
scope,
DISTRIBUTION_NAME,
{
defaultBehavior: {
origin: new origins.S3Origin(bucket),
allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL,
viewerProtocolPolicy:
cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
},
defaultRootObject: "index.html",
}
);

new s3deploy.BucketDeployment(scope, DEPLOY_NAME, {
sources: [s3deploy.Source.asset(pathToWebsiteContents)],
destinationBucket: bucket,
distribution,
});

new cdk.CfnOutput(scope, name + " URL", {
value: "https://" + distribution.domainName,
description: "External URL for " + name + " website",
});
}

function addApiGatewayMethod(
restApi,
resourcePath,
Expand Down
Loading

0 comments on commit 7f9c544

Please sign in to comment.