├── .npmrc ├── .eslintignore ├── .gitignore ├── .travis.yml ├── .eslintrc.yml ├── .editorconfig ├── LICENSE ├── package.json ├── src └── index.js ├── lib └── index.js ├── README.md └── test └── index.spec.js /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | coverage 4 | .nyc_output 5 | 6 | .vscode 7 | yarn-error.log 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "12" 4 | script: npm run build 5 | after_success: npm run ci:coverage 6 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | extends: [standard, plugin:ava/recommended] 3 | plugins: [ava] 4 | rules: 5 | space-before-function-paren: ["error", "never"] 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jacob Meacham 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 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-plugin-bind-deployment-id", 3 | "version": "2.0.3", 4 | "engines": { 5 | "node": ">=12.0" 6 | }, 7 | "description": "Serverless plugin to bind the randomly generated deployment id to custom resources", 8 | "author": "jemonjam ", 9 | "license": "MIT", 10 | "bugs": { 11 | "url": "https://github.com/jacob-meacham/serverless-plugin-bind-deployment-id/issues" 12 | }, 13 | "homepage": "https://github.com/jacob-meacham/serverless-plugin-bind-deployment-id#readme", 14 | "private": false, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/jacob-meacham/serverless-plugin-bind-deployment-id" 18 | }, 19 | "keywords": [ 20 | "serverless", 21 | "serverless applications", 22 | "aws", 23 | "aws lambda", 24 | "amazon", 25 | "amazon web services", 26 | "serverless.com" 27 | ], 28 | "main": "lib/index.js", 29 | "files": [ 30 | "lib", 31 | "src" 32 | ], 33 | "scripts": { 34 | "lint": "eslint .", 35 | "test": "nyc ava", 36 | "test:watch": "ava --watch", 37 | "build:node": "cross-env BABEL_ENV=production babel src --out-dir lib", 38 | "build": "npm run lint && npm run test && npm run build:node", 39 | "ci:coverage": "nyc report --reporter=text-lcov | coveralls" 40 | }, 41 | "dependencies": { 42 | "lodash": "^4.17.15" 43 | }, 44 | "devDependencies": { 45 | "@ava/babel": "1.0.1", 46 | "@babel/cli": "7.12.1", 47 | "@babel/core": "7.12.3", 48 | "@babel/preset-env": "7.12.1", 49 | "@babel/register": "7.12.1", 50 | "ava": "3.13.0", 51 | "coveralls": "3.1.0", 52 | "cross-env": "7.0.2", 53 | "eslint": "7.12.1", 54 | "eslint-config-standard": "16.0.1", 55 | "eslint-plugin-ava": "11.0.0", 56 | "eslint-plugin-import": "2.22.1", 57 | "eslint-plugin-node": "11.1.0", 58 | "eslint-plugin-promise": "4.2.1", 59 | "eslint-plugin-standard": "4.0.2", 60 | "nyc": "15.1.0", 61 | "rimraf": "3.0.2", 62 | "serverless": "2.9.0" 63 | }, 64 | "ava": { 65 | "require": [ 66 | "@babel/register" 67 | ] 68 | }, 69 | "babel": { 70 | "presets": [ 71 | [ 72 | "@babel/preset-env", 73 | { 74 | "targets": { 75 | "node": 12 76 | } 77 | } 78 | ] 79 | ], 80 | "env": { 81 | "development": { 82 | "sourceMaps": "inline" 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | 3 | class BindDeploymentId { 4 | constructor(serverless, options) { 5 | this.serverless = serverless 6 | this.hooks = { 7 | 'before:aws:package:finalize:mergeCustomProviderResources': this.bindDeploymentId.bind(this) 8 | } 9 | } 10 | 11 | bindDeploymentId() { 12 | const template = this.serverless.service.provider.compiledCloudFormationTemplate 13 | 14 | // Find the deployment resource 15 | let deploymentId 16 | for (const key of Object.keys(template.Resources)) { 17 | const resource = template.Resources[key] 18 | if (resource.Type === 'AWS::ApiGateway::Deployment') { 19 | deploymentId = key 20 | break 21 | } 22 | } 23 | 24 | // Now, replace the reference to the deployment id 25 | const resources = _.get(this.serverless.service, 'resources.Resources', null) 26 | if (resources) { 27 | const variableRegex = new RegExp(_.get(this.serverless.service, 'custom.deploymentId.variableSyntax', '__deployment__'), 'g') 28 | this.serverless.service.resources.Resources = this.replaceDeploymentIdReferences(resources, deploymentId, variableRegex) 29 | 30 | const customStages = this.getCustomStages(this.serverless.service) 31 | if (Object.keys(customStages).length > 0) { 32 | // We have custom stages. The deployment will also create a stage, so we'll map 33 | // that to an unused stage instead. The API keys will also need to depend on 34 | // the stage, instead of the deployment 35 | template.Resources = this.fixUpDeploymentStage(template.Resources, deploymentId) 36 | template.Resources = this.fixUpApiKeys(template.Resources, customStages) 37 | } 38 | } 39 | 40 | const resourceExtensions = _.get(this.serverless.service, 'resources.extensions', null) 41 | if (resourceExtensions) { 42 | const variableRegex = new RegExp(_.get(this.serverless.service, 'custom.deploymentId.variableSyntax', '__deployment__'), 'g') 43 | this.serverless.service.resources.extensions = this.replaceDeploymentIdReferences(resourceExtensions, deploymentId, variableRegex) 44 | } 45 | } 46 | 47 | replaceDeploymentIdReferences(resources, deploymentId, variableRegex) { 48 | return JSON.parse(JSON.stringify(resources).replace(variableRegex, deploymentId)) 49 | } 50 | 51 | getCustomStages(service) { 52 | const resources = _.get(service, 'resources.Resources', null) 53 | if (!resources) { 54 | return {} 55 | } 56 | 57 | return _.pickBy(resources, (resource) => { 58 | return resource.Type === 'AWS::ApiGateway::Stage' 59 | }) 60 | } 61 | 62 | fixUpDeploymentStage(resources, deploymentId) { 63 | const newResources = _.cloneDeep(resources) 64 | newResources[deploymentId].Properties.StageName = '__unused_stage__' 65 | 66 | return newResources 67 | } 68 | 69 | fixUpApiKeys(resources, stages) { 70 | const stageKeys = Object.keys(stages) 71 | const stageToDependOn = _.first(stageKeys) 72 | if (stageKeys.length > 1) { 73 | this.serverless.cli.log(`Multiple stages detected. The API keys will depend on ${stageToDependOn}`) 74 | } 75 | 76 | return _.mapValues(resources, (resource) => { 77 | if (resource.Type === 'AWS::ApiGateway::ApiKey') { 78 | return { ...resource, DependsOn: stageToDependOn } 79 | } 80 | return resource 81 | }) 82 | } 83 | } 84 | 85 | exports.default = BindDeploymentId 86 | module.exports = exports.default 87 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _lodash = _interopRequireDefault(require("lodash")); 4 | 5 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 6 | 7 | class BindDeploymentId { 8 | constructor(serverless, options) { 9 | this.serverless = serverless; 10 | this.hooks = { 11 | 'before:aws:package:finalize:mergeCustomProviderResources': this.bindDeploymentId.bind(this) 12 | }; 13 | } 14 | 15 | bindDeploymentId() { 16 | const template = this.serverless.service.provider.compiledCloudFormationTemplate; // Find the deployment resource 17 | 18 | let deploymentId; 19 | 20 | for (const key of Object.keys(template.Resources)) { 21 | const resource = template.Resources[key]; 22 | 23 | if (resource.Type === 'AWS::ApiGateway::Deployment') { 24 | deploymentId = key; 25 | break; 26 | } 27 | } // Now, replace the reference to the deployment id 28 | 29 | 30 | const resources = _lodash.default.get(this.serverless.service, 'resources.Resources', null); 31 | 32 | if (resources) { 33 | const variableRegex = new RegExp(_lodash.default.get(this.serverless.service, 'custom.deploymentId.variableSyntax', '__deployment__'), 'g'); 34 | this.serverless.service.resources.Resources = this.replaceDeploymentIdReferences(resources, deploymentId, variableRegex); 35 | const customStages = this.getCustomStages(this.serverless.service); 36 | 37 | if (Object.keys(customStages).length > 0) { 38 | // We have custom stages. The deployment will also create a stage, so we'll map 39 | // that to an unused stage instead. The API keys will also need to depend on 40 | // the stage, instead of the deployment 41 | template.Resources = this.fixUpDeploymentStage(template.Resources, deploymentId); 42 | template.Resources = this.fixUpApiKeys(template.Resources, customStages); 43 | } 44 | } 45 | 46 | const resourceExtensions = _lodash.default.get(this.serverless.service, 'resources.extensions', null); 47 | 48 | if (resourceExtensions) { 49 | const variableRegex = new RegExp(_lodash.default.get(this.serverless.service, 'custom.deploymentId.variableSyntax', '__deployment__'), 'g'); 50 | this.serverless.service.resources.extensions = this.replaceDeploymentIdReferences(resourceExtensions, deploymentId, variableRegex); 51 | } 52 | } 53 | 54 | replaceDeploymentIdReferences(resources, deploymentId, variableRegex) { 55 | return JSON.parse(JSON.stringify(resources).replace(variableRegex, deploymentId)); 56 | } 57 | 58 | getCustomStages(service) { 59 | const resources = _lodash.default.get(service, 'resources.Resources', null); 60 | 61 | if (!resources) { 62 | return {}; 63 | } 64 | 65 | return _lodash.default.pickBy(resources, resource => { 66 | return resource.Type === 'AWS::ApiGateway::Stage'; 67 | }); 68 | } 69 | 70 | fixUpDeploymentStage(resources, deploymentId) { 71 | const newResources = _lodash.default.cloneDeep(resources); 72 | 73 | newResources[deploymentId].Properties.StageName = '__unused_stage__'; 74 | return newResources; 75 | } 76 | 77 | fixUpApiKeys(resources, stages) { 78 | const stageKeys = Object.keys(stages); 79 | 80 | const stageToDependOn = _lodash.default.first(stageKeys); 81 | 82 | if (stageKeys.length > 1) { 83 | this.serverless.cli.log(`Multiple stages detected. The API keys will depend on ${stageToDependOn}`); 84 | } 85 | 86 | return _lodash.default.mapValues(resources, resource => { 87 | if (resource.Type === 'AWS::ApiGateway::ApiKey') { 88 | return { ...resource, 89 | DependsOn: stageToDependOn 90 | }; 91 | } 92 | 93 | return resource; 94 | }); 95 | } 96 | 97 | } 98 | 99 | exports.default = BindDeploymentId; 100 | module.exports = exports.default; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serverless-plugin-bind-deployment-id 2 | [![Coverage Status](https://coveralls.io/repos/github/jacob-meacham/serverless-plugin-bind-deployment-id/badge.svg?branch=develop)](https://coveralls.io/github/jacob-meacham/serverless-plugin-bind-deployment-id?branch=develop) 3 | [![Build Status](https://travis-ci.org/jacob-meacham/serverless-plugin-bind-deployment-id.svg?branch=develop)](https://travis-ci.org/jacob-meacham/serverless-plugin-bind-deployment-id) 4 | 5 | Bind the serverless deployment to your custom resources like magic! Simply use `__deployment__` in place of anywhere you want the deployment to show up. 6 | 7 | # Usage 8 | ```yaml 9 | 10 | custom: 11 | myVariable: bar 12 | 13 | resources: 14 | Resources: 15 | PathMapping: 16 | Type: AWS::ApiGateway::BasePathMapping 17 | DependsOn: ApiGatewayStage 18 | Properties: 19 | BasePath: basePath 20 | DomainName: ${self:provider.domain} 21 | RestApiId: 22 | Ref: ApiGatewayRestApi 23 | Stage: ${self:provider.stage} 24 | __deployment__: 25 | Properties: 26 | Description: This is my deployment 27 | ApiGatewayStage: 28 | Type: AWS::ApiGateway::Stage 29 | Properties: 30 | DeploymentId: 31 | Ref: __deployment__ 32 | RestApiId: 33 | Ref: ApiGatewayRestApi 34 | StageName : ${self:provider.stage} 35 | MethodSettings: 36 | - DataTraceEnabled: true 37 | HttpMethod: "*" 38 | LoggingLevel: INFO 39 | ResourcePath: "/*" 40 | MetricsEnabled: true 41 | ApiGatewayStage2: 42 | Type: AWS::ApiGateway::Stage 43 | Properties: 44 | DeploymentId: 45 | Ref: __deployment__ 46 | RestApiId: 47 | Ref: ApiGatewayRestApi 48 | StageName : myOtherStage 49 | Variables: [${self:custom.myVariable}] 50 | 51 | plugins: 52 | - serverless-plugin-bind-deployment-id 53 | ``` 54 | 55 | When built, this will merge the custom properties set above with the default CloudFormation template, allowing you to apply custom properties to your Deployment or Stage. This will even allow you to add multiple Stages! 56 | 57 | ### Compatibility Note 58 | If you are using Serverless 0.12+, please use the 1.x.x plugin. For previous Serverless versions, use the 0.1.x plugin. 59 | 60 | ## Advanced Usage 61 | By default `__deployment__` is the sentinel value which is replaced by the API Deployment Id. This is configurable. If you'd like to use a different value, you can set: 62 | 63 | ```yaml 64 | custom: 65 | deploymentId: 66 | variableSyntax: ApiGatewayDeployment 67 | ``` 68 | 69 | In this example, any instance of ApiGatewayDeployment in your custom resources will be replaced with the true deployment Id. 70 | 71 | ## Known Issues 72 | Because the deployment id is not stable across CloudFormation stack updates, you cannot make changes to the default stage with the StageDescription property. If you attempt to do so, you will see an error: 73 | 74 | ``` 75 | An error occurred while provisioning your stack: ApiGatewayDeployment1490846212163 76 | - StageDescription cannot be specified when stage referenced 77 | by StageName already exists. 78 | ``` 79 | 80 | The easiest way to get around this is to leave the default stage unused, and create a new stage that you actually use. By default, we name this default stage __unused_stage__, but you could change it to something else by setting: 81 | 82 | ```yaml 83 | __deployment__: 84 | Properties: 85 | StageName: myUnusedStage 86 | ``` 87 | 88 | ## Release Notes 89 | * 2.0.3 - Dependency security updates 90 | * 2.0.2 - Fix default export breaking change 91 | * 2.0.1 - Remove serverless peer dependency (thanks [@johnmee](https://github.com/johnmee)) 92 | * 2.0.0 - Major dependency upgrades, force node >= 12. 93 | * 1.2.0 - The Serverless framework has added support for the resources.extensions block, which has slightly-more-formalized merge behaviour. 1.2.0 adds support for replacing the deployment ID in the extensions 94 | block. (thanks [@glb](https://github.com/glb)) 95 | * 1.1.1 - Dependency upgrades from dependabot 96 | * 1.1.0 - Dependency upgrade (thanks [@ericsorensen](https://github.com/ericsorenson)) 97 | * 1.0.2 - Update lodash to fix known security issue (thanks [@brownjava](https://github.com/brownjava)) 98 | * 1.0.1 - Fix peer dependency 99 | * 1.0.0 - Fix an incompatibility with serverless 1.12 100 | * 0.1.0 - Initial release 101 | 102 | ## Contributors 103 | * [Jacob Meacham](https://github.com/jacob-meacham) 104 | * [ajkerr](https://github.com/ajkerr) 105 | -------------------------------------------------------------------------------- /test/index.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import _ from 'lodash' 3 | 4 | import BindDeploymentId from '../src' 5 | 6 | function buildServerless() { 7 | return { 8 | cli: { log: console.log }, 9 | service: { 10 | provider: { }, 11 | custom: { 12 | bindDeploymentId: { } 13 | } 14 | } 15 | } 16 | } 17 | 18 | function defaultCompiledCloudFormation() { 19 | return { 20 | AWSTemplateFormatVersion: '2010-09-09', 21 | Resources: { 22 | ApiGatewayRestApi: { 23 | Type: 'AWS::ApiGateway::RestApi', 24 | Properties: { 25 | Name: 'test-api' 26 | } 27 | }, 28 | ApiGatewayDeployment1484416530047: { 29 | Type: 'AWS::ApiGateway::Deployment', 30 | Properties: { 31 | RestApiId: { 32 | Ref: 'ApiGatewayRestApi' 33 | }, 34 | StageName: 'staging' 35 | }, 36 | DependsOn: ['a', 'b', 'c'] 37 | } 38 | } 39 | } 40 | } 41 | 42 | test('bindDeploymentId#noCustomResources', t => { 43 | const serverless = buildServerless() 44 | serverless.service.provider.compiledCloudFormationTemplate = defaultCompiledCloudFormation() 45 | const originalTemplate = _.cloneDeep(serverless.service.provider.compiledCloudFormationTemplate) 46 | 47 | const plugin = new BindDeploymentId(serverless, {}) 48 | plugin.bindDeploymentId() 49 | t.deepEqual(serverless.service.provider.compiledCloudFormationTemplate, originalTemplate) 50 | }) 51 | 52 | test('bindDeploymentId#default', t => { 53 | const serverless = buildServerless() 54 | serverless.service.provider.compiledCloudFormationTemplate = defaultCompiledCloudFormation() 55 | serverless.service.resources = { 56 | Resources: { 57 | ApiGatewayStage: { 58 | DependsOn: '__deployment__', 59 | Type: 'AWS::ApiGateway::Stage', 60 | Properties: { 61 | MethodSettings: [] 62 | } 63 | }, 64 | __deployment__: { 65 | Properties: { 66 | DataTraceEnabled: true, 67 | MetricsEnabled: true 68 | } 69 | } 70 | } 71 | } 72 | 73 | const plugin = new BindDeploymentId(serverless, {}) 74 | plugin.bindDeploymentId() 75 | 76 | t.is(serverless.service.provider.compiledCloudFormationTemplate.Resources.ApiGatewayDeployment1484416530047.Properties.StageName, '__unused_stage__') 77 | t.is(serverless.service.resources.Resources.ApiGatewayStage.DependsOn, 'ApiGatewayDeployment1484416530047') 78 | t.deepEqual(serverless.service.resources.Resources.ApiGatewayDeployment1484416530047, { 79 | Properties: { 80 | DataTraceEnabled: true, 81 | MetricsEnabled: true 82 | } 83 | }) 84 | }) 85 | 86 | test('bindDeploymentId#replacesExtensions', t => { 87 | const serverless = buildServerless() 88 | serverless.service.provider.compiledCloudFormationTemplate = defaultCompiledCloudFormation() 89 | serverless.service.resources = { 90 | extensions: { 91 | __deployment__: { 92 | DependsOn: [ 93 | 'sample' 94 | ] 95 | } 96 | } 97 | } 98 | 99 | const plugin = new BindDeploymentId(serverless, {}) 100 | plugin.bindDeploymentId() 101 | 102 | t.deepEqual(serverless.service.resources.extensions.ApiGatewayDeployment1484416530047, { 103 | DependsOn: [ 104 | 'sample' 105 | ] 106 | }) 107 | }) 108 | 109 | test('bindDeploymentId#noCustomStages', t => { 110 | const serverless = buildServerless() 111 | serverless.service.provider.compiledCloudFormationTemplate = defaultCompiledCloudFormation() 112 | const originalTemplate = _.cloneDeep(serverless.service.provider.compiledCloudFormationTemplate) 113 | 114 | serverless.service.resources = { 115 | Resources: { 116 | __deployment__: { 117 | Properties: { 118 | DataTraceEnabled: true, 119 | MetricsEnabled: true 120 | } 121 | } 122 | } 123 | } 124 | 125 | const plugin = new BindDeploymentId(serverless, {}) 126 | plugin.bindDeploymentId() 127 | 128 | t.deepEqual(serverless.service.provider.compiledCloudFormationTemplate, originalTemplate) 129 | }) 130 | 131 | test('replaceDeploymentIdReferences#references', t => { 132 | const plugin = new BindDeploymentId(buildServerless(), {}) 133 | 134 | const toReplace = { 135 | __deployment__: { 136 | a: [], 137 | b: { 138 | c: ['__deployment__'] 139 | } 140 | } 141 | } 142 | 143 | const expected = { 144 | foo: { 145 | a: [], 146 | b: { 147 | c: ['foo'] 148 | } 149 | } 150 | } 151 | 152 | const actual = plugin.replaceDeploymentIdReferences(toReplace, 'foo', /__deployment__/g) 153 | 154 | t.deepEqual(actual, expected) 155 | }) 156 | 157 | test('fixUpApiKeys#oneStage', t => { 158 | const plugin = new BindDeploymentId(buildServerless(), {}) 159 | 160 | const resources = { 161 | A: { 162 | Type: 'AWS::ApiGateway::ApiKey', 163 | Properties: { }, 164 | DependsOn: 'DeploymentId' 165 | }, 166 | B: { 167 | Type: 'AWS::ApiGateway::ApiStage', 168 | Properties: { } 169 | } 170 | } 171 | 172 | const stages = { 173 | StageA: { 174 | Type: 'AWS::ApiGateway::ApiStage' 175 | } 176 | } 177 | 178 | const fixedUpResources = plugin.fixUpApiKeys(resources, stages) 179 | t.is(fixedUpResources.A.DependsOn, 'StageA') 180 | }) 181 | 182 | test('fixUpApiKeys#multipleStages', t => { 183 | const plugin = new BindDeploymentId(buildServerless(), {}) 184 | 185 | const resources = { 186 | A: { 187 | Type: 'AWS::ApiGateway::ApiKey', 188 | Properties: { }, 189 | DependsOn: 'DeploymentId' 190 | }, 191 | B: { 192 | Type: 'AWS::ApiGateway::ApiStage', 193 | Properties: { } 194 | } 195 | } 196 | 197 | const stages = { 198 | StageA: { 199 | Type: 'AWS::ApiGateway::ApiStage' 200 | }, 201 | AStageB: { 202 | Type: 'AWS::ApiGateway::ApiStage' 203 | } 204 | } 205 | 206 | const fixedUpResources = plugin.fixUpApiKeys(resources, stages) 207 | t.is(fixedUpResources.A.DependsOn, 'StageA') 208 | }) 209 | 210 | test('getCustomStages#noResources', t => { 211 | const serverless = buildServerless() 212 | const plugin = new BindDeploymentId(serverless, {}) 213 | 214 | t.deepEqual(plugin.getCustomStages(serverless.service), {}) 215 | }) 216 | 217 | test('getCustomStages#noStages', t => { 218 | const serverless = buildServerless() 219 | serverless.service.resources = { 220 | Resources: { 221 | A: { 222 | Type: 'AWS::ApiGateway::ApiKey' 223 | }, 224 | B: { 225 | Type: 'AWS::ApiGateway::Deployment' 226 | } 227 | } 228 | } 229 | const plugin = new BindDeploymentId(serverless, {}) 230 | 231 | t.deepEqual(plugin.getCustomStages(serverless.service), {}) 232 | }) 233 | 234 | test('getCustomStages#stages', t => { 235 | const serverless = buildServerless() 236 | const expectedStages = { 237 | A: { 238 | Type: 'AWS::ApiGateway::Stage', 239 | Properties: {} 240 | }, 241 | C: { 242 | Type: 'AWS::ApiGateway::Stage', 243 | Properties: {} 244 | } 245 | } 246 | 247 | serverless.service.resources = { 248 | Resources: { ...expectedStages, B: { Type: 'AWS::ApiGateway::Deployment' } } 249 | } 250 | 251 | const plugin = new BindDeploymentId(serverless, {}) 252 | 253 | const stages = plugin.getCustomStages(serverless.service) 254 | t.deepEqual(stages, expectedStages) 255 | }) 256 | --------------------------------------------------------------------------------