Skip to content

Commit

Permalink
Add base AWS SAM set up for deploying to AWS Lambda
Browse files Browse the repository at this point in the history
This is mostly taken from the devs.DC API SAM set up
  • Loading branch information
symroe committed Feb 3, 2021
1 parent 6383c45 commit 23e93f4
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,8 @@ dmypy.json

# Ignore Djanog settings files
ec_api/settings/local.py


# Deployment related files
/.aws-sam/
/lambda-layers/DependenciesLayer/requirements.txt
24 changes: 24 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.DEFAULT_GOAL := help

export SECRET_KEY?=badf00d
export DJANGO_SETTINGS_MODULE?=ec_api.settings.base_lambda
export APP_IS_BEHIND_CLOUDFRONT?=False

.PHONY: all
all: clean collectstatic lambda-layers/DependenciesLayer/requirements.txt ## Rebuild everything this Makefile knows how to build

.PHONY: clean
clean: ## Delete any generated static asset or req.txt files and git-restore the rendered API documentation file
rm -rf ec_api/static_files/ lambda-layers/DependenciesLayer/requirements.txt

.PHONY: collectstatic
collectstatic: ## Rebuild the static assets
python manage.py collectstatic --noinput --clear

lambda-layers/DependenciesLayer/requirements.txt: Pipfile Pipfile.lock ## Update the requirements.txt file used to build this Lambda function's DependenciesLayer
pipenv lock -r | sed "s/^-e //" >lambda-layers/DependenciesLayer/requirements.txt

.PHONY: help
# gratuitously adapted from https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
help: ## Display this help text
@grep -E '^[-a-zA-Z0-9_/.]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%s\033[0m\n\t%s\n", $$1, $$2}'
106 changes: 106 additions & 0 deletions public-access-template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: "EC API public access: TLS, CDN, DNS"

Parameters:
StackNameSuffix:
Description: "The suffix (automatically prefixed with 'ECApi-') constructing the name of the CloudFormation Stack that created the API Gateway & Lambda function to which this Stack will attach TLS, CDN, and DNS."
Type: String

CertificateArn:
Type: String

PublicFqdn:
Type: String

Resources:

CloudFrontDistribution:
Type: 'AWS::CloudFront::Distribution'
Properties:
DistributionConfig:
Comment: 'Cloudfront Distribution pointing to Lambda origin'
Origins:

- Id: Static
DomainName:
Fn::ImportValue: !Sub "ECApiApp-${StackNameSuffix}:ECApiFqdn"
OriginPath: "/Prod"
CustomOriginConfig:
OriginProtocolPolicy: "https-only"
OriginCustomHeaders:
- HeaderName: X-Forwarded-Host
HeaderValue: !Ref PublicFqdn
- HeaderName: X-Forwarded-Proto
HeaderValue: https

OriginShield:
Enabled: true
OriginShieldRegion: eu-west-2

- Id: Dynamic
DomainName:
Fn::ImportValue: !Sub "ECApiApp-${StackNameSuffix}:ECApiFqdn"
OriginPath: "/Prod"
CustomOriginConfig:
OriginProtocolPolicy: "https-only"
OriginCustomHeaders:
- HeaderName: X-Forwarded-Host
HeaderValue: !Ref PublicFqdn
- HeaderName: X-Forwarded-Proto
HeaderValue: https

Enabled: true
HttpVersion: 'http2'
Aliases:
- !Ref PublicFqdn
PriceClass: "PriceClass_100"
ViewerCertificate:
AcmCertificateArn: !Ref CertificateArn
MinimumProtocolVersion: TLSv1.1_2016
SslSupportMethod: sni-only

DefaultCacheBehavior:
AllowedMethods: [ GET, HEAD, OPTIONS ]
TargetOriginId: Dynamic
ForwardedValues:
QueryString: true
Cookies:
Forward: "all"
Headers:
- Authorization
- Origin
ViewerProtocolPolicy: "redirect-to-https"

CacheBehaviors:
- AllowedMethods: [ GET, HEAD, OPTIONS ]
PathPattern: static/*
TargetOriginId: Static
ForwardedValues:
QueryString: true
Cookies:
Forward: none
Headers:
- Authorization
- Origin
ViewerProtocolPolicy: "redirect-to-https"
MinTTL: '50'


DnsRecord:
Type: AWS::Route53::RecordSet
Properties:
AliasTarget:
DNSName: !GetAtt CloudFrontDistribution.DomainName
HostedZoneId: Z2FDTNDATAQYW2 # this is an AWS-owned, global singleton required for Aliases to CloudFront
HostedZoneName: !Sub "${PublicFqdn}."
Name: !Sub "${PublicFqdn}."
Type: A

Outputs:
CloudFrontDistributionFqdn:
Description: "The FQDN of the CloudFront distribution serving this instance."
Value: !GetAtt CloudFrontDistribution.DomainName
PublicFqdn:
Description: "The EC API's URL."
Value: !Sub "https://${PublicFqdn}/"
1 change: 1 addition & 0 deletions samconfig.toml
47 changes: 47 additions & 0 deletions samconfig.toml.d/development.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# This file and its quirks are documented here:
# https://github.com/aws/aws-sam-cli/blob/develop/docs/sam-config-docs.md
version = 0.1

####################################################################################
## NB: Don't insert a "default" profile in this file! ##############################
###### Only use named, per-environment profiles. ###################################
###### This will help guard against accidentally targetting the wrong environment. #
####################################################################################

[SYM]

[SYM.deploy]
[SYM.deploy.parameters]
stack_name = "ECApiApp-sym-dev"
s3_bucket = "ec-api-deployment-artifacts-development-0342fgsd318"
s3_prefix = "sym-dev"
region = "eu-west-2"
confirm_changeset = false
capabilities = "CAPABILITY_IAM"
tags = 'dc-product="ec-api" dc-environment="development" dc-instance="sym-dev"'
# These parameter overrides are *not* merged with those provided directly to the
# `sam XXXX` CLI command: those provided at the CLI are the *only* ones used.
parameter_overrides = """
AppDjangoSettingsModule=ec_api.settings.base_lambda \
AppSecretKey=badf00d \
AppIsBehindCloudFront=False \
"""

[SYM.logs]
[SYM.logs.parameters]
stack_name = "ECApiApp-sym-dev"
name = "ECApiFunction"
region = "eu-west-2"

[SYM-public-access]
[SYM-public-access.deploy]
[SYM-public-access.deploy.parameters]
template = "public-access-template.yaml"
stack_name = "ECApiPublicAccess-sym-dev"
region = "eu-west-2"
capabilities "CAPABILITY_IAM"
parameter_overrides = """
StackNameSuffix="sym-dev" \
CertificateArn="arn:aws:acm:us-east-1:486957117838:certificate/26ca3576-14a4-452d-b567-b286d8287308" \
PublicFqdn="sym-dev.ec-dev.womblelabs.co.uk" \
"""
94 changes: 94 additions & 0 deletions template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: "EC API app: Lambda, API Gateway"

Globals:
Function:
Timeout: 10
Api:
BinaryMediaTypes:
- "*/*"
- "*/*"

Parameters:

AppSecretKey:
Description: "The SECRET_KEY environment variable passed to the app."
Type: String

AppDjangoSettingsModule:
# NB This parameter (and how it reaches the app, and how it's set in
# developer and CI-managed deployments) is used in
# `docs/new-development-deployment.md` as a reference to demonstrate how to
# communicate variables to the app. If you modify this parameter, or remove
# it, please update the document so developers aren't left without
# guidance!
Description: "The DJANGO_SETTINGS_MODULE environment variable passed to the app."
Type: String

AppIsBehindCloudFront:
Description: "The APP_IS_BEHIND_CLOUDFRONT environment variable passed to the app, which modifies various path- and host-related settings."
Type: String
AllowedValues:
- "True"
- "False"
Default: "False"

AppLogRetentionDays:
Description: "The number of days that CloudWatch Logs will keep logs from the app."
Type: Number
Default: 60
AllowedValues: [ 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653 ]

Resources:

DependenciesLayer:
Type: AWS::Serverless::LayerVersion
Properties:
ContentUri: ./lambda-layers/DependenciesLayer/
CompatibleRuntimes:
- python3.8
Metadata:
BuildMethod: python3.8
RetentionPolicy: Delete

ECApiFunction:
Type: AWS::Serverless::Function
Properties:
Role: !Sub "arn:aws:iam::${AWS::AccountId}:role/ECApiLambdaExecutionRole"
CodeUri: .
Handler: ec_api.lambda_awsgi.lambda_handler
Layers:
- !Ref DependenciesLayer
Runtime: python3.8
MemorySize: 192
Environment:
Variables:
SECRET_KEY: !Ref AppSecretKey
DJANGO_SETTINGS_MODULE: !Ref AppDjangoSettingsModule
APP_IS_BEHIND_CLOUDFRONT: !Ref AppIsBehindCloudFront
Events:
HTTPRequests:
Type: Api
Properties:
Path: /{proxy+}
Method: ANY
HTTPRequestRoots:
Type: Api
Properties:
Path: /
Method: ANY

ECApiFunctionLogGroup:
Type: AWS::Logs::LogGroup
DependsOn: [ ECApiFunction ]
Properties:
LogGroupName: !Sub /aws/lambda/${ECApiFunction}
RetentionInDays: !Ref AppLogRetentionDays

Outputs:
ECApiFqdn:
Description: "API Gateway endpoint FQDN for EC API function"
Value: !Sub "${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com"
Export:
Name: !Join [ ":", [ !Ref "AWS::StackName", "ECApiFqdn" ] ]

0 comments on commit 23e93f4

Please sign in to comment.