Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Scott Winkler authored and Scott Winkler committed Dec 25, 2020
0 parents commit a98dc22
Show file tree
Hide file tree
Showing 50 changed files with 16,138 additions and 0 deletions.
35 changes: 35 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Local .terraform directories
**/.terraform/*

# .tfstate files
*.tfstate
*.tfstate.*

# Crash log files
crash.log
terraform
.DS_Store

# React app files
.idea/
.vscode/
node_modules/
build
.DS_Store
*.tgz
my-app*
template/src/__tests__/__snapshots__/
lerna-debug.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/.changelog
.npm/
yarn.lock

# project specific files
dist/public/
dist/deployment
src/server/deployment
src/client/build.zip
providers.tf
24 changes: 24 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Go parameters
BINARY_NAME=deployment
PACKAGE_NAME=function
all: clean deps build package
server-only: clean server-deps server-build package
clean:
cd src/server && go clean
rm -f ${PACKAGE_NAME}.zip
rm -rf dist
deps:
cd src/client && npm install
cd src/server && go get
build:
cd src/client && npm run build
cd src/server && GOOS=linux go build -o $(BINARY_NAME)
server-deps:
cd src/server && go get
server-build:
cd src/server && GOOS=linux go build -o $(BINARY_NAME)
package:
mkdir -p dist/public
cp -a src/client/build/* dist/public/
cp src/server/$(BINARY_NAME) dist/$(BINARY_NAME)
cd dist && zip -r ${PACKAGE_NAME}.zip public/* $(BINARY_NAME)
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Serverless-Example
This is a fully serverless app that runs on AWS and is deployed using terraform. The basic architecture is:
AWS Lambda (for the golang server)
AWS APIGateway (for proxying all requests to lambda)
AWS S3 (for hosting the static content for the react app)
AWS RDS (for storing data)

It is an extremely scalable solution -- it costs less than $0.20 per month to host in AWS, and can easily scale to tens of thousands of concurrent users with only minor configuration modifications.

hosted at http://sjw-example-dev.s3-website-us-west-2.amazonaws.com/
Binary file added dist/function.zip
Binary file not shown.
39 changes: 39 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
resource "random_string" "rand" {
length = 24
special = false
upper = false
}

locals {
namespace = substr(join("-", [var.namespace, random_string.rand.result]), 0, 24)
}

module "networking" {
source = "./modules/networking"
namespace = local.namespace
}

module "database" {
source = "./modules/database"
namespace = local.namespace
rds_user = var.rds_user
rds_password = var.rds_password
sg = module.networking.sg
}

module "lambda" {
source = "./modules/lambda"
namespace = local.namespace
rds_user = var.rds_user
rds_password = var.rds_password
rds_host = module.database.rds_host
rds_port = module.database.rds_port
rds_database = module.database.rds_database
sg = module.networking.sg
}

module "apigw" {
source = "./modules/apigw"
namespace = local.namespace
lambda_arn = module.lambda.lambda_arn
}
149 changes: 149 additions & 0 deletions modules/apigw/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
data "aws_region" "current" {
}

data "aws_caller_identity" "current" {
}

resource "aws_api_gateway_rest_api" "api" {
name = "${var.namespace}-api"
description = "This api proxies all request to a lambda handler"
}

#apigw role
resource "aws_api_gateway_account" "apigw_account" {
depends_on = [aws_iam_role_policy.cloudwatch]
cloudwatch_role_arn = aws_iam_role.cloudwatch.arn
}

resource "aws_iam_role" "cloudwatch" {
name = "api_gateway_cloudwatch_global-${var.namespace}"

assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}

resource "aws_iam_role_policy" "cloudwatch" {
name = "default"
role = aws_iam_role.cloudwatch.id

policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"logs:PutLogEvents",
"logs:GetLogEvents",
"logs:FilterLogEvents"
],
"Resource": "*"
}
]
}
EOF
}

resource "aws_lambda_permission" "lambda_permission_api_gateway" {
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = var.lambda_arn
principal = "apigateway.amazonaws.com"
source_arn = "arn:aws:execute-api:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.api.id}/*/*"
}

#GET / (redirects to /ui)
resource "aws_api_gateway_method" "redirect_method" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_rest_api.api.root_resource_id
http_method = "GET"
authorization = "NONE"
}

resource "aws_api_gateway_integration" "redirect_integration" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_rest_api.api.root_resource_id
http_method = aws_api_gateway_method.redirect_method.http_method
type = "MOCK"
request_templates = { "application/json" = <<-EOF
{
"statusCode" : 302
}
EOF
}
}

resource "aws_api_gateway_method_response" "redirect" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_rest_api.api.root_resource_id
http_method = aws_api_gateway_method.redirect_method.http_method

response_parameters = {
"method.response.header.Location" = true
}

status_code = "302"
}

resource "aws_api_gateway_integration_response" "redirect_integration_response" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_rest_api.api.root_resource_id
http_method = aws_api_gateway_method.redirect_method.http_method
status_code = aws_api_gateway_method_response.redirect.status_code

response_parameters = {
"method.response.header.Location" = "'/v1/ui'"
}
}

# /{proxy+}
resource "aws_api_gateway_resource" "proxy" {
rest_api_id = aws_api_gateway_rest_api.api.id
parent_id = aws_api_gateway_rest_api.api.root_resource_id
path_part = "{proxy+}"
}

#ANY /{proxy+}
resource "aws_api_gateway_method" "auth_any" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = "ANY"
authorization = "NONE"
}

resource "aws_api_gateway_integration" "any_method_integration" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = aws_api_gateway_method.auth_any.http_method
type = "AWS_PROXY"
uri = "arn:aws:apigateway:${data.aws_region.current.name}:lambda:path/2015-03-31/functions/${var.lambda_arn}/invocations"
integration_http_method = "POST"
}

resource "aws_api_gateway_deployment" "api_deployment" {
depends_on = [
aws_api_gateway_resource.proxy,
aws_api_gateway_method.auth_any,
aws_api_gateway_integration.any_method_integration,
]

rest_api_id = aws_api_gateway_rest_api.api.id
stage_name = "v1"
}
3 changes: 3 additions & 0 deletions modules/apigw/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "invoke_url" {
value = aws_api_gateway_deployment.api_deployment.invoke_url
}
8 changes: 8 additions & 0 deletions modules/apigw/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
variable "namespace" {
type = string
}

variable "lambda_arn" {
type = string
}

18 changes: 18 additions & 0 deletions modules/database/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
data "aws_region" "current" {}

resource "aws_rds_cluster" "rds_cluster" {
cluster_identifier = "${var.namespace}-aurora-cluster"
engine = "aurora"
availability_zones = ["${data.aws_region.current.name}a","${data.aws_region.current.name}b","${data.aws_region.current.name}c"]
database_name = "petstore"
master_username = var.rds_user
master_password = var.rds_password
backup_retention_period = 1
skip_final_snapshot = true
preferred_backup_window = "04:00-07:00"
engine_mode = "serverless"
scaling_configuration {
max_capacity = 1
min_capacity = 1
}
}
12 changes: 12 additions & 0 deletions modules/database/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
output "rds_host" {
value = aws_rds_cluster.rds_cluster.endpoint
}

output "rds_port" {
value = aws_rds_cluster.rds_cluster.port
}

output "rds_database" {
value = aws_rds_cluster.rds_cluster.database_name
}

15 changes: 15 additions & 0 deletions modules/database/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
variable "namespace" {
type = string
}

variable "rds_user" {
type = string
}

variable "rds_password" {
type = string
}

variable "sg" {
type = any
}
Loading

0 comments on commit a98dc22

Please sign in to comment.