Skip to content

Commit

Permalink
Implement CDK deployment (#18)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
3 people authored Nov 29, 2024
1 parent cb2f9f9 commit 39650fa
Show file tree
Hide file tree
Showing 24 changed files with 693 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ __pycache__
# LocalStack

volume/
.idea
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ EXTRA_CORS_ALLOWED_ORIGINS='*' DISABLE_CUSTOM_CORS_APIGATEWAY=1 DISABLE_CUSTOM_C
If you run into specific CORS issues, disable it using a [browser extension](https://webextension.org/listing/access-control.html).

## Local Deployment
### awslocal

To deploy the app locally, run the following command:

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

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.

### cdk

To deploy the application to AWS, ensure your account is bootstraped via `cdk bootstrap` and then run

```bash
AWS_CMD=aws CDK_CMD=cdk bash ./bin/deploy_cdk.sh
```

### cdklocal

Alternatively the application can be deployed to LocalStack via `cdklocal`, our wrapper around the AWS CDK. Perform the following steps:
1. Bootstrap LocalStack: `cd cdk && cdklocal bootstrap`
2. Deploy the application: `AWS_CMD=awslocal CDK_CMD=cdklocal bash ./bin/deploy_cdk.sh`

_Note: while the core quiz application works with CDK, additional features have not been implemented yet._

## Local Testing

To run an automated test suite against the local deployment, run the following command:
Expand Down
36 changes: 36 additions & 0 deletions bin/deploy_cdk.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env bash

set -euo pipefail

AWS_CMD=${AWS_CMD:-aws}
CDK_CMD=${CDK_CMD:-cdk}

# stub build the frontend code since the CDK stack needs this code to
# synthesise the FrontendStack, but we don't yet know the backend URL to inject
# into the static HTML
if [ ! -d frontend/build ]; then
(cd frontend
echo "REACT_APP_API_ENDPOINT=https://example.com" > .env.local
npx react-scripts build
)
fi

# deploy bulk of the application
(cd cdk
npm run ${CDK_CMD} -- deploy --require-approval never QuizAppStack
)

# get the backend API url
API_URL=$($AWS_CMD cloudformation describe-stacks --stack-name QuizAppStack --query Stacks[0].Outputs[0].OutputValue --output text)
echo "Backend API URL: $API_URL"

# build the frontend code
(cd frontend
echo "REACT_APP_API_ENDPOINT=$API_URL" > .env.local
npx react-scripts build
)

# deploy the frontend stack
(cd cdk
npm run ${CDK_CMD} -- deploy --require-approval never FrontendStack
)
2 changes: 2 additions & 0 deletions cdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
cdk.out
31 changes: 31 additions & 0 deletions cdk/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env python3
import os

import aws_cdk as cdk

from quiz_app.frontend_stack import FrontendStack
from quiz_app.quiz_app_stack import QuizAppStack


app = cdk.App()
QuizAppStack(app, "QuizAppStack",
# If you don't specify 'env', this stack will be environment-agnostic.
# Account/Region-dependent features and context lookups will not work,
# but a single synthesized template can be deployed anywhere.

# Uncomment the next line to specialize this stack for the AWS Account
# and Region that are implied by the current CLI configuration.

#env=cdk.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'), region=os.getenv('CDK_DEFAULT_REGION')),

# Uncomment the next line if you know exactly what Account and Region you
# want to deploy the stack to. */

#env=cdk.Environment(account='123456789012', region='us-east-1'),

# For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html
)

FrontendStack(app, "FrontendStack")

app.synth()
66 changes: 66 additions & 0 deletions cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"app": "python3 app.py",
"watch": {
"include": [
"**"
],
"exclude": [
"README.md",
"cdk*.json",
"requirements*.txt",
"source.bat",
"**/__init__.py",
"**/__pycache__",
"tests"
]
},
"context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": [
"aws",
"aws-cn"
],
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
"@aws-cdk/core:enablePartitionLiterals": true,
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
"@aws-cdk/aws-route53-patters:useCertificate": true,
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
"@aws-cdk/aws-redshift:columnId": true,
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
"@aws-cdk/aws-kms:aliasNameRef": true,
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true
}
}
67 changes: 67 additions & 0 deletions cdk/package-lock.json

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

17 changes: 17 additions & 0 deletions cdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "serverless-quiz-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"cdk": "cdk",
"cdklocal": "cdklocal"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"aws-cdk": "^2.171.0",
"aws-cdk-local": "^2.19.0"
}
}
Empty file added cdk/quiz_app/__init__.py
Empty file.
58 changes: 58 additions & 0 deletions cdk/quiz_app/frontend_stack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import os

import aws_cdk
from aws_cdk import (
Stack,
aws_s3 as s3,
aws_cloudfront as cf,
aws_cloudfront_origins as origins,
aws_s3_deployment as s3deploy,
CfnOutput,
)
from constructs import Construct


class FrontendStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)

webapp_bucket = s3.Bucket(
self,
"WebAppBucket",
auto_delete_objects=True,
removal_policy=aws_cdk.RemovalPolicy.DESTROY,
)
origin_access_identity = cf.OriginAccessIdentity(self, "OriginAccessIdentity")
webapp_bucket.grant_read(origin_access_identity)

# deploy process
distribution = cf.Distribution(
self,
"FrontendDistribution",
default_root_object="index.html",
default_behavior=cf.BehaviorOptions(
origin=origins.S3Origin(
webapp_bucket,
origin_access_identity=origin_access_identity,
),
viewer_protocol_policy=cf.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
),
)

s3deploy.BucketDeployment(
self,
"DeployApp",
sources=[
s3deploy.Source.asset(
os.path.join(
os.path.dirname(__file__), "..", "..", "frontend", "build"
)
),
],
destination_bucket=webapp_bucket,
distribution=distribution,
distribution_paths=["/*"],
)

CfnOutput(self, "DistributionDomainName", value=distribution.domain_name)

Loading

0 comments on commit 39650fa

Please sign in to comment.