├── .gitignore ├── LICENSE ├── README.md ├── bin └── index.js ├── example ├── goodbye.js ├── hello.js └── sample.yml └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Luis Farzati 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | docker-lambda-api-server 2 | ------------------------ 3 | 4 | A companion tool for the awesome [docker-lambda](https://github.com/lambci/docker-lambda) sandbox. 5 | 6 | This is a very basic, local AWS Lambda API server that lets you invoke your lambda functions via the AWS SDK instead of running Docker from the command-line or docker-lambda Node helper. 7 | 8 | It also leverages (although very limited at this point) the new [AWS Cloudformation SAM template](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md). 9 | 10 | Prerequisites 11 | ------------- 12 | 13 | Same as [docker-lambda](https://github.com/lambci/docker-lambda#prerequisites). 14 | 15 | Tested with the latest Node LTS (v6.9.1). 16 | 17 | Installation 18 | ------------ 19 | 20 | ```bash 21 | npm install docker-lambda-api-server 22 | ``` 23 | 24 | Example 25 | ------- 26 | 27 | Let's suppose you have 2 functions: `hello` and `goodbye`. 28 | 29 | ```javascript 30 | // hello.js 31 | exports.handler = function(event, context, cb) { 32 | cb(null, JSON.stringify({hello: 'world'})) 33 | } 34 | ``` 35 | 36 | ```javascript 37 | // goodbye.js 38 | exports.handler = function(event, context, cb) { 39 | cb(null, JSON.stringify({good: 'bye'})) 40 | } 41 | ``` 42 | 43 | First thing you need to do (if you haven't already), is to create a [SAM template](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md) like this: 44 | 45 | ```yaml 46 | # /greetings.yml 47 | AWSTemplateFormatVersion: '2010-09-09' 48 | Transform: AWS::Serverless-2016-10-31 49 | Resources: 50 | SayHello: 51 | Type: AWS::Serverless::Function 52 | Properties: 53 | Handler: hello.handler 54 | Runtime: nodejs4.3 55 | 56 | SayGoodbye: 57 | Type: AWS::Serverless::Function 58 | Properties: 59 | Handler: goodbye.handler 60 | Runtime: nodejs4.3 61 | ``` 62 | 63 | As you can see above, we created the definition for our 2 functions, which we named `SayHello` and `SayGoodbye`, each one pointing to the right code file. 64 | 65 | We are now ready to start the API server: 66 | 67 | ```bash 68 | docker-lambda-api-server -f greetings.yml 69 | ``` 70 | 71 | An HTTP server will start listening at `localhost:3000`. You can do a quick test by invoking any of your functions via the AWS Lambda REST API: 72 | 73 | ```bash 74 | curl http://localhost:3000/2015-03-31/functions/SayHello/invocations 75 | 76 | {"hello":"world"} 77 | ``` 78 | 79 | Or you can also invoke the function via the AWS SDK: 80 | 81 | ```javascript 82 | import { Lambda } from 'aws-sdk' 83 | 84 | let lambda = new Lambda({ 85 | region: 'foo-west-1', 86 | endpoint: 'http://localhost:3000' 87 | }) 88 | 89 | lambda 90 | .invoke({ FunctionName: 'SayGoodbye' }) 91 | .promise() 92 | .then(result => console.log(result)) 93 | ``` 94 | 95 | Documentation 96 | ------------- 97 | 98 | // TODO 99 | 100 | In the meanwhile, you can run 101 | 102 | ```bash 103 | docker-lambda-api-server -h 104 | ``` 105 | 106 | to see all the options available. 107 | 108 | You can also check out the [provided example](/example). 109 | 110 | 111 | TODO 112 | ---- 113 | 114 | * Unit tests? 115 | * Proper runtime validation (node, node4.3) 116 | * Implement `ClientContext` 117 | * Implement `InvocationType` 118 | * Implement `Qualifier` 119 | * Implement `LogType` 120 | * Implement environment variables in SAM template 121 | * [Your suggestion here](https://github.com/luisfarzati/docker-lambda-api-server/issues/new?labels=feat-request) 122 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | var args = require('args') 5 | var path = require('path') 6 | var yaml = require('js-yaml') 7 | var fs = require('fs') 8 | var http = require('http') 9 | var lambdaInvoke = require('docker-lambda') 10 | 11 | var DEFAULT_PORT = 3000 12 | var DEFAULT_FUNCTION_PATH = '.' 13 | var AWS_LAMBDA_API_URL_REGEX = /^\/\d{4}-\d{2}-\d{2}\/functions\/(.+?)\/invocations/ 14 | 15 | args 16 | .option(['h', 'help'], 'Show usage information') 17 | .option(['p', 'port'], 'Port on which the API server will be running', DEFAULT_PORT) 18 | .option(['P', 'path'], 'Path to the function handler scripts', DEFAULT_FUNCTION_PATH) 19 | .option(['f', 'file'], 'AWS SAM template file') 20 | .option(['v', 'verbose'], 'Verbose output') 21 | 22 | var opts = args.parse(process.argv, { help: false, version: false }) 23 | 24 | if (!opts.file) { 25 | return args.showHelp() 26 | } 27 | 28 | var functionsDir = path.resolve(process.cwd(), opts.path) 29 | var doc = yaml.safeLoad(fs.readFileSync(opts.file, 'utf8')) 30 | 31 | // TODO: improve -- quick & dirty filtering 32 | var functions = Object.keys(doc.Resources) 33 | .filter(name => 34 | doc.Resources[name].Type === 'AWS::Serverless::Function' 35 | && 36 | doc.Resources[name].Properties.Runtime.indexOf('nodejs') >= 0 37 | ) 38 | .reduce((obj, name) => { 39 | obj[name] = doc.Resources[name].Properties.Handler 40 | return obj 41 | }, {}) 42 | 43 | http.createServer(function (req, res) { 44 | opts.v && console.log(new Date().toISOString(), '>>', req.method, req.url) 45 | 46 | var match = req.url.match(AWS_LAMBDA_API_URL_REGEX) || [] 47 | var name = match[1] 48 | 49 | if (functions[name] == null) { 50 | res.statusCode = 404 51 | opts.v && console.log(new Date().toISOString(), '<< 404 Missing function', name, 'in template') 52 | return res.end('Function ' + name + ' not found in template') 53 | } 54 | 55 | var eventBody = [] 56 | 57 | req.on('data', data => eventBody.push(data)) 58 | req.on('end', () => { 59 | var params = { 60 | handler: functions[name], 61 | taskDir: functionsDir 62 | } 63 | 64 | var event = eventBody.join('') 65 | if (event !== '') { 66 | params.event = JSON.parse(event) 67 | } 68 | 69 | var result = lambdaInvoke(params) 70 | opts.v && console.log(new Date().toISOString(), '<< 200', result.substr(0, 40), result.length > 40 ? '...' : '') 71 | res.end(result) 72 | }) 73 | 74 | }).listen(opts.port, function () { 75 | console.log('Starting server at port:', opts.port) 76 | }) -------------------------------------------------------------------------------- /example/goodbye.js: -------------------------------------------------------------------------------- 1 | exports.handler = function(event, context, cb) { 2 | cb(null, JSON.stringify({good: 'bye'})) 3 | } 4 | -------------------------------------------------------------------------------- /example/hello.js: -------------------------------------------------------------------------------- 1 | exports.handler = function(event, context, cb) { 2 | cb(null, JSON.stringify({ hello: event.name || 'world' })) 3 | } 4 | -------------------------------------------------------------------------------- /example/sample.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | Resources: 4 | SayHello: 5 | Type: AWS::Serverless::Function 6 | Properties: 7 | Handler: hello.handler 8 | Runtime: nodejs4.3 9 | 10 | SayGoodbye: 11 | Type: AWS::Serverless::Function 12 | Properties: 13 | Handler: goodbye.handler 14 | Runtime: nodejs4.3 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docker-lambda-api-server", 3 | "version": "1.0.3", 4 | "bin": { 5 | "docker-lambda-api-server": "./bin/index.js" 6 | }, 7 | "dependencies": { 8 | "args": "^2.1.0", 9 | "docker-lambda": "^0.9.1", 10 | "js-yaml": "^3.7.0" 11 | } 12 | } 13 | --------------------------------------------------------------------------------