├── examples └── basic │ ├── package.json │ └── serverless.yml ├── lib ├── validate.js ├── configure.js └── buildSwagger.js ├── .gitignore ├── package.json ├── LICENSE ├── index.js └── README.md /examples/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swag-basic", 3 | "version": "0.0.1", 4 | "description": "Showcase swag", 5 | "repository": { 6 | "url": "https://github.com/doapp-ryanp/serverless-plugin-swag" 7 | }, 8 | "author": "Ryan Pendergast ", 9 | "license": "MIT", 10 | "dependencies": { 11 | }, 12 | "devDependencies": { 13 | "serverless-plugin-swag": "^1.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/validate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.validate = function() { 4 | if (process.env.SLS_DEBUG) { 5 | this.serverless.cli.log('SlsSwag::validate'); 6 | } 7 | 8 | if (!this.serverless.service.custom || !this.serverless.service.custom.swag || !this.serverless.service.custom.swag.restApiId) { 9 | throw new this.serverless.classes.Error('Swag plugin requires custom.swag.restApiId is set in serverless.yml'); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /lib/configure.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const xtend = require('xtend'); 4 | 5 | module.exports = { 6 | /** 7 | * Compute the base configuration 8 | */ 9 | globalConfig() { 10 | if (process.env.SLS_DEBUG) { 11 | this.serverless.cli.log('SlsBrowserify::globalConfig'); 12 | } 13 | 14 | const globalCustom = this.serverless.service.custom.swag || {}; 15 | 16 | let globalDefault = { //Browserify plugin config 17 | globalCORS: false, 18 | }; 19 | 20 | //Merge in global config 21 | this.globalSwagConfig = xtend(globalDefault, globalCustom); 22 | 23 | if (process.env.SLS_DEBUG) { 24 | console.log('cli options', this.options); 25 | console.log('computed globalSwagConfig', this.globalSwagConfig); 26 | } 27 | }, 28 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | dist 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # Compiled binary addons (http://nodejs.org/api/addons.html) 22 | build/Release 23 | 24 | #IDE 25 | .idea 26 | .vscode 27 | 28 | #OS 29 | .DS_Store 30 | .tmp 31 | 32 | node_modules 33 | 34 | #SERVERLESS 35 | admin.env 36 | .serverless 37 | 38 | #angular2 39 | typings 40 | 41 | # Users Environment Variables 42 | .lock-wscript 43 | .tsdrc 44 | .typingsrc 45 | 46 | #IDE configuration files 47 | 48 | tools/**/*.js 49 | dev 50 | docs 51 | test 52 | tmp 53 | 54 | gulpfile.js 55 | gulpfile.js.map 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-plugin-swag", 3 | "version": "1.0.0", 4 | "description": "Serverless v1.0 plugin that creates an maintains a swagger.yaml file for API Gateway (APIG).", 5 | "main": "index.js", 6 | "author": "Ryan Pendergast (http://rynop.com)", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/doapp-ryanp/serverless-plugin-swag.git" 10 | }, 11 | "keywords": [ 12 | "serverless", 13 | "1.0", 14 | "plugin", 15 | "swagger", 16 | "swag", 17 | "api gateway", 18 | "apig" 19 | ], 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/doapp-ryanp/serverless-plugin-swag/issues" 23 | }, 24 | "homepage": "https://github.com/doapp-ryanp/serverless-plugin-swag#readme", 25 | "dependencies": { 26 | "aws-sdk": "~2.6.14", 27 | "bluebird": "~3.4.6", 28 | "fs-extra": "~0.30.0", 29 | "lodash": "^4.16.4", 30 | "xtend": "~4.0.1" 31 | }, 32 | "devDependencies": {} 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Ryan Pendergast 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 | -------------------------------------------------------------------------------- /examples/basic/serverless.yml: -------------------------------------------------------------------------------- 1 | service: simpleSwag 2 | 3 | package: 4 | individually: true 5 | 6 | plugins: 7 | - serverless-plugin-swag 8 | # - serverless-plugin-browserify 9 | 10 | custom: 11 | swag: 12 | globalCORS: 13 | allowOrigins: "*" 14 | allowHeaders: "Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token" 15 | allowMethods: "DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT" 16 | restApiId: ${self:custom.${self:provider.stage}.apigId} 17 | dev: 18 | profile: rynop 19 | apigId: "yhpgg2jdev" 20 | iamRoleArnLambda: "" #Not sure if we need this yet, or if the plugin can infer it. Need this when creating lambda 21 | prod: 22 | profile: aws-prod 23 | apigId: "yhpgg2prod" 24 | iamRoleArnLambda: "" 25 | 26 | provider: 27 | name: aws 28 | runtime: nodejs4.3 29 | stage: dev 30 | region: us-east-1 31 | deploymentBucket: ${self:provider.stage}-useast1-slsdeploys.alaynapage.org 32 | profile: ${self:custom.${self:provider.stage}.profile} 33 | iamRoleStatements: 34 | - Effect: "Allow" 35 | Action: 36 | - "lambda:InvokeFunction" 37 | Resource: arn:aws:lambda:${self:provider.region}:254269101111:function:${self:provider.stage}-${self:service}* 38 | - Effect: "Allow" 39 | Action: 40 | - "s3:G*" 41 | - "s3:L*" 42 | Resource: "*" 43 | 44 | functions: 45 | pagesList: 46 | name: ${self:provider.stage}-${self:service}-pagesList 47 | description: list pages 48 | handler: pages/list.hello 49 | memorySize: 512 50 | timeout: 10 # optional, default is 6 51 | events: 52 | - http: #swag plugin will ignore 53 | path: pages 54 | method: get 55 | 56 | pagesGet: 57 | name: ${self:provider.stage}-${self:service}-pagesGet 58 | description: get page 59 | handler: pages/get.hello 60 | memorySize: 512 61 | timeout: 10 # optional, default is 6 62 | events: 63 | - swagHttp: 64 | path: pages/{id} 65 | method: get 66 | 67 | pagesUpdate: 68 | name: ${self:provider.stage}-${self:service}-pagesUpdate 69 | description: update Page 70 | handler: pages/update.hello 71 | memorySize: 512 72 | timeout: 10 # optional, default is 6 73 | events: 74 | - swagHttp: 75 | path: pages 76 | method: put -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BbPromise = require('bluebird'), 4 | _ = require('lodash'); 5 | 6 | const validate = require('./lib/validate'), 7 | configure = require('./lib/configure'), 8 | buildSwagger = require('./lib/buildSwagger'); 9 | // cleanup = require('./lib/cleanup'); 10 | 11 | class SlsSwag { 12 | constructor(serverless, options) { 13 | this.serverless = serverless; 14 | this.options = options; 15 | this.provider = 'aws'; 16 | this.awsProvider = this.serverless.getProvider('aws'); 17 | this.globalSwagConfig = {}; 18 | this.swagDefObj = {}; 19 | 20 | //Why is this not already done by framework? https://github.com/serverless/serverless/issues/2631 21 | this.options.stage = this.options.stage 22 | || (this.serverless.service.defaults && this.serverless.service.defaults.stage) 23 | || 'dev'; 24 | this.options.region = this.options.region 25 | || (this.serverless.service.defaults && this.serverless.service.defaults.region) 26 | || 'us-east-1'; 27 | 28 | Object.assign( 29 | this, 30 | validate, 31 | configure, 32 | buildSwagger 33 | ); 34 | 35 | this.commands = { 36 | swag: { 37 | usage: 'Enterprise mgmt of APIG and Lambda', 38 | lifecycleEvents: [ 39 | 'help', 40 | ], 41 | options: {}, 42 | commands: { 43 | 44 | "apig-import": { 45 | usage: 'Import API Gateway (overwrite or merge with -p). --region supported', 46 | lifecycleEvents: [ 47 | 'initialize', 48 | 'configure', 49 | 'buildSwagger', 50 | 'import', 51 | 'cleanup' 52 | ], 53 | options: { 54 | regex: { 55 | usage: 'Regex of resource paths to import (merge mode)', 56 | shortcut: 'e', 57 | }, 58 | methods: { 59 | usage: 'CSV of methods to import. Ex: -m PUT,GET -e "^u" Ex2: -m PUT', 60 | shortcut: 'm', 61 | }, 62 | noConfirm: { 63 | usage: 'Only valid if -e or -m specified. Don\'t confirm before importing matched paths/methods', 64 | shortcut: 'n', 65 | }, 66 | out: { 67 | usage: 'Full path to where you want the swagger.json file stored. Default is swagger.json next to serverless.yml', 68 | shortcut: 'o', 69 | }, 70 | }, 71 | }, 72 | 73 | "func-deploy": { 74 | usage: 'Deploy HTTP evented Lambdas via AWS APIs. --stage and --region supported', 75 | lifecycleEvents: [ 76 | 'initialize', 77 | 'configure', 78 | 'package', 79 | 'deploy', 80 | 'cleanup' 81 | ], 82 | options: { 83 | regex: { 84 | usage: 'Regex of functions to deploy', 85 | shortcut: 'e', 86 | }, 87 | }, 88 | } 89 | }, 90 | }, 91 | }; 92 | 93 | this.hooks = { 94 | //Handle `sls swat apig-import` 95 | 'swag:apig-import:initialize': () => BbPromise.bind(this) 96 | .then(this.validate) 97 | .then(this.globalConfig) 98 | .then(this.buildCoreSwagger) 99 | .then(() => { 100 | let q = []; 101 | const functionNames = this.serverless.service.getAllFunctions(); 102 | for (const fName of functionNames) { 103 | q.push(this.genSwaggerPathObj(fName) 104 | .then(spo => _.extend(this.swagDefObj.paths, spo))); 105 | } 106 | 107 | return BbPromise.all(q); 108 | }) 109 | .then(() => { 110 | console.log(this.swagDefObj); 111 | }), 112 | // 113 | // //Handle `sls deploy function` 114 | // 'before:deploy:function:packageFunction': () => BbPromise.bind(this) 115 | // .then(this.validate) 116 | // .then(this.globalConfig) 117 | // .then(() => this.bundle(this.options.function)), 118 | // 119 | 120 | //Handle `sls swag` 121 | 'swag:help': () => BbPromise.bind(this) 122 | .then(() => { 123 | this.serverless.cli.log('Must specify a swag command. Run "serverless swag -h"'); 124 | }), 125 | }; 126 | } 127 | } 128 | 129 | module.exports = SlsSwag; 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Serverless Swagger Plugin 2 | 3 | **NOTE:** This plugin is currently under development 4 | 5 | [![serverless](http://public.serverless.com/badges/v3.svg)](http://www.serverless.com) 6 | 7 | A [Serverless](https://serverless.com) v1.0 plugin for AWS that generates swagger.yaml for API Gateway (APIG) **lambda proxy** and uses AWS APIs to deploy/update HTTP evented Lambdas. 8 | 9 | ## Why? 10 | 11 | Serverless currently maintains API Gateway and Lambda via Cloudformation. This does not work for us because: 12 | 13 | **CloudFormation (CF):** 14 | - CloudFormation [has item limits](https://github.com/serverless/serverless/issues/2387) 15 | - It is not a security best practice to allow deletion of assets from an AWS API Key (can't back with MFA) 16 | - CloudFormation is very powerful. We like to visually validate (via diff and CF web UI diff function) exactly what is going to change. 17 | - Serverless [deletes and re-creates APIG distro on every update](https://github.com/serverless/serverless/issues/2530) 18 | - CloudFormation is not good for frequently changing assets because it sometimes gets into a rollback failed state. You must delete CF and re-create. Not fun hitting this in production 19 | - CloudFormation does not stay on top of new AWS features 20 | 21 | **We prefer Swagger:** 22 | - It is an industry standard, and the "export swagger" from APIG web UI creates ugly and complex swagger 23 | - We can tie a swagger file to a release. This can be referenced and imported at any time into tooling like postman 24 | - We manually review changes to our API Gateway before deploying to different stages 25 | 26 | 27 | ## Install 28 | 29 | From your serverless project run: 30 | ``` 31 | npm install serverless-plugin-swag --save-dev 32 | ``` 33 | 34 | Add the plugin to your `serverless.yml` file: 35 | 36 | ```yaml 37 | plugins: 38 | - serverless-plugin-swag 39 | ``` 40 | 41 | ## Configure 42 | 43 | This plugin minimal configuration under the `custom.swag` namespace. Only 2 elments in your `serverless.yml` are required: 44 | 45 | 1. `custom.swag.restApiId`: the API Gateway distribution ID. 46 | 1. A `swagHttp` (instead of `http`) entry in `functions.FUNCTION_NAME.events`. Currently, the only 2 attributes supported are `path` and `method`. 47 | 48 | This plugin requires that the lambda code artifact already exist (or be build outside of this plugin). I highly recommend my [browserify plugin](https://github.com/doapp-ryanp/serverless-plugin-browserify) but you can also manually define `functions.FUNCTION.package.artifact` (see [artifacts](https://serverless.com/framework/docs/providers/aws/guide/packaging/#artifact)). 49 | 50 | see [example serverless.yml](./examples/basic/serverless.yml) 51 | 52 | ## Usage 53 | 54 | TODO: 55 | - Be able to create/update functions via glob syntax. Ex: `sls swag deployFunc pages*` 56 | - Create/update list of functions. Ex: `sls swag deployFunc pageGet pageUpdate` 57 | - Create/update entire APIG distro 58 | - Create/update one APIG resource 59 | - Support events.swagHttp.authorizer and events.swagHttp. 60 | - Do I need to supporot a 61 | 62 | ## FAQ 63 | 64 | - **Why Swagger as JSON instead of YAML?** Because AWS only supports importing swagger as JSON :( 65 | 66 | 67 | Tmp Backup for me 68 | ```yaml 69 | --- 70 | swagger: "2.0" 71 | info: 72 | version: "2016-09-22T21:51:08Z" 73 | title: "ryan-proxy-test" 74 | host: "myuid.execute-api.us-east-1.amazonaws.com" 75 | basePath: "/yrdy" 76 | schemes: 77 | - "https" 78 | paths: 79 | /{proxy+}: 80 | options: 81 | consumes: 82 | - "application/json" 83 | produces: 84 | - "application/json" 85 | responses: 86 | 200: 87 | description: "200 response" 88 | schema: 89 | $ref: "#/definitions/Empty" 90 | headers: 91 | Access-Control-Allow-Origin: 92 | type: "string" 93 | Access-Control-Allow-Methods: 94 | type: "string" 95 | Access-Control-Allow-Headers: 96 | type: "string" 97 | x-amazon-apigateway-integration: 98 | responses: 99 | default: 100 | statusCode: "200" 101 | responseParameters: 102 | method.response.header.Access-Control-Allow-Methods: "'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT'" 103 | method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'" 104 | method.response.header.Access-Control-Allow-Origin: "'*'" 105 | requestTemplates: 106 | application/json: "{\"statusCode\": 200}" 107 | passthroughBehavior: "when_no_match" 108 | type: "mock" 109 | x-amazon-apigateway-any-method: 110 | produces: 111 | - "application/json" 112 | parameters: 113 | - name: "proxy" 114 | in: "path" 115 | required: true 116 | type: "string" 117 | responses: {} 118 | x-amazon-apigateway-integration: 119 | responses: 120 | default: 121 | statusCode: "200" 122 | uri: "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:myAWSAccount:function:helloworld-proxy/invocations" 123 | passthroughBehavior: "when_no_match" 124 | httpMethod: "POST" 125 | cacheNamespace: "1rnijn" 126 | cacheKeyParameters: 127 | - "method.request.path.proxy" 128 | type: "aws_proxy" 129 | definitions: 130 | Empty: 131 | type: "object" 132 | title: "Empty Schema" 133 | ``` -------------------------------------------------------------------------------- /lib/buildSwagger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const BbPromise = require('bluebird'), 4 | os = require('os'), 5 | path = require('path'), 6 | fs = BbPromise.promisifyAll(require('fs-extra')), 7 | _ = require('lodash'); 8 | 9 | const APP_JSON_CONTENT_TYPE = 'application/json; charset=utf-8', 10 | SWAG_200_RESPONSE = { 11 | "description": "200 response", 12 | "schema": { 13 | "$ref": "#/definitions/Empty" 14 | } 15 | }, 16 | apigLambdaProxyTemplate = { 17 | "produces": [ 18 | "application/json" 19 | ], 20 | "responses": { 21 | "200": SWAG_200_RESPONSE, 22 | }, 23 | "x-amazon-apigateway-integration": { 24 | "uri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:527930388204:function:echo/invocations", 25 | "passthroughBehavior": "when_no_match", 26 | "httpMethod": "POST", 27 | "responses": { 28 | "default": { 29 | "statusCode": "200" 30 | } 31 | }, 32 | "type": "aws_proxy" 33 | } 34 | }; 35 | 36 | module.exports = { 37 | buildCoreSwagger() { 38 | const tmpSwagFilePath = path.join(os.tmpdir(), +new Date() + '.json'), 39 | finalSwagFilePath = this.options.out || path.join(this.serverless.config.servicePath, 'swagger.json'), 40 | now = new Date(); 41 | 42 | if (process.env.SLS_DEBUG) { 43 | this.serverless.cli.log(`Building swagger file at ${tmpSwagFilePath}. Final destination ${finalSwagFilePath}`); 44 | } 45 | 46 | this.swagDefObj = { 47 | "swagger": "2.0", 48 | "info": { 49 | "version": now.toISOString(), 50 | "title": this.serverless.service.service 51 | }, 52 | "host": "", 53 | "basePath": "", 54 | "schemes": [ 55 | "https" 56 | ], 57 | "paths": {}, 58 | "definitions": { 59 | "Empty": { 60 | "type": "object", 61 | "title": "Empty Schema" 62 | } 63 | }, 64 | }; 65 | 66 | if (this.globalSwagConfig.globalCORS) { 67 | this.swagDefObj.paths['/{proxy+}'] = generateGlobalCORS(this.globalSwagConfig); 68 | } 69 | }, 70 | 71 | genSwaggerPathObj(functionName){ 72 | const importOnlyTheseMethods = (this.options.methods && !!this.options.methods.trim()) ? this.options.methods.trim.split(',').map(m=>m.trim()) : [], 73 | functionObject = this.serverless.service.getFunction(functionName), 74 | funcNameAndAlias = ('dev' != this.options.stage) ? `${functionObject.name}:${this.options.stage}` : functionObject.name; 75 | 76 | //If function does not have any swagHttp events return 77 | if (!functionObject.events.some(e => 'swagHttp' in e)) { 78 | return {}; 79 | } 80 | 81 | return checkIfLambdaExists(this.awsProvider, funcNameAndAlias, this.options.stage, this.options.region) 82 | .then(lambdaArn => { 83 | if (process.env.SLS_DEBUG) { 84 | this.serverless.cli.log(`Building swagger path objs for ${functionName}`); 85 | } 86 | 87 | let pathsToReturn = {}; 88 | const awsAcctId = lambdaArn.split(':')[4], 89 | uri = `arn:aws:apigateway:${this.options.region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${this.options.region}:${awsAcctId}:function:${funcNameAndAlias}/invocations`, 90 | template = { 91 | "responses": { 92 | "200": { 93 | "description": "200 response", 94 | "headers": { 95 | "Access-Control-Allow-Origin": { 96 | "type": "string" 97 | } 98 | } 99 | } 100 | }, 101 | "x-amazon-apigateway-integration": { 102 | "httpMethod": "", //POST,GET etc 103 | "uri": "", 104 | "passthroughBehavior": "never", 105 | "responses": { 106 | "default": { 107 | "statusCode": "200", 108 | // "responseParameters": (this.globalSwagConfig.globalCORS) ? {"method.response.header.Access-Control-Allow-Origin": "'" + this.globalSwagConfig.globalCORS.allowOrigins + "'"} : {}, 109 | } 110 | }, 111 | "type": "aws_proxy" 112 | } 113 | }; 114 | 115 | functionObject.events.filter(e => 'swagHttp' in e).map(e => e.swagHttp).forEach(swagEvent => { 116 | //Filter out methods user didn't want to import (if specified) 117 | if (importOnlyTheseMethods.length && -1 != importOnlyTheseMethods.indexOf(swagEvent.method)) { 118 | return; 119 | } 120 | 121 | const path = (0 !== swagEvent.path.indexOf('/')) ? `/${swagEvent.path}` : swagEvent.path, 122 | apigIntegration = { 123 | "uri": uri, 124 | "httpMethod": swagEvent.method.toUpperCase(), 125 | }; 126 | 127 | if (!(path in pathsToReturn)) { 128 | pathsToReturn[path] = {}; 129 | } 130 | 131 | pathsToReturn[path][swagEvent.method.toLowerCase()] = _.extend({}, template, { 132 | "x-amazon-apigateway-integration": apigIntegration 133 | }); 134 | 135 | if (process.env.SLS_DEBUG) { 136 | console.log('Computed APIG integration', apigIntegration); 137 | } 138 | }); 139 | 140 | console.log(pathsToReturn); 141 | 142 | return pathsToReturn; 143 | }); 144 | }, 145 | 146 | // buildPath() { 147 | // let functionObject = this.serverless.service.getFunction(functionName); 148 | // const tmpSwagFile = path.join(os.tmpdir(), +new Date() + '.json'), 149 | // finalSwagFile = this.options.out || path.join(this.serverless.config.servicePath, 'swagger.json'), 150 | // functionBrowserifyConfig = this.getFunctionConfig(functionName), 151 | // finalZipFilePath = path.resolve(path.join(outputDir, '..', `${functionName}.zip`)); 152 | // 153 | // let b = browserify(functionBrowserifyConfig); 154 | // 155 | // this.serverless.cli.log(`Bundling ${functionName} with Browserify...`); 156 | // 157 | // if (process.env.SLS_DEBUG) { 158 | // this.serverless.cli.log(`Writing browserfied bundle to ${outputDir}`); 159 | // } 160 | // 161 | // fs.emptyDirSync(outputDir); 162 | // 163 | // functionBrowserifyConfig.exclude.forEach(file => b.exclude(file)); 164 | // functionBrowserifyConfig.ignore.forEach(file => b.ignore(file)); 165 | // 166 | // return new BbPromise((resolve, reject) => { 167 | // b.bundle((err, bundledBuf) => { 168 | // if (err) { 169 | // return reject(err); 170 | // } 171 | // 172 | // const handlerPath = path.join(outputDir, functionObject.handler.split('.')[0] + '.js'); 173 | // 174 | // fs.mkdirSync(path.dirname(handlerPath), '0777'); //handler may be in a subdir 175 | // fs.writeFile(handlerPath, bundledBuf, (err)=> { 176 | // (err) ? reject(err) : resolve(); 177 | // }); 178 | // }); 179 | // }) 180 | // .then(()=> { 181 | // if (process.env.SLS_DEBUG) { 182 | // this.serverless.cli.log(`Zipping ${outputDir} to ${finalZipFilePath}`); 183 | // } 184 | // 185 | // return zipDir(outputDir, finalZipFilePath); 186 | // }) 187 | // .then((sizeBytes)=> { 188 | // const fileSizeInMegabytes = sizeBytes / 1000000.0; 189 | // this.serverless.cli.log(`Created ${functionName}.zip (${Math.round(fileSizeInMegabytes * 100) / 100} MB)...`); 190 | // 191 | // if (!functionObject.package) { 192 | // functionObject.package = {}; 193 | // } 194 | // 195 | // //This is how we tell Serverless to not do any bunding or zipping 196 | // //@see https://serverless.com/framework/docs/providers/aws/guide/packaging/#artifact 197 | // functionObject.package.artifact = finalZipFilePath; 198 | // }); 199 | // }, 200 | }; 201 | 202 | function generateGlobalCORS(swagConfig) { 203 | ['allowOrigins', 'allowHeaders', 'allowMethods'].forEach(reqAttr => { 204 | if (!swagConfig.globalCORS[reqAttr]) { 205 | throw new this.serverless.classes.Error('allowOrigins, allowHeaders, allowMethods must be specified for swag.globalCORS'); 206 | } 207 | }); 208 | 209 | return { 210 | "options": { 211 | "consumes": [ 212 | APP_JSON_CONTENT_TYPE 213 | ], 214 | 215 | "produces": [ 216 | APP_JSON_CONTENT_TYPE 217 | ], 218 | 219 | "responses": { 220 | "200": SWAG_200_RESPONSE, 221 | "headers": { 222 | "Access-Control-Allow-Origin": { 223 | "type": "string" 224 | }, 225 | "Access-Control-Allow-Methods": { 226 | "type": "string" 227 | }, 228 | "Access-Control-Allow-Headers": { 229 | "type": "string" 230 | } 231 | } 232 | } 233 | 234 | }, 235 | "x-amazon-apigateway-integration": { 236 | "passthroughBehavior": "when_no_match", 237 | 238 | "requestTemplates": { 239 | "application/json": "{\"statusCode\": 200}" 240 | }, 241 | 242 | "responses": { 243 | "default": { 244 | "statusCode": "200", 245 | "responseParameters": { 246 | "method.response.header.Access-Control-Allow-Methods": "'" + swagConfig.globalCORS.allowMethods + "'", 247 | "method.response.header.Access-Control-Allow-Headers": "'" + swagConfig.globalCORS.allowHeaders + "'", 248 | "method.response.header.Access-Control-Allow-Origin": "'" + swagConfig.globalCORS.allowOrigins + "'" 249 | } 250 | } 251 | }, 252 | 253 | "type": "mock" 254 | } 255 | }; 256 | } 257 | 258 | /** 259 | * 260 | * @param provider 261 | * @param functionNameAndAlias echo or echo:prod 262 | * @param stage 263 | * @param region 264 | * @returns {Promise.} function arn 265 | */ 266 | function checkIfLambdaExists(provider, functionNameAndAlias, stage, region) { 267 | const aliasNameOrVer = functionNameAndAlias.split(':')[1] || '', 268 | params = { 269 | FunctionName: functionNameAndAlias, 270 | }; 271 | 272 | if (!!aliasNameOrVer) { 273 | params.Qualifier = aliasNameOrVer; 274 | } 275 | 276 | return provider.request('Lambda', 277 | 'getFunction', 278 | params, 279 | stage, 280 | region) 281 | .then(d => { 282 | return d.Configuration.FunctionArn; 283 | }); 284 | } --------------------------------------------------------------------------------