diff --git a/README.md b/README.md new file mode 100644 index 0000000..e25fb6b --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# untitled-serverless + +## Deploying to AWS Lambda & API Gateway + +The _serverless directory contains a serverless project (generated with sls project create _serverless), and the _serverless/_auto directory is the target for the automatically built artifacts. + +To deploy: + +```bash +node demo-app-prepare-deploy.js +cd _serverless +sls dash deploy +``` + + diff --git a/_serverless/.gitignore b/_serverless/.gitignore new file mode 100644 index 0000000..7af0b6e --- /dev/null +++ b/_serverless/.gitignore @@ -0,0 +1,43 @@ +# Logs +logs +*.log +npm-debug.log + +# Runtime data +pids +*.pid +*.seed +dist + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules + +#IDE +**/.idea + +#OS +.DS_Store +.tmp + +#SERVERLESS +admin.env +.env + +#Ignore _meta folder +_meta \ No newline at end of file diff --git a/_serverless/_auto/demo-app-posts/endpoint-logic.js b/_serverless/_auto/demo-app-posts/endpoint-logic.js new file mode 100644 index 0000000..8b68f29 --- /dev/null +++ b/_serverless/_auto/demo-app-posts/endpoint-logic.js @@ -0,0 +1,6 @@ +module.exports = { + index: function(req, res) { + res.status(200); + res.json({ message: "ohai"}); + } +}; diff --git a/_serverless/_auto/demo-app-posts/handler.js b/_serverless/_auto/demo-app-posts/handler.js new file mode 100644 index 0000000..bba5e78 --- /dev/null +++ b/_serverless/_auto/demo-app-posts/handler.js @@ -0,0 +1,12 @@ +const lambdaUtils = require('./lambda-utils'); + +const endpointLogic = require('./endpoint-logic'); + +module.exports.handler = function(event, context, cb) { + console.log('got event'); + console.log(JSON.stringify(event,null,'\t')); + var req = lambdaUtils.buildRequest(event,context,cb); + var res = lambdaUtils.buildResponse(event,context,cb); + endpointLogic.index(req, res); +}; + diff --git a/_serverless/_auto/demo-app-posts/lambda-utils.js b/_serverless/_auto/demo-app-posts/lambda-utils.js new file mode 100644 index 0000000..e9ce029 --- /dev/null +++ b/_serverless/_auto/demo-app-posts/lambda-utils.js @@ -0,0 +1,156 @@ +module.exports.buildRequest = function(event, context, cb) { + var req = {}; + + // http://expressjs.com/en/api.html#req + // Properties + req.app = null; // Not sure this applies in our context... + req.baseUrl = event.baseUrl; + req.body = event.body; + req.cookies = {}; // TODO : Figure out how to get cookies from AWS Gateway + req.fresh = true; // TODO : Does this apply in our context? + req.hostname = event.params.header.Host; + //req.ip = event.params.header["X-Forwarded-For"].split(", ")[0]; + req.ips = event.params.header["X-Forwarded-For"]; + req.method = event.method; + req.originalUrl = ""; // TODO : Figrure out how to reconstruct this + req.params = event.params; + req.path = event.baseUrl; // TODO : This isn't quite right... + req.protocol = event.params.header["X-Forwarded-Proto"]; + req.query = event.query; + req.route = null; // TODO : Does this apply to us? + req.secure = req.protocol === 'https'; + req.signedCookies = {}; // TODO : ??? + req.stale = false; // TODO : Does this apply in our context? + req.subdomains = []; // TODO: Does this apply? + req.xhr = event.params.header["X-Requested-With"] === 'XMLHttpRequest'; + + // Methods + /* + var accept = realAccepts(req); + req.accepts = function accepts(types){ + // TODO : Fix me! + return types; + //return accept.type(types); + }; + */ + req.acceptsCharsets = function acceptsCharsets(){ + // TODO : Fix me! + }; + + req.acceptsEncodings = function acceptsEncodings(){ + // TODO : Fix me! + }; + + req.acceptsLanguages = function acceptsLanguages(){ + // TODO : Fix me! + }; + + req.get = function get(field){ + return event.params.header[field]; + } + + req.is = function is(type){ + // TODO: Fix me! + } + + return req; +} + +module.exports.buildResponse = function(event, context, cb) { + var res = {}; + + // Properties + res.app = null; // TODO : Does this apply? + res.headersSent = false; // TODO : Is this right? + res.locals = {}; // TODO: Does this apply? + + res.headers = []; + + // Methods + res.append = function append(field,value){ + res.headers.push([field,value]); + } + + res.attachment = function attachment(){ + // TODO: Can we return attachments through API Gatewway? + } + + res.cookie = function cookie(name, value, options){ + // TODO: Can we set cookies? + } + + res.clearCookie = function clearCookie(name, options){ + // TODO: Can we set cookies? + } + + res.download = function download(){ + // TODO: Can we do this? + } + + res.end = function end(){ + // TODO: Implement me! + } + + res.format = function format(){ + // TODO: does this apply? + } + + res.get = function get(field){ + return headers[field]; + } + + res.json = function json(payload){ + cb(null, payload); + } + + res.jsonp = function jsonp(){ + // TODO: Do we need to support josnp? + } + + res.links = function links(){ + // TODO: Implement me! + } + + res.location = function location(){ + // TODO: Implement me! + } + + res.redirect = function redirect(){ + // TODO: Implement me! + } + + res.render = function render(){ + // TODO: Does this apply? + } + + res.send = function send(){ + // TODO: Does this apply? + } + + res.sendFile = function sendFile(){ + // TODO: Does this apply? + } + + res.sendStatus = function sendStatus(){ + // TODO: Implement me! + } + + res.set = function set(){ + // TODO: Implement me! + } + + res.status = function status(){ + // TODO: Implement me! + } + + res.type = function type(){ + // TODO: Implement me! + } + + res.vary = function vary(){ + // TODO: Implement me! + } + + return res; +} + diff --git a/_serverless/_auto/demo-app-posts/s-function.json b/_serverless/_auto/demo-app-posts/s-function.json new file mode 100644 index 0000000..4a9a443 --- /dev/null +++ b/_serverless/_auto/demo-app-posts/s-function.json @@ -0,0 +1,51 @@ +{ + "name": "demo-app-posts", + "runtime": "nodejs4.3", + "description": "Serverless Lambda function for project: demo-app", + "customName": false, + "customRole": false, + "handler": "handler.handler", + "timeout": 6, + "memorySize": 1024, + "authorizer": {}, + "custom": { + "excludePatterns": [] + }, + "endpoints": [ + { + "path": "/posts", + "method": "GET", + "type": "AWS", + "authorizationType": "none", + "authorizerFunction": false, + "apiKeyRequired": false, + "requestParameters": {}, + "requestTemplates": "$${passthroughTemplate}", + "responses": { + "400": { + "statusCode": "400" + }, + "default": { + "statusCode": "200", + "responseParameters": {}, + "responseModels": { + "application/json;charset=UTF-8": "Empty" + }, + "responseTemplates": { + "application/json;charset=UTF-8": "" + } + } + } + } + ], + "events": [], + "environment": { + "SERVERLESS_PROJECT": "${project}", + "SERVERLESS_STAGE": "${stage}", + "SERVERLESS_REGION": "${region}" + }, + "vpc": { + "securityGroupIds": [], + "subnetIds": [] + } +} diff --git a/_serverless/_auto/demo-app-posts/s-templates.yaml b/_serverless/_auto/demo-app-posts/s-templates.yaml new file mode 100644 index 0000000..a2729c5 --- /dev/null +++ b/_serverless/_auto/demo-app-posts/s-templates.yaml @@ -0,0 +1,46 @@ +passthroughTemplate: + application/json: | + ## See http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html + ## This template will pass through all parameters including path, querystring, header, stage variables, and context through to the integration endpoint via the body/payload + #set($allParams = $input.params()) + { + "body-json" : "$input.json('$')", + "params" : { + #foreach($type in $allParams.keySet()) + #set($params = $allParams.get($type)) + "$type" : { + #foreach($paramName in $params.keySet()) + "$paramName" : "$util.escapeJavaScript($params.get($paramName))" + #if($foreach.hasNext),#end + #end + } + #if($foreach.hasNext),#end + #end + }, + "stage-variables" : { + #foreach($key in $stageVariables.keySet()) + "$key" : "$util.escapeJavaScript($stageVariables.get($key))" + #if($foreach.hasNext),#end + #end + }, + "context" : { + "account-id" : "$context.identity.accountId", + "api-id" : "$context.apiId", + "api-key" : "$context.identity.apiKey", + "authorizer-principal-id" : "$context.authorizer.principalId", + "caller" : "$context.identity.caller", + "cognito-authentication-provider" : "$context.identity.cognitoAuthenticationProvider", + "cognito-authentication-type" : "$context.identity.cognitoAuthenticationType", + "cognito-identity-id" : "$context.identity.cognitoIdentityId", + "cognito-identity-pool-id" : "$context.identity.cognitoIdentityPoolId", + "http-method" : "$context.httpMethod", + "stage" : "$context.stage", + "source-ip" : "$context.identity.sourceIp", + "user" : "$context.identity.user", + "user-agent" : "$context.identity.userAgent", + "user-arn" : "$context.identity.userArn", + "request-id" : "$context.requestId", + "resource-id" : "$context.resourceId", + "resource-path" : "$context.resourcePath" + } + } diff --git a/_serverless/demo-app-posts/endpoint-logic.js b/_serverless/demo-app-posts/endpoint-logic.js new file mode 100644 index 0000000..8b68f29 --- /dev/null +++ b/_serverless/demo-app-posts/endpoint-logic.js @@ -0,0 +1,6 @@ +module.exports = { + index: function(req, res) { + res.status(200); + res.json({ message: "ohai"}); + } +}; diff --git a/_serverless/demo-app-posts/event.json b/_serverless/demo-app-posts/event.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/_serverless/demo-app-posts/event.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/_serverless/demo-app-posts/handler.js b/_serverless/demo-app-posts/handler.js new file mode 100644 index 0000000..bba5e78 --- /dev/null +++ b/_serverless/demo-app-posts/handler.js @@ -0,0 +1,12 @@ +const lambdaUtils = require('./lambda-utils'); + +const endpointLogic = require('./endpoint-logic'); + +module.exports.handler = function(event, context, cb) { + console.log('got event'); + console.log(JSON.stringify(event,null,'\t')); + var req = lambdaUtils.buildRequest(event,context,cb); + var res = lambdaUtils.buildResponse(event,context,cb); + endpointLogic.index(req, res); +}; + diff --git a/_serverless/demo-app-posts/lambda-utils.js b/_serverless/demo-app-posts/lambda-utils.js new file mode 100644 index 0000000..e9ce029 --- /dev/null +++ b/_serverless/demo-app-posts/lambda-utils.js @@ -0,0 +1,156 @@ +module.exports.buildRequest = function(event, context, cb) { + var req = {}; + + // http://expressjs.com/en/api.html#req + // Properties + req.app = null; // Not sure this applies in our context... + req.baseUrl = event.baseUrl; + req.body = event.body; + req.cookies = {}; // TODO : Figure out how to get cookies from AWS Gateway + req.fresh = true; // TODO : Does this apply in our context? + req.hostname = event.params.header.Host; + //req.ip = event.params.header["X-Forwarded-For"].split(", ")[0]; + req.ips = event.params.header["X-Forwarded-For"]; + req.method = event.method; + req.originalUrl = ""; // TODO : Figrure out how to reconstruct this + req.params = event.params; + req.path = event.baseUrl; // TODO : This isn't quite right... + req.protocol = event.params.header["X-Forwarded-Proto"]; + req.query = event.query; + req.route = null; // TODO : Does this apply to us? + req.secure = req.protocol === 'https'; + req.signedCookies = {}; // TODO : ??? + req.stale = false; // TODO : Does this apply in our context? + req.subdomains = []; // TODO: Does this apply? + req.xhr = event.params.header["X-Requested-With"] === 'XMLHttpRequest'; + + // Methods + /* + var accept = realAccepts(req); + req.accepts = function accepts(types){ + // TODO : Fix me! + return types; + //return accept.type(types); + }; + */ + req.acceptsCharsets = function acceptsCharsets(){ + // TODO : Fix me! + }; + + req.acceptsEncodings = function acceptsEncodings(){ + // TODO : Fix me! + }; + + req.acceptsLanguages = function acceptsLanguages(){ + // TODO : Fix me! + }; + + req.get = function get(field){ + return event.params.header[field]; + } + + req.is = function is(type){ + // TODO: Fix me! + } + + return req; +} + +module.exports.buildResponse = function(event, context, cb) { + var res = {}; + + // Properties + res.app = null; // TODO : Does this apply? + res.headersSent = false; // TODO : Is this right? + res.locals = {}; // TODO: Does this apply? + + res.headers = []; + + // Methods + res.append = function append(field,value){ + res.headers.push([field,value]); + } + + res.attachment = function attachment(){ + // TODO: Can we return attachments through API Gatewway? + } + + res.cookie = function cookie(name, value, options){ + // TODO: Can we set cookies? + } + + res.clearCookie = function clearCookie(name, options){ + // TODO: Can we set cookies? + } + + res.download = function download(){ + // TODO: Can we do this? + } + + res.end = function end(){ + // TODO: Implement me! + } + + res.format = function format(){ + // TODO: does this apply? + } + + res.get = function get(field){ + return headers[field]; + } + + res.json = function json(payload){ + cb(null, payload); + } + + res.jsonp = function jsonp(){ + // TODO: Do we need to support josnp? + } + + res.links = function links(){ + // TODO: Implement me! + } + + res.location = function location(){ + // TODO: Implement me! + } + + res.redirect = function redirect(){ + // TODO: Implement me! + } + + res.render = function render(){ + // TODO: Does this apply? + } + + res.send = function send(){ + // TODO: Does this apply? + } + + res.sendFile = function sendFile(){ + // TODO: Does this apply? + } + + res.sendStatus = function sendStatus(){ + // TODO: Implement me! + } + + res.set = function set(){ + // TODO: Implement me! + } + + res.status = function status(){ + // TODO: Implement me! + } + + res.type = function type(){ + // TODO: Implement me! + } + + res.vary = function vary(){ + // TODO: Implement me! + } + + return res; +} + diff --git a/_serverless/demo-app-posts/s-function.json b/_serverless/demo-app-posts/s-function.json new file mode 100644 index 0000000..e9e3461 --- /dev/null +++ b/_serverless/demo-app-posts/s-function.json @@ -0,0 +1,51 @@ +{ + "name": "manual-demo-app-posts", + "runtime": "nodejs4.3", + "description": "Serverless Lambda function for project: demo-app", + "customName": false, + "customRole": false, + "handler": "handler.handler", + "timeout": 6, + "memorySize": 1024, + "authorizer": {}, + "custom": { + "excludePatterns": [] + }, + "endpoints": [ + { + "path": "demo-app-posts", + "method": "GET", + "type": "AWS", + "authorizationType": "none", + "authorizerFunction": false, + "apiKeyRequired": false, + "requestParameters": {}, + "requestTemplates": "$${passthroughTemplate}", + "responses": { + "400": { + "statusCode": "400" + }, + "default": { + "statusCode": "200", + "responseParameters": {}, + "responseModels": { + "application/json;charset=UTF-8": "Empty" + }, + "responseTemplates": { + "application/json;charset=UTF-8": "" + } + } + } + } + ], + "events": [], + "environment": { + "SERVERLESS_PROJECT": "${project}", + "SERVERLESS_STAGE": "${stage}", + "SERVERLESS_REGION": "${region}" + }, + "vpc": { + "securityGroupIds": [], + "subnetIds": [] + } +} diff --git a/_serverless/demo-app-posts/s-templates.yaml b/_serverless/demo-app-posts/s-templates.yaml new file mode 100644 index 0000000..de5912b --- /dev/null +++ b/_serverless/demo-app-posts/s-templates.yaml @@ -0,0 +1,93 @@ +passthroughTemplate: + application/json: | + ## See http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html + ## This template will pass through all parameters including path, querystring, header, stage variables, and context through to the integration endpoint via the body/payload + #set($allParams = $input.params()) + { + "body-json" : "$input.json('$')", + "params" : { + #foreach($type in $allParams.keySet()) + #set($params = $allParams.get($type)) + "$type" : { + #foreach($paramName in $params.keySet()) + "$paramName" : "$util.escapeJavaScript($params.get($paramName))" + #if($foreach.hasNext),#end + #end + } + #if($foreach.hasNext),#end + #end + }, + "stage-variables" : { + #foreach($key in $stageVariables.keySet()) + "$key" : "$util.escapeJavaScript($stageVariables.get($key))" + #if($foreach.hasNext),#end + #end + }, + "context" : { + "account-id" : "$context.identity.accountId", + "api-id" : "$context.apiId", + "api-key" : "$context.identity.apiKey", + "authorizer-principal-id" : "$context.authorizer.principalId", + "caller" : "$context.identity.caller", + "cognito-authentication-provider" : "$context.identity.cognitoAuthenticationProvider", + "cognito-authentication-type" : "$context.identity.cognitoAuthenticationType", + "cognito-identity-id" : "$context.identity.cognitoIdentityId", + "cognito-identity-pool-id" : "$context.identity.cognitoIdentityPoolId", + "http-method" : "$context.httpMethod", + "stage" : "$context.stage", + "source-ip" : "$context.identity.sourceIp", + "user" : "$context.identity.user", + "user-agent" : "$context.identity.userAgent", + "user-arn" : "$context.identity.userArn", + "request-id" : "$context.requestId", + "resource-id" : "$context.resourceId", + "resource-path" : "$context.resourcePath" + } + } +expressRequestTemplate: + application/json: | + { + "body": "$input.json('$')", + "headers": { + #foreach($header in $input.params().header.keySet()) + "$header": "$util.escapeJavaScript($input.params().header.get($header))" #if($foreach.hasNext),#end + #end + }, + "method": "$context.httpMethod", + "params": { + #foreach($param in $input.params().path.keySet()) + "$param": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end + #end + }, + "query": { + #foreach($queryParam in $input.params().querystring.keySet()) + "$queryParam": "$util.escapeJavaScript($input.params().querystring.get($queryParam))" #if($foreach.hasNext),#end + #end + }, + "baseUrl": "$context.resourcePath", + "context": { + #foreach($contextItem in $context.keySet()) + "$contextItem": "$util.escapeJavaScript($context.get($contextItem))" #if($foreach.hasNext),#end + #end + } + } +fullRequestTemplate: + application/json: | + $input.json('$'), + "headers": { + #foreach($header in $input.params().header.keySet()) + "$header": "$util.escapeJavaScript($input.params().header.get($header))" #if($foreach.hasNext),#end + #end + }, + "method": "$context.httpMethod", + "params": { + #foreach($param in $input.params().path.keySet()) + "$param": "$util.escapeJavaScript($input.params().path.get($param))" #if($foreach.hasNext),#end + #end + }, + "query": { + #foreach($queryParam in $input.params().querystring.keySet()) + "$queryParam": "$util.escapeJavaScript($input.params().querystring.get($queryParam))" #if($foreach.hasNext),#end + #end + } + } diff --git a/_serverless/package.json b/_serverless/package.json new file mode 100644 index 0000000..e6a3fd9 --- /dev/null +++ b/_serverless/package.json @@ -0,0 +1,13 @@ +{ + "name": "demo-app", + "version": "0.0.1", + "description": "A Serverless Project and its Serverless Plugin dependencies.", + "author": "me", + "license": "MIT", + "private": false, + "repository": { + "type": "git", + "url": "git://github.com/" + }, + "dependencies": {} +} \ No newline at end of file diff --git a/_serverless/s-project.json b/_serverless/s-project.json new file mode 100644 index 0000000..a4dc70b --- /dev/null +++ b/_serverless/s-project.json @@ -0,0 +1,5 @@ +{ + "name": "demo-app", + "custom": {}, + "plugins": [] +} \ No newline at end of file diff --git a/_serverless/s-resources-cf.json b/_serverless/s-resources-cf.json new file mode 100644 index 0000000..8b5cd2d --- /dev/null +++ b/_serverless/s-resources-cf.json @@ -0,0 +1,64 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "The AWS CloudFormation template for this Serverless application's resources outside of Lambdas and Api Gateway", + "Resources": { + "IamRoleLambda": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + "Path": "/" + } + }, + "IamPolicyLambda": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyName": "${stage}-${project}-lambda", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Resource": "arn:aws:logs:${region}:*:*" + } + ] + }, + "Roles": [ + { + "Ref": "IamRoleLambda" + } + ] + } + } + }, + "Outputs": { + "IamRoleArnLambda": { + "Description": "ARN of the lambda IAM role", + "Value": { + "Fn::GetAtt": [ + "IamRoleLambda", + "Arn" + ] + } + } + } +} \ No newline at end of file diff --git a/demo-app-posts-lambda-handler.js b/demo-app-posts-lambda-handler.js new file mode 100644 index 0000000..0f36723 --- /dev/null +++ b/demo-app-posts-lambda-handler.js @@ -0,0 +1,9 @@ +const lambdaUtils = require('./lambda-utils'); + +const endpointLogic = require('./demo-app-posts'); + +module.exports.handler = function(event, context, cb) { + var req = lambdaUtils.buildRequest(event,context,cb); + var res = lambdaUtils.buildResponse(event,context,cb); + endpointLogic.index(req, res); +}; diff --git a/demo-app-posts-lambda-runner.js b/demo-app-posts-lambda-runner.js new file mode 100644 index 0000000..0ce812d --- /dev/null +++ b/demo-app-posts-lambda-runner.js @@ -0,0 +1,17 @@ +const lambdaHandler = require('./demo-app-posts-lambda-handler'); + +var event = { + headers: {} +}; +var context = {}; +function cb(error, data){ + if(error){ + console.log("got error-------"); + console.log(error); + return; + } + console.log("got data-------"); + console.log(data); +} + +lambdaHandler.handler(event, context, cb); diff --git a/demo-app-posts.js b/demo-app-posts.js index 2462e0d..8b68f29 100644 --- a/demo-app-posts.js +++ b/demo-app-posts.js @@ -1,5 +1,6 @@ module.exports = { index: function(req, res) { - return [200, {}, "ohai"]; + res.status(200); + res.json({ message: "ohai"}); } -}; \ No newline at end of file +}; diff --git a/demo-app-prepare-deploy.js b/demo-app-prepare-deploy.js new file mode 100644 index 0000000..4b123f3 --- /dev/null +++ b/demo-app-prepare-deploy.js @@ -0,0 +1,20 @@ +var Server = require('./server'); +var Router = require('./demo-router'); +var ServerlessGenerator = require('./serverless-generator'); + +var util = require('util'); + +var server = new Server(); +var router = new Router(); + +router.loadRoutes(server); + +var routes = server.toJSON(); + +routes.routes.forEach(function(route){ + console.log('about to generate serverless function + endpiont'); + console.log(route); + ServerlessGenerator.generateRoute(route); +}); + +console.log(routes) diff --git a/demo-app.js b/demo-app.js index 59f5040..b2b8f07 100644 --- a/demo-app.js +++ b/demo-app.js @@ -1,11 +1,12 @@ var Server = require('./server'); +var Router = require('./demo-router'); + var util = require('util'); var server = new Server(); +var router = new Router(); -server.routes(function() { - this.get('/posts', {from: 'demo-app-posts', with: 'index'}); -}); +router.loadRoutes(server); server.start(3000); diff --git a/demo-router.js b/demo-router.js new file mode 100644 index 0000000..cbca409 --- /dev/null +++ b/demo-router.js @@ -0,0 +1,13 @@ +function Router() { + +} + +Router.prototype.loadRoutes = function(server){ + + server.routes(function() { + this.get('/posts', {from: 'demo-app-posts', with: 'index'}); + }); + +} + +module.exports = Router; diff --git a/serverless-generator.js b/serverless-generator.js new file mode 100644 index 0000000..cecee51 --- /dev/null +++ b/serverless-generator.js @@ -0,0 +1,72 @@ + +const fs = require('fs'); + +function preflight(route){ + makeAutoDir(); + makeRouteDir(route); +} + +function buildRoutePath(route){ + return `_serverless/_auto/${route.module}`; +} + +function templateDestinationPath(route, template){ + return `${buildRoutePath(route)}/${template}` +} + +function makeAutoDir(){ + try{ + fs.accessSync('_serverless/_auto'); + }catch(error){ + fs.mkdirSync(`_serverless/_auto`); + } +} + +function makeRouteDir(route){ + const routePath = buildRoutePath(route); + try{ + fs.accessSync(routePath); + }catch(error){ + fs.mkdirSync(routePath); + } +} + +function copyTemplate(route, template){ + console.log(`copying template ${template}`) + var destination = templateDestinationPath(route,template); + var source = `serverless-templates/${template}`; + fs.createReadStream(source).pipe(fs.createWriteStream(destination)); +} + +function copyTemplates(route){ + copyTemplate(route,'handler.js'); + copyTemplate(route,'s-templates.yaml'); + copyTemplate(route,'lambda-utils.js'); +} + +function updateSFunction(route){ + var readPath = `serverless-templates/s-function.json` + var writePath = templateDestinationPath(route, 's-function.json'); + var contents = fs.readFileSync(readPath, {encoding:"utf8"}); + contents = contents.replace(/{{module}}/g,route.module); + contents = contents.replace(/{{method}}/g,route.method); + contents = contents.replace(/{{url}}/g,route.url); + fs.writeFileSync(writePath,contents); +} + +function copyModule(route){ + var destination = templateDestinationPath(route,'endpoint-logic.js'); + var source = `${route.module}.js`; + fs.createReadStream(source).pipe(fs.createWriteStream(destination)); +} + +module.exports.generateRoute = function(route){ + console.log('in generateRoute -----------'); + console.log(route); + console.log('-------------------'); + + preflight(route); + copyTemplates(route); + updateSFunction(route); + copyModule(route); +} diff --git a/serverless-templates/handler.js b/serverless-templates/handler.js new file mode 100644 index 0000000..bba5e78 --- /dev/null +++ b/serverless-templates/handler.js @@ -0,0 +1,12 @@ +const lambdaUtils = require('./lambda-utils'); + +const endpointLogic = require('./endpoint-logic'); + +module.exports.handler = function(event, context, cb) { + console.log('got event'); + console.log(JSON.stringify(event,null,'\t')); + var req = lambdaUtils.buildRequest(event,context,cb); + var res = lambdaUtils.buildResponse(event,context,cb); + endpointLogic.index(req, res); +}; + diff --git a/serverless-templates/lambda-utils.js b/serverless-templates/lambda-utils.js new file mode 100644 index 0000000..e9ce029 --- /dev/null +++ b/serverless-templates/lambda-utils.js @@ -0,0 +1,156 @@ +module.exports.buildRequest = function(event, context, cb) { + var req = {}; + + // http://expressjs.com/en/api.html#req + // Properties + req.app = null; // Not sure this applies in our context... + req.baseUrl = event.baseUrl; + req.body = event.body; + req.cookies = {}; // TODO : Figure out how to get cookies from AWS Gateway + req.fresh = true; // TODO : Does this apply in our context? + req.hostname = event.params.header.Host; + //req.ip = event.params.header["X-Forwarded-For"].split(", ")[0]; + req.ips = event.params.header["X-Forwarded-For"]; + req.method = event.method; + req.originalUrl = ""; // TODO : Figrure out how to reconstruct this + req.params = event.params; + req.path = event.baseUrl; // TODO : This isn't quite right... + req.protocol = event.params.header["X-Forwarded-Proto"]; + req.query = event.query; + req.route = null; // TODO : Does this apply to us? + req.secure = req.protocol === 'https'; + req.signedCookies = {}; // TODO : ??? + req.stale = false; // TODO : Does this apply in our context? + req.subdomains = []; // TODO: Does this apply? + req.xhr = event.params.header["X-Requested-With"] === 'XMLHttpRequest'; + + // Methods + /* + var accept = realAccepts(req); + req.accepts = function accepts(types){ + // TODO : Fix me! + return types; + //return accept.type(types); + }; + */ + req.acceptsCharsets = function acceptsCharsets(){ + // TODO : Fix me! + }; + + req.acceptsEncodings = function acceptsEncodings(){ + // TODO : Fix me! + }; + + req.acceptsLanguages = function acceptsLanguages(){ + // TODO : Fix me! + }; + + req.get = function get(field){ + return event.params.header[field]; + } + + req.is = function is(type){ + // TODO: Fix me! + } + + return req; +} + +module.exports.buildResponse = function(event, context, cb) { + var res = {}; + + // Properties + res.app = null; // TODO : Does this apply? + res.headersSent = false; // TODO : Is this right? + res.locals = {}; // TODO: Does this apply? + + res.headers = []; + + // Methods + res.append = function append(field,value){ + res.headers.push([field,value]); + } + + res.attachment = function attachment(){ + // TODO: Can we return attachments through API Gatewway? + } + + res.cookie = function cookie(name, value, options){ + // TODO: Can we set cookies? + } + + res.clearCookie = function clearCookie(name, options){ + // TODO: Can we set cookies? + } + + res.download = function download(){ + // TODO: Can we do this? + } + + res.end = function end(){ + // TODO: Implement me! + } + + res.format = function format(){ + // TODO: does this apply? + } + + res.get = function get(field){ + return headers[field]; + } + + res.json = function json(payload){ + cb(null, payload); + } + + res.jsonp = function jsonp(){ + // TODO: Do we need to support josnp? + } + + res.links = function links(){ + // TODO: Implement me! + } + + res.location = function location(){ + // TODO: Implement me! + } + + res.redirect = function redirect(){ + // TODO: Implement me! + } + + res.render = function render(){ + // TODO: Does this apply? + } + + res.send = function send(){ + // TODO: Does this apply? + } + + res.sendFile = function sendFile(){ + // TODO: Does this apply? + } + + res.sendStatus = function sendStatus(){ + // TODO: Implement me! + } + + res.set = function set(){ + // TODO: Implement me! + } + + res.status = function status(){ + // TODO: Implement me! + } + + res.type = function type(){ + // TODO: Implement me! + } + + res.vary = function vary(){ + // TODO: Implement me! + } + + return res; +} + diff --git a/serverless-templates/s-function.json b/serverless-templates/s-function.json new file mode 100644 index 0000000..84f8a2b --- /dev/null +++ b/serverless-templates/s-function.json @@ -0,0 +1,51 @@ +{ + "name": "{{module}}", + "runtime": "nodejs4.3", + "description": "Serverless Lambda function for project: demo-app", + "customName": false, + "customRole": false, + "handler": "handler.handler", + "timeout": 6, + "memorySize": 1024, + "authorizer": {}, + "custom": { + "excludePatterns": [] + }, + "endpoints": [ + { + "path": "{{url}}", + "method": "{{method}}", + "type": "AWS", + "authorizationType": "none", + "authorizerFunction": false, + "apiKeyRequired": false, + "requestParameters": {}, + "requestTemplates": "$${passthroughTemplate}", + "responses": { + "400": { + "statusCode": "400" + }, + "default": { + "statusCode": "200", + "responseParameters": {}, + "responseModels": { + "application/json;charset=UTF-8": "Empty" + }, + "responseTemplates": { + "application/json;charset=UTF-8": "" + } + } + } + } + ], + "events": [], + "environment": { + "SERVERLESS_PROJECT": "${project}", + "SERVERLESS_STAGE": "${stage}", + "SERVERLESS_REGION": "${region}" + }, + "vpc": { + "securityGroupIds": [], + "subnetIds": [] + } +} diff --git a/serverless-templates/s-templates.yaml b/serverless-templates/s-templates.yaml new file mode 100644 index 0000000..a2729c5 --- /dev/null +++ b/serverless-templates/s-templates.yaml @@ -0,0 +1,46 @@ +passthroughTemplate: + application/json: | + ## See http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html + ## This template will pass through all parameters including path, querystring, header, stage variables, and context through to the integration endpoint via the body/payload + #set($allParams = $input.params()) + { + "body-json" : "$input.json('$')", + "params" : { + #foreach($type in $allParams.keySet()) + #set($params = $allParams.get($type)) + "$type" : { + #foreach($paramName in $params.keySet()) + "$paramName" : "$util.escapeJavaScript($params.get($paramName))" + #if($foreach.hasNext),#end + #end + } + #if($foreach.hasNext),#end + #end + }, + "stage-variables" : { + #foreach($key in $stageVariables.keySet()) + "$key" : "$util.escapeJavaScript($stageVariables.get($key))" + #if($foreach.hasNext),#end + #end + }, + "context" : { + "account-id" : "$context.identity.accountId", + "api-id" : "$context.apiId", + "api-key" : "$context.identity.apiKey", + "authorizer-principal-id" : "$context.authorizer.principalId", + "caller" : "$context.identity.caller", + "cognito-authentication-provider" : "$context.identity.cognitoAuthenticationProvider", + "cognito-authentication-type" : "$context.identity.cognitoAuthenticationType", + "cognito-identity-id" : "$context.identity.cognitoIdentityId", + "cognito-identity-pool-id" : "$context.identity.cognitoIdentityPoolId", + "http-method" : "$context.httpMethod", + "stage" : "$context.stage", + "source-ip" : "$context.identity.sourceIp", + "user" : "$context.identity.user", + "user-agent" : "$context.identity.userAgent", + "user-arn" : "$context.identity.userArn", + "request-id" : "$context.requestId", + "resource-id" : "$context.resourceId", + "resource-path" : "$context.resourcePath" + } + }