Skip to content

Commit 39650fa

Browse files
pinzonsimonrw
andauthored
Implement CDK deployment (#18)
* implement backent into cdk * Initial app deploys * Set up top level package.json for cdk/cdklocal * Move cdk into subdir * Subscribe scoring function to submission queue * Add APIGW * Format file * Set up sqs queue and dlq * Set up verified email and sns subscription * Implement pipe for sqs -> sns * Include step function * Start to build website deploy custom resource * Implement permissions from manual JSON files * Add deploy cdk script * Add CORS handling to lambdas * Add cors handling to rest api * Fix bugs after deployment * Replace previous policy json files They are used for the integration tests * Perform first build if doesn't exist * Use CDK_CMD in deploy script * Update README with CDK instructions * Add AWS deployment instructions --------- Co-authored-by: Simon Walker <[email protected]> Co-authored-by: Simon Walker <[email protected]>
1 parent cb2f9f9 commit 39650fa

File tree

24 files changed

+693
-3
lines changed

24 files changed

+693
-3
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ __pycache__
2121
# LocalStack
2222

2323
volume/
24+
.idea

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ EXTRA_CORS_ALLOWED_ORIGINS='*' DISABLE_CUSTOM_CORS_APIGATEWAY=1 DISABLE_CUSTOM_C
4646
If you run into specific CORS issues, disable it using a [browser extension](https://webextension.org/listing/access-control.html).
4747

4848
## Local Deployment
49+
### awslocal
4950

5051
To deploy the app locally, run the following command:
5152

@@ -61,6 +62,22 @@ API Gateway Endpoint: http://localhost:4566/_aws/execute-api/4xu5emxibf/test
6162

6263
Navigate to the CloudFront URL to check out the app. The script would also seed some quiz data and user data to make local testing easier.
6364

65+
### cdk
66+
67+
To deploy the application to AWS, ensure your account is bootstraped via `cdk bootstrap` and then run
68+
69+
```bash
70+
AWS_CMD=aws CDK_CMD=cdk bash ./bin/deploy_cdk.sh
71+
```
72+
73+
### cdklocal
74+
75+
Alternatively the application can be deployed to LocalStack via `cdklocal`, our wrapper around the AWS CDK. Perform the following steps:
76+
1. Bootstrap LocalStack: `cd cdk && cdklocal bootstrap`
77+
2. Deploy the application: `AWS_CMD=awslocal CDK_CMD=cdklocal bash ./bin/deploy_cdk.sh`
78+
79+
_Note: while the core quiz application works with CDK, additional features have not been implemented yet._
80+
6481
## Local Testing
6582

6683
To run an automated test suite against the local deployment, run the following command:

bin/deploy_cdk.sh

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
AWS_CMD=${AWS_CMD:-aws}
6+
CDK_CMD=${CDK_CMD:-cdk}
7+
8+
# stub build the frontend code since the CDK stack needs this code to
9+
# synthesise the FrontendStack, but we don't yet know the backend URL to inject
10+
# into the static HTML
11+
if [ ! -d frontend/build ]; then
12+
(cd frontend
13+
echo "REACT_APP_API_ENDPOINT=https://example.com" > .env.local
14+
npx react-scripts build
15+
)
16+
fi
17+
18+
# deploy bulk of the application
19+
(cd cdk
20+
npm run ${CDK_CMD} -- deploy --require-approval never QuizAppStack
21+
)
22+
23+
# get the backend API url
24+
API_URL=$($AWS_CMD cloudformation describe-stacks --stack-name QuizAppStack --query Stacks[0].Outputs[0].OutputValue --output text)
25+
echo "Backend API URL: $API_URL"
26+
27+
# build the frontend code
28+
(cd frontend
29+
echo "REACT_APP_API_ENDPOINT=$API_URL" > .env.local
30+
npx react-scripts build
31+
)
32+
33+
# deploy the frontend stack
34+
(cd cdk
35+
npm run ${CDK_CMD} -- deploy --require-approval never FrontendStack
36+
)

cdk/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
cdk.out

cdk/app.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env python3
2+
import os
3+
4+
import aws_cdk as cdk
5+
6+
from quiz_app.frontend_stack import FrontendStack
7+
from quiz_app.quiz_app_stack import QuizAppStack
8+
9+
10+
app = cdk.App()
11+
QuizAppStack(app, "QuizAppStack",
12+
# If you don't specify 'env', this stack will be environment-agnostic.
13+
# Account/Region-dependent features and context lookups will not work,
14+
# but a single synthesized template can be deployed anywhere.
15+
16+
# Uncomment the next line to specialize this stack for the AWS Account
17+
# and Region that are implied by the current CLI configuration.
18+
19+
#env=cdk.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'), region=os.getenv('CDK_DEFAULT_REGION')),
20+
21+
# Uncomment the next line if you know exactly what Account and Region you
22+
# want to deploy the stack to. */
23+
24+
#env=cdk.Environment(account='123456789012', region='us-east-1'),
25+
26+
# For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html
27+
)
28+
29+
FrontendStack(app, "FrontendStack")
30+
31+
app.synth()

cdk/cdk.json

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"app": "python3 app.py",
3+
"watch": {
4+
"include": [
5+
"**"
6+
],
7+
"exclude": [
8+
"README.md",
9+
"cdk*.json",
10+
"requirements*.txt",
11+
"source.bat",
12+
"**/__init__.py",
13+
"**/__pycache__",
14+
"tests"
15+
]
16+
},
17+
"context": {
18+
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
19+
"@aws-cdk/core:checkSecretUsage": true,
20+
"@aws-cdk/core:target-partitions": [
21+
"aws",
22+
"aws-cn"
23+
],
24+
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
25+
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
26+
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
27+
"@aws-cdk/aws-iam:minimizePolicies": true,
28+
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
29+
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
30+
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
31+
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
32+
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
33+
"@aws-cdk/core:enablePartitionLiterals": true,
34+
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
35+
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
36+
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
37+
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
38+
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
39+
"@aws-cdk/aws-route53-patters:useCertificate": true,
40+
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
41+
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
42+
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
43+
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
44+
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
45+
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
46+
"@aws-cdk/aws-redshift:columnId": true,
47+
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
48+
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
49+
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
50+
"@aws-cdk/aws-kms:aliasNameRef": true,
51+
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
52+
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
53+
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
54+
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
55+
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
56+
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
57+
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
58+
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
59+
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
60+
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
61+
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
62+
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
63+
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
64+
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true
65+
}
66+
}

cdk/package-lock.json

Lines changed: 67 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cdk/package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "serverless-quiz-app",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"cdk": "cdk",
8+
"cdklocal": "cdklocal"
9+
},
10+
"keywords": [],
11+
"author": "",
12+
"license": "ISC",
13+
"dependencies": {
14+
"aws-cdk": "^2.171.0",
15+
"aws-cdk-local": "^2.19.0"
16+
}
17+
}

cdk/quiz_app/__init__.py

Whitespace-only changes.

cdk/quiz_app/frontend_stack.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import os
2+
3+
import aws_cdk
4+
from aws_cdk import (
5+
Stack,
6+
aws_s3 as s3,
7+
aws_cloudfront as cf,
8+
aws_cloudfront_origins as origins,
9+
aws_s3_deployment as s3deploy,
10+
CfnOutput,
11+
)
12+
from constructs import Construct
13+
14+
15+
class FrontendStack(Stack):
16+
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
17+
super().__init__(scope, construct_id, **kwargs)
18+
19+
webapp_bucket = s3.Bucket(
20+
self,
21+
"WebAppBucket",
22+
auto_delete_objects=True,
23+
removal_policy=aws_cdk.RemovalPolicy.DESTROY,
24+
)
25+
origin_access_identity = cf.OriginAccessIdentity(self, "OriginAccessIdentity")
26+
webapp_bucket.grant_read(origin_access_identity)
27+
28+
# deploy process
29+
distribution = cf.Distribution(
30+
self,
31+
"FrontendDistribution",
32+
default_root_object="index.html",
33+
default_behavior=cf.BehaviorOptions(
34+
origin=origins.S3Origin(
35+
webapp_bucket,
36+
origin_access_identity=origin_access_identity,
37+
),
38+
viewer_protocol_policy=cf.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
39+
),
40+
)
41+
42+
s3deploy.BucketDeployment(
43+
self,
44+
"DeployApp",
45+
sources=[
46+
s3deploy.Source.asset(
47+
os.path.join(
48+
os.path.dirname(__file__), "..", "..", "frontend", "build"
49+
)
50+
),
51+
],
52+
destination_bucket=webapp_bucket,
53+
distribution=distribution,
54+
distribution_paths=["/*"],
55+
)
56+
57+
CfnOutput(self, "DistributionDomainName", value=distribution.domain_name)
58+

0 commit comments

Comments
 (0)