From 6df5261501ac33be069984bc00d8b28944b58a12 Mon Sep 17 00:00:00 2001 From: Logan Raarup Date: Mon, 5 Dec 2016 20:21:15 +0100 Subject: [PATCH 1/2] Enable using the requirements packaging functionality alone, without the WSGI handler --- CHANGELOG.md | 6 +++++ README.md | 63 ++++++++++++++++++++++++++++++++++++++++++++-------- index.js | 34 ++++++++++++++++++---------- package.json | 3 ++- 4 files changed, 84 insertions(+), 22 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8676311 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +# 1.0.1 +## Features +* Enable using the requirements packaging functionality alone, without the WSGI handler. This is enabled by omitting the `custom.wsgi.app` setting from `serverless.yml`. + +## Bugs +* If no `requirements.txt` file is present and the WSGI handler is enabled, make sure to package werkzeug. diff --git a/README.md b/README.md index fbd553d..5679cac 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,9 @@ Load the plugin and set the `custom.wsgi.app` configuration in `serverless.yml` module path of your Flask application. All functions that will use WSGI need to have `wsgi.handler` set as the Lambda handler and -use the `lambda-proxy` integration for API Gateway. +use the default `lambda-proxy` integration for API Gateway. This configuration example treats +API Gateway as a transparent proxy, passing all requests directly to your Flask application, +and letting the application handle errors, 404s etc. ```yaml service: example @@ -80,20 +82,14 @@ functions: api: handler: wsgi.handler events: - - http: - path: cats - method: get - integration: lambda-proxy - - http: - path: dogs/{id} - method: get - integration: lambda-proxy + - http: ANY {proxy+} custom: wsgi: app: api.app ``` + ### requirements.txt Add Flask to the application bundle. @@ -146,6 +142,21 @@ requests==2.11.1 For more information, see [https://pip.readthedocs.io/en/1.1/requirements.html](https://pip.readthedocs.io/en/1.1/requirements.html). +You can use the requirement packaging functionality of *serverless-wsgi* without the WSGI +handler itself by including the plugin in your `serverless.yml` configuration, without specifying +the `custom.wsgi.app` setting. This will omit the WSGI handler from the package, but include +any requirements specified in `requirements.txt`. + +If you do not include the WSGI handler, you'll need to add `.requirements` to the Python search path +manually in your handler, before importing any packages: + +``` +import os +import sys +root = os.path.abspath(os.path.join(os.path.dirname(__file__))) +sys.path.insert(0, os.path.join(root, '.requirements')) +``` + ### Local server For convenience, a `sls wsgi serve` command is provided to run your WSGI application @@ -169,3 +180,37 @@ $ sls wsgi serve -p 8000 * Restarting with stat * Debugger is active! ``` + + +### Explicit routes + +If you'd like to be explicit about which routes and HTTP methods should pass through to your +application, see the following example: + +```yaml +service: example + +provider: + name: aws + runtime: python2.7 + +plugins: + - serverless-wsgi + +functions: + api: + handler: wsgi.handler + events: + - http: + path: cats + method: get + integration: lambda-proxy + - http: + path: dogs/{id} + method: get + integration: lambda-proxy + +custom: + wsgi: + app: api.app +``` diff --git a/index.js b/index.js index 68b42e4..943584e 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,5 @@ /* jshint ignore:start */ 'use strict'; - const BbPromise = require('bluebird'); const _ = require('lodash'); const path = require('path'); @@ -13,13 +12,15 @@ class ServerlessWSGI { validate() { if (this.serverless.service.custom && this.serverless.service.custom.wsgi && this.serverless.service.custom.wsgi.app) { this.wsgiApp = this.serverless.service.custom.wsgi.app; - } else { - throw new this.serverless.classes.Error( - 'Missing WSGI app, please specify custom.wsgi.app. For instance, if you have a Flask application "app" in "api.py", set the Serverless custom.wsgi.app configuration option to: api.app'); } }; packWsgiHandler() { + if (!this.wsgiApp) { + this.serverless.cli.log('Warning: No WSGI app specified, omitting WSGI handler from package'); + return BbPromise.resolve(); + } + this.serverless.cli.log('Packaging Python WSGI handler...'); return BbPromise.all([ @@ -34,20 +35,24 @@ class ServerlessWSGI { packRequirements() { const requirementsFile = path.join(this.serverless.config.servicePath, 'requirements.txt'); + let args = [path.resolve(__dirname, 'requirements.py')]; - if (!fse.existsSync(requirementsFile)) { - return BbPromise.resolve(); + if (fse.existsSync(requirementsFile)) { + args.push(requirementsFile); + } else { + if (this.wsgiApp) { + args.push(path.resolve(__dirname, 'requirements.txt')); + } else { + return BbPromise.resolve(); + } } + args.push(path.join(this.serverless.config.servicePath, '.requirements')); + this.serverless.cli.log('Packaging required Python packages...'); return new BbPromise((resolve, reject) => { - const res = child_process.spawnSync('python', [ - path.resolve(__dirname, 'requirements.py'), - path.resolve(__dirname, 'requirements.txt'), - requirementsFile, - path.join(this.serverless.config.servicePath, '.requirements') - ]); + const res = child_process.spawnSync('python', args); if (res.error) { return reject(res.error); } @@ -66,6 +71,11 @@ class ServerlessWSGI { }; serve() { + if (!this.wsgiApp) { + throw new this.serverless.classes.Error( + 'Missing WSGI app, please specify custom.wsgi.app. For instance, if you have a Flask application "app" in "api.py", set the Serverless custom.wsgi.app configuration option to: api.app'); + } + const port = this.options.port || 5000; return new BbPromise((resolve, reject) => { diff --git a/package.json b/package.json index a2d7890..5f15248 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless-wsgi", - "version": "1.0.0", + "version": "1.0.1", "engines": { "node": ">=4.0" }, @@ -27,6 +27,7 @@ "serverless.com" ], "files": [ + "CHANGELOG.md", "index.js", "LICENSE", "package.json", From 1350a12a4ec95b4327aa64cf015991211319c641 Mon Sep 17 00:00:00 2001 From: Logan Raarup Date: Mon, 5 Dec 2016 20:53:06 +0100 Subject: [PATCH 2/2] Load provider and function environment variables when serving WSGI app locally --- CHANGELOG.md | 1 + index.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8676311..7b66d4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 1.0.1 ## Features * Enable using the requirements packaging functionality alone, without the WSGI handler. This is enabled by omitting the `custom.wsgi.app` setting from `serverless.yml`. +* Load provider and function environment variables when serving WSGI app locally ## Bugs * If no `requirements.txt` file is present and the WSGI handler is enabled, make sure to package werkzeug. diff --git a/index.js b/index.js index 943584e..77adb88 100644 --- a/index.js +++ b/index.js @@ -70,6 +70,20 @@ class ServerlessWSGI { fse.removeAsync(path.join(this.serverless.config.servicePath, artifact))));; }; + loadEnvVars() { + const providerEnvVars = this.serverless.service.provider.environment || {}; + _.merge(process.env, providerEnvVars); + + _.each(this.serverless.service.functions, function (func) { + if (func.handler == 'wsgi.handler') { + const functionEnvVars = func.environment || {}; + _.merge(process.env, functionEnvVars); + } + }); + + return BbPromise.resolve(); + }; + serve() { if (!this.wsgiApp) { throw new this.serverless.classes.Error( @@ -123,6 +137,7 @@ class ServerlessWSGI { 'wsgi:serve:serve': () => BbPromise.bind(this) .then(this.validate) + .then(this.loadEnvVars) .then(this.serve) }; }