Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
matsev committed Aug 17, 2016
0 parents commit 889de92
Show file tree
Hide file tree
Showing 9 changed files with 380 additions and 0 deletions.
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# IntelliJ
*.iml
*.ipr
*.iws
.idea/

# OS X
.DS_Store

node_modules
target

xunit-*.xml
npm-debug.log
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Introduction to CloudFormation for API Gateway

Sample project that shows how [AWS CloudFormation](https://aws.amazon.com/cloudformation/) can be
used to create an [AWS API Gateway](https://aws.amazon.com/api-gateway/) backed by [AWS Lambda](https://aws.amazon.com/lambda/).

Please read the [blog post](https://www.jayway.com/2016/08/17/introduction-to-cloudformation-for-api-gateway/) for details.

## CloudFormation Template

[cloudformation.template](cloudformation.template)


## Tools

In order to execute the scripts, you need to install the following tools:

- [AWS CLI](https://aws.amazon.com/cli/) (AWS Command Line Interface)
- [npm](https://www.npmjs.com/) (JavaScript package manager)


## Scripts

| Script | Description |
| ------------------------------------------------------------- | -------------------------- |
| [create-stack.sh](scripts/create-stack.sh) | Creates the stack |
| [integration-test.sh](scripts/integration-test.sh) | Executes integration tests |
| [update-stack.sh](scripts/update-stack.sh) | Updates the stack |
180 changes: 180 additions & 0 deletions cloudformation.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "AWS CloudFormation sample template that contains a single Lambda function behind an API Gateway",

"Resources": {

"GreetingLambda": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"ZipFile": { "Fn::Join": ["\n", [
"'use strict';",
"",
"// Greeter Lambda",
"exports.handler = (event, context, callback) => {",
" console.log('Event:', JSON.stringify(event));",
" const name = event.name || 'World';",
" const response = {greeting: `Hello, ${name}!`};",
" callback(null, response);",
"};"
]]}
},
"Description": "A greeting function",
"FunctionName": "GreetingLambda",
"Handler": "index.handler",
"Role": { "Fn::GetAtt": ["LambdaExecutionRole", "Arn"]},
"Runtime": "nodejs4.3"
}
},

"LambdaExecutionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": ["lambda.amazonaws.com"] },
"Action": ["sts:AssumeRole"]
}]
},
"ManagedPolicyArns": ["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"]
}
},

"GreetingApi": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {
"Name": "Greeting API",
"Description": "API used for Greeting requests",
"FailOnWarnings": true
}
},

"LambdaPermission": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:invokeFunction",
"FunctionName": {"Fn::GetAtt": ["GreetingLambda", "Arn"]},
"Principal": "apigateway.amazonaws.com",
"SourceArn": {"Fn::Join": ["", ["arn:aws:execute-api:", {"Ref": "AWS::Region"}, ":", {"Ref": "AWS::AccountId"}, ":", {"Ref": "GreetingApi"}, "/*"]]}
}
},

"ApiGatewayCloudWatchLogsRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": ["apigateway.amazonaws.com"] },
"Action": ["sts:AssumeRole"]
}]
},
"Policies": [{
"PolicyName": "ApiGatewayLogsPolicy",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"logs:PutLogEvents",
"logs:GetLogEvents",
"logs:FilterLogEvents"
],
"Resource": "*"
}]
}
}]
}
},

"ApiGatewayAccount": {
"Type": "AWS::ApiGateway::Account",
"Properties": {
"CloudWatchRoleArn": {"Fn::GetAtt": ["ApiGatewayCloudWatchLogsRole", "Arn"] }
}
},

"GreetingApiStage": {
"DependsOn": ["ApiGatewayAccount"],
"Type": "AWS::ApiGateway::Stage",
"Properties": {
"DeploymentId": {"Ref": "ApiDeployment"},
"MethodSettings": [{
"DataTraceEnabled": true,
"HttpMethod": "*",
"LoggingLevel": "INFO",
"ResourcePath": "/*"
}],
"RestApiId": {"Ref": "GreetingApi"},
"StageName": "LATEST"
}
},

"ApiDeployment": {
"Type": "AWS::ApiGateway::Deployment",
"DependsOn": ["GreetingRequest"],
"Properties": {
"RestApiId": {"Ref": "GreetingApi"},
"StageName": "DummyStage"
}
},

"GreetingResource": {
"Type": "AWS::ApiGateway::Resource",
"Properties": {
"RestApiId": {"Ref": "GreetingApi"},
"ParentId": {"Fn::GetAtt": ["GreetingApi", "RootResourceId"]},
"PathPart": "greeting"
}
},

"GreetingRequest": {
"DependsOn": "LambdaPermission",
"Type": "AWS::ApiGateway::Method",
"Properties": {
"AuthorizationType": "NONE",
"HttpMethod": "GET",
"Integration": {
"Type": "AWS",
"IntegrationHttpMethod": "POST",
"Uri": {"Fn::Join": ["",
["arn:aws:apigateway:", {"Ref": "AWS::Region"}, ":lambda:path/2015-03-31/functions/", {"Fn::GetAtt": ["GreetingLambda", "Arn"]}, "/invocations"]
]},
"IntegrationResponses": [{
"StatusCode": 200
}],
"RequestTemplates": {
"application/json": {"Fn::Join": ["", [
"{",
" \"name\": \"$input.params('name')\"",
"}"
]]}
}
},
"RequestParameters": {
"method.request.querystring.name": false
},
"ResourceId": {"Ref": "GreetingResource"},
"RestApiId": {"Ref": "GreetingApi"},
"MethodResponses": [{
"StatusCode": 200
}]
}
}
},

"Outputs": {
"RootUrl": {
"Description": "Root URL of the API gateway",
"Value": {"Fn::Join": ["", ["https://", {"Ref": "GreetingApi"}, ".execute-api.", {"Ref": "AWS::Region"}, ".amazonaws.com"]]}
}
}
}
44 changes: 44 additions & 0 deletions integration-test/greeting-lambda-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use strict';

const expect = require('chai').expect;
const AWS = require('aws-sdk');
const AWS_REGION = process.env.AWS_DEFAULT_REGION || 'eu-west-1';
AWS.config.update({region: AWS_REGION});
const lambda = new AWS.Lambda({apiVersion: '2015-03-31'});

function callGreeterLambda(name) {
const params = {
FunctionName: 'GreetingLambda'
};
if (name) {
params.Payload = JSON.stringify({name: name});
}
return lambda.invoke(params).promise();
}


describe('Greeting Lambda integration tests', () => {

it('Returns the name if it is provided', done => {
callGreeterLambda('Superman')
.then(data => {
expect(data.StatusCode).to.eql(200);
const payload = JSON.parse(data.Payload);
expect(payload.greeting).to.eql('Hello, Superman!');
done();
})
.catch(done);
});

it('Defaults to Hello World if no name is provided', done => {
callGreeterLambda()
.then(data => {
expect(data.StatusCode).to.eql(200);
const payload = JSON.parse(data.Payload);
expect(payload.greeting).to.eql('Hello, World!');
done();
})
.catch(done);
});

});
53 changes: 53 additions & 0 deletions integration-test/greeting-rest-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict';

const expect = require('chai').expect;
const querystring = require('querystring');
const url = require('url');
const requestPromise = require('request-promise');

const APP_ID = // insert your API Gateway app id here
const AWS_REGION = process.env.AWS_DEFAULT_REGION || 'eu-west-1';
const HOST = `${APP_ID}.execute-api.${AWS_REGION}.amazonaws.com`;
const STAGE = 'LATEST';
const RESOURCE = 'greeting';


function callGreeterEndpoint(name) {
const query = querystring.stringify({name: name});
const requestUrl = url.format({
protocol: 'https',
host: HOST,
pathname: `${STAGE}/${RESOURCE}`,
search: query
});
const params = {
method: 'GET',
uri: url.format(requestUrl),
};
return requestPromise(params);
}


describe('Greeting REST integration tests', () => {

it('Returns the name if it is provided', done => {
callGreeterEndpoint('Superman')
.then(json => {
const response = JSON.parse(json);
expect(response.greeting).to.eql('Hello, Superman!');
done();
})
.catch(done);
});

it('Defaults to Hello World if no name is provided', done => {
callGreeterEndpoint()
.then(json => {
const response = JSON.parse(json);
expect(response.greeting).to.eql('Hello, World!');
done();
})
.catch(done);
});

});
17 changes: 17 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "api-gateway-lambda",
"version": "1.0.0",
"private": true,
"scripts": {
"itest": "mocha --recursive integration-test",
"xunit-itest": "XUNIT_FILE=xunit-itest.xml mocha --recursive integration-test -R xunit-file"
},
"devDependencies": {
"aws-sdk": "2.4.6",
"chai": "3.5.0",
"mocha": "2.5.3",
"request": "2.74.0",
"request-promise": "4.1.1",
"xunit-file": "1.0.0"
}
}
17 changes: 17 additions & 0 deletions scripts/create-stack.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash

# Creates the stack

set -e

script_dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
template_file="$script_dir/../cloudformation.template"
stack_name=greeting-stack

if [ -z "$AWS_DEFAULT_REGION" ]; then
aws_region="eu-west-1"
else
aws_region=$AWS_DEFAULT_REGION
fi

aws cloudformation create-stack --stack-name $stack_name --template-body fileb://"$template_file" --capabilities CAPABILITY_IAM --region $aws_region
11 changes: 11 additions & 0 deletions scripts/integration-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash

# Executes integration tests against version $LATEST

set -e

script_dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
cd "$script_dir/.."

npm install
npm run xunit-itest
17 changes: 17 additions & 0 deletions scripts/update-stack.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash

# Creates the stack

set -e

script_dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
template_file="$script_dir/../cloudformation.template"
stack_name=greeting-stack

if [ -z "$AWS_DEFAULT_REGION" ]; then
aws_region="eu-west-1"
else
aws_region=$AWS_DEFAULT_REGION
fi

aws cloudformation update-stack --stack-name $stack_name --template-body fileb://"$template_file" --capabilities CAPABILITY_IAM --region $aws_region

0 comments on commit 889de92

Please sign in to comment.