├── .gitignore ├── example ├── customs │ └── development.yml ├── handler.js ├── serverless.yml └── .serverless_plugins │ └── serverless-ssm-fetch.js ├── package.json ├── LICENSE ├── README.md ├── src └── serverless-ssm-fetch.js └── dist └── serverless-ssm-fetch.js /.gitignore: -------------------------------------------------------------------------------- 1 | # package directories 2 | node_modules 3 | 4 | # Serverless directories 5 | .idea 6 | /example/.serverless/ 7 | .npmrc 8 | .DS_Store 9 | serverless-ssm-fetch-*.tgz 10 | -------------------------------------------------------------------------------- /example/customs/development.yml: -------------------------------------------------------------------------------- 1 | env_vars: 2 | NODE_ENV: "development" 3 | serverlessSsmFetch: 4 | APP_ID: /aws/ssm/parameter/path/app_id 5 | APP_KEY: /aws/ssm/parameter/path/app_key 6 | APP_SECRET: /aws/ssm/parameter/path/app_secret~true -------------------------------------------------------------------------------- /example/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports.hello = (event, context, callback) => { 4 | const response = { 5 | statusCode: 200, 6 | body: JSON.stringify({ 7 | message: 'Go Serverless v1.0! Your function executed successfully!', 8 | input: event, 9 | }), 10 | }; 11 | 12 | callback(null, response); 13 | 14 | // Use this code if you don't use the http event with the LAMBDA-PROXY integration 15 | // callback(null, { message: 'Go Serverless v1.0! Your function executed successfully!', event }); 16 | }; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-ssm-fetch", 3 | "version": "2.0.0", 4 | "main": "dist/serverless-ssm-fetch.js", 5 | "description": "Sets \"AWS Systems Manager Parameter Store (SSM)\" parameters into functions' environment variables.", 6 | "scripts": { 7 | "babel": "babel --presets env src/serverless-ssm-fetch.js --out-file dist/serverless-ssm-fetch.js", 8 | "upgrade-interactive": "npm-check --update" 9 | }, 10 | "dependencies": { 11 | "aws-sdk": "^2.1046.0", 12 | "bluebird": "^3.7.2" 13 | }, 14 | "peerDependencies": { 15 | "serverless": "^3.0.0" 16 | }, 17 | "devDependencies": { 18 | "babel-cli": "^6.26.0", 19 | "babel-preset-env": "^1.6.0", 20 | "npm-check": "^5.9.2" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/gozup/serverless-ssm-fetch.git" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/serverless.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Serverless! 2 | # 3 | # This file is the main config file for your service. 4 | # It's very minimal at this point and uses default values. 5 | # You can always add more config options for more control. 6 | # We've included some commented out config examples here. 7 | # Just uncomment any of them to get that config option. 8 | # 9 | # For full config options, check the docs: 10 | # docs.serverless.com 11 | # 12 | # Happy Coding! 13 | 14 | service: aws-nodejs # NOTE: update this with your service name 15 | 16 | # You can pin your service to only deploy with a specific Serverless version 17 | # Check out our docs for more details 18 | frameworkVersion: "=1.23.0" 19 | 20 | custom: ${file(./customs/development.yml)} 21 | 22 | provider: 23 | name: aws 24 | runtime: nodejs6.10 25 | stage: dev 26 | region: eu-central-1 27 | 28 | functions: 29 | hello: 30 | handler: handler.hello 31 | ssmToEnvironment: 32 | - APP_SECRET 33 | - APP_KEY 34 | environment: ${self:custom.env_vars} 35 | 36 | plugins: 37 | - serverless-ssm-fetch 38 | 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright © 2017 Emmanuel Lemoine 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Serverless SSM Fetch** 2 | ================ 3 | 4 | Serverless SSM Fetch is an **"AWS provider only"** plugin that allows to fetch parameters from AWS Store Parameters and assign them to serverless.yml functions environment variables. 5 | 6 | Before using this plugin you must have set your parameters into [AWS System Manager Parameter Store](http://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-paramstore.html) 7 | 8 | **NOTE**: to use this plugin, the AWS credentials that you use for your project must have permissions for: 9 | * ssm:Describe* 10 | * ssm:Get* 11 | * ssm:List* 12 | 13 | ## Versions 14 | 15 | * For serverless framework **v2**: use serverless-ssm-fetch@**1.x** 16 | * For serverless framework **v3**: use serverless-ssm-fetch@**2.x** 17 | 18 | 19 | ## Setup 20 | 21 | First, you have to set `serverless-ssm-fetch` plugin by running: 22 | ``` 23 | serverless plugin install --name serverless-ssm-fetch 24 | ``` 25 | 26 | This will install the required npm package and add the plugin to your `serverless.yml` file. 27 | ```yaml 28 | ... 29 | 30 | plugins: 31 | - serverless-ssm-fetch 32 | 33 | ... 34 | ``` 35 | 36 | Then, you must declare the SSM Parameters that must be assigned to your functions environment variables. 37 | 38 | It consists in key value pairs, where the key is the environment variable name you want to use, and the value is the parameter path you set in AWS System Manager Parameter Store on your AWS account. Example: `APP_ID: /aws/ssm/parameter/path/app_id`. 39 | 40 | To declare them, use the `serverlessSsmFetch` accessor inside `custom` variable in your `serverless.yml` file. FYI, it also works if you use a nested file for your `custom` (`custom: $(file:./path/to/file)`). 41 | ```yaml 42 | ... 43 | 44 | custom: 45 | serverlessSsmFetch: 46 | APP_ID: /aws/ssm/parameter/path/app_id 47 | APP_KEY: /aws/ssm/parameter/path/app_key 48 | APP_SECRET: /aws/ssm/parameter/path/app_secret~true 49 | 50 | ... 51 | ``` 52 | 53 | ## Usage 54 | 55 | By default, there is nothing more to do if you want *all* your SSM Parameters injected in *all* your functions. 56 | But of course, you will probably want to assign specific parameters to specific functions. You can specify it on a per function basis this way: 57 | ```yaml 58 | ... 59 | 60 | custom: 61 | serverlessSsmFetch: 62 | APP_ID: /aws/ssm/parameter/path/app_id 63 | APP_KEY: /aws/ssm/parameter/path/app_key 64 | APP_SECRET: /aws/ssm/parameter/path/app_secret~true 65 | 66 | functions: 67 | hello: 68 | handler: handler.hello 69 | ssmToEnvironment: 70 | - APP_ID 71 | environment: 72 | - NODE_ENV: development 73 | 74 | ... 75 | ``` 76 | This will add *ONLY* the SSM Parameter `APP_ID` to the function environment variables. In this case, the function `hello` will be pushed to AWS Lambda with environment variables `NODE_ENV` and `APP_ID`. 77 | 78 | ## Decryption 79 | 80 | On AWS Parameters Store you can decide to encrypt parameters when you set them. Meaning that you must decrypt them with your KMS key when you use them within your application. 81 | 82 | With `serverless-ssm-fetch` you can decide to decrypt them at runtime to add the decrypted format of the parameter to your environment variable. It is useful if you don't use an AWS KMS key to decrypt parameters within your app. 83 | 84 | To decrypt a parameter you just have to add `~true` at the end of your AWS SSM Parameter path: 85 | ```yaml 86 | ... 87 | 88 | custom: 89 | serverlessSsmFetch: 90 | APP_SECRET: /aws/ssm/parameter/path/app_secret~true 91 | 92 | ... 93 | ``` 94 | 95 | 96 | ## Licensing 97 | 98 | MIT License - Copyright © 2017 Emmanuel Lemoine 99 | -------------------------------------------------------------------------------- /src/serverless-ssm-fetch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as BbPromise from 'bluebird'; 4 | import AWS from 'aws-sdk'; 5 | 6 | class SsmFetch { 7 | 8 | constructor(serverless, options, { log }) { 9 | this.serverless = serverless; 10 | this.options = options; 11 | 12 | serverless.configSchemaHandler.defineFunctionProperties('aws', { 13 | properties: { 14 | ssmToEnvironment: { type: 'array' } 15 | }, 16 | }); 17 | 18 | this.validate(); 19 | 20 | this.commands = { 21 | 'serverless-ssm-fetch': { 22 | commands: { 23 | parameter: { 24 | usage: 'Internal use only!', 25 | lifecycleEvents: [ 26 | 'validate', 27 | 'get' 28 | ] 29 | } 30 | } 31 | } 32 | }; 33 | 34 | this.hooks = { 35 | 'after:package:cleanup': () => { 36 | this._triggeredFromHook = true; 37 | return this.serverless.pluginManager.run(['serverless-ssm-fetch', 'parameter']) 38 | }, 39 | 'serverless-ssm-fetch:parameter:validate': () => this._triggeredFromHook ? BbPromise.resolve() : BbPromise.reject(new Error('Internal use only')), 40 | 'serverless-ssm-fetch:parameter:get': () => BbPromise.bind(this) 41 | .then(() => this.getParameter(log)) 42 | .then(() => this.assignParameter(log)) 43 | } 44 | } 45 | 46 | getParameter(log) { 47 | 48 | return new Promise((resolve, reject) => { 49 | 50 | log.info('> serverless-ssm-fetch: Get parameters...'); 51 | 52 | // Instantiate an AWS.SSM client() 53 | let ssmClient = new AWS.SSM({region: this.serverless.service.provider.region}); 54 | 55 | // Get the SSM Parameters set in serverless.yml 56 | let ssmParameters = this.serverless.service.custom['serverlessSsmFetch']; 57 | 58 | // Init an empty collection of Promises that will be populated by 59 | // the needed calls to AWS.SSM to get all parameters 60 | let promiseCollection = []; 61 | 62 | // Make this as self to access it from the following promise 63 | let self = this; 64 | 65 | // Init serverless variable that will store fetched data 66 | this.serverless.serverlessSsmFetch = {}; 67 | 68 | // For each SSM parameters to retrieve 69 | Object.keys(ssmParameters).forEach((parameter) => { 70 | 71 | // Populate promiseCollection with the request to do to AWS.SSM 72 | promiseCollection.push(new Promise((resolve, reject) => { 73 | 74 | // Splits the parameter string to check if encryption is needed or not 75 | let splitParameterEncryptionOption = ssmParameters[parameter].split('~'); 76 | 77 | // Builds AWS.SSM request payload 78 | let params = { 79 | Name: splitParameterEncryptionOption[0], 80 | WithDecryption: (splitParameterEncryptionOption[1] == 'true') 81 | }; 82 | 83 | // Triggers the `getParameter`request to AWS.SSM 84 | ssmClient.getParameter(params, function (err, data) { 85 | log.info(`> serverless-ssm-fetch: Fetching "${parameter}: ${ssmParameters[parameter]}"...`); 86 | if (err) { 87 | log.error(`> serverless-ssm-fetch: ${err}`); 88 | reject(err); 89 | } else { 90 | self.serverless.serverlessSsmFetch[parameter] = data.Parameter.Value; 91 | resolve(data); 92 | } 93 | }); 94 | 95 | })); 96 | 97 | }); 98 | 99 | // Triggers all `getParameter` queries concurrently 100 | Promise.all(promiseCollection) 101 | .then((success) => { 102 | log.info('> serverless-ssm-fetch: Get parameters success. Fetched SSM parameters:'); 103 | log.info(JSON.stringify(Object.keys(this.serverless.serverlessSsmFetch), null, 2)); 104 | return resolve(success); 105 | }) 106 | .catch((error) => { 107 | log.error('> serverless-ssm-fetch: Get parameter: ERROR'); 108 | log.error(error); 109 | return reject(error); 110 | }); 111 | 112 | }); 113 | 114 | } 115 | 116 | assignParameter(log) { 117 | 118 | // forEach function to deploy 119 | Object.keys(this.serverless.service.functions).forEach((functionName) => { 120 | // Aliases of the current function path and the got ssm parameters path 121 | let currentFunction = this.serverless.service.functions[functionName]; 122 | let fetchedSsmParameters = this.serverless.serverlessSsmFetch; 123 | 124 | if (this.isSet(currentFunction.ssmToEnvironment)) { 125 | // If the property `ssmToEnvironment` has been set at the function level 126 | 127 | // Creates the function `environment` property if it doesn't already exist 128 | if (!this.isSet(currentFunction.environment)) { 129 | currentFunction.environment = {}; 130 | } 131 | 132 | // forEach ssmParameter assigned over `ssmToEnvironment` function property... 133 | currentFunction.ssmToEnvironment.forEach((ssmParameterToAssign) => { 134 | if (this.isSet(fetchedSsmParameters[ssmParameterToAssign])) { 135 | // merges it into the function `environment` property 136 | currentFunction.environment[ssmParameterToAssign] = fetchedSsmParameters[ssmParameterToAssign]; 137 | } 138 | }) 139 | 140 | } else { 141 | // Else, the property `ssmToEnvironment` has NOT been set at the function level 142 | 143 | // Creates the function `environment` property if it doesn't already exist 144 | if (!this.isSet(currentFunction.environment)) { 145 | currentFunction.environment = {}; 146 | } 147 | 148 | // Merges ALL the fetched ssmParameters 149 | Object.keys(fetchedSsmParameters).forEach((ssmParameterToAssign) => { 150 | currentFunction.environment[ssmParameterToAssign] = fetchedSsmParameters[ssmParameterToAssign]; 151 | }) 152 | 153 | } 154 | 155 | log.info(`> serverless-ssm-fetch: Function "${functionName}" set environment variables:`); 156 | log.info(JSON.stringify(Object.keys(currentFunction.environment), null, 2)); 157 | 158 | }); 159 | 160 | } 161 | 162 | validate() { 163 | setTimeout(() => { 164 | if (this.serverless.service.provider.name !== 'aws') { 165 | throw new this.serverless.classes.Error('> serverless-ssm-fetch: The plugin "serverless-ssm-fetch" is only available for `aws` provider.'); 166 | } 167 | 168 | if (!this.isSet(this.serverless.service.custom) || !this.isSet(this.serverless.service.custom['serverlessSsmFetch'])) { 169 | throw new this.serverless.classes.Error('> serverless-ssm-fetch: You are using the plugin "serverless-ssm-fetch". You must set a `custom.serverlessSsmFetch` element in your serverless conf file.'); 170 | } 171 | }, 5000); 172 | } 173 | 174 | isSet(attribute) { 175 | let bool = true; 176 | if (typeof attribute === 'undefined' || attribute === null) { 177 | bool = false; 178 | } 179 | return bool; 180 | } 181 | 182 | 183 | } 184 | 185 | module.exports = SsmFetch; 186 | -------------------------------------------------------------------------------- /example/.serverless_plugins/serverless-ssm-fetch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 4 | 5 | var _bluebird = require('bluebird'); 6 | 7 | var BbPromise = _interopRequireWildcard(_bluebird); 8 | 9 | var _awsSdk = require('aws-sdk'); 10 | 11 | var _awsSdk2 = _interopRequireDefault(_awsSdk); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 16 | 17 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 18 | 19 | var SsmFetch = function () { 20 | function SsmFetch(serverless, options) { 21 | var _this = this; 22 | 23 | _classCallCheck(this, SsmFetch); 24 | 25 | this.serverless = serverless; 26 | this.options = options; 27 | 28 | this.validate(); 29 | 30 | this.commands = { 31 | 'serverless-ssm-fetch': { 32 | commands: { 33 | parameter: { 34 | usage: 'Internal use only!', 35 | lifecycleEvents: ['validate', 'get'] 36 | } 37 | } 38 | } 39 | }; 40 | 41 | this.hooks = { 42 | 'after:package:cleanup': function afterPackageCleanup() { 43 | _this._triggeredFromHook = true; 44 | return _this.serverless.pluginManager.run(['serverless-ssm-fetch', 'parameter']); 45 | }, 46 | 'serverless-ssm-fetch:parameter:validate': function serverlessSsmFetchParameterValidate() { 47 | return _this._triggeredFromHook ? BbPromise.resolve() : BbPromise.reject(new Error('Internal use only')); 48 | }, 49 | 'serverless-ssm-fetch:parameter:get': function serverlessSsmFetchParameterGet() { 50 | return BbPromise.bind(_this).then(_this.getParameter).then(_this.assignParameter); 51 | } 52 | }; 53 | } 54 | 55 | _createClass(SsmFetch, [{ 56 | key: 'getParameter', 57 | value: function getParameter() { 58 | var _this2 = this; 59 | 60 | return new Promise(function (resolve, reject) { 61 | 62 | _this2.serverless.cli.log('> serverless-ssm-fetch: Get parameters...'); 63 | 64 | // Instantiate an AWS.SSM client() 65 | var ssmClient = new _awsSdk2.default.SSM({ region: _this2.serverless.service.provider.region }); 66 | 67 | // Get the SSM Parameters set in serverless.yml 68 | var ssmParameters = _this2.serverless.service.custom['serverlessSsmFetch']; 69 | 70 | // Init an empty collection of Promises that will be populated by 71 | // the needed calls to AWS.SSM to get all parameters 72 | var promiseCollection = []; 73 | 74 | // Make this as self to access it from the following promise 75 | var self = _this2; 76 | 77 | // Init serverless variable that will store fetched data 78 | _this2.serverless.variables.serverlessSsmFetch = {}; 79 | 80 | // For each SSM parameters to retrieve 81 | Object.keys(ssmParameters).forEach(function (parameter) { 82 | 83 | // Populate promiseCollection with the request to do to AWS.SSM 84 | promiseCollection.push(new Promise(function (resolve, reject) { 85 | 86 | // Splits the parameter string to check if encryption is needed or not 87 | var splitParameterEncryptionOption = ssmParameters[parameter].split('~'); 88 | 89 | // Builds AWS.SSM request payload 90 | var params = { 91 | Name: splitParameterEncryptionOption[0], 92 | WithDecryption: splitParameterEncryptionOption[1] == 'true' 93 | }; 94 | 95 | // Triggers the `getParameter`request to AWS.SSM 96 | ssmClient.getParameter(params, function (err, data) { 97 | self.serverless.cli.log('> serverless-ssm-fetch: Fetching "' + parameter + ': ' + ssmParameters[parameter] + '" ...'); 98 | if (err) { 99 | self.serverless.cli.log('> serverless-ssm-fetch: ' + err); 100 | reject(err); 101 | } else { 102 | self.serverless.variables.serverlessSsmFetch[parameter] = data.Parameter.Value; 103 | resolve(data); 104 | } 105 | }); 106 | })); 107 | }); 108 | 109 | // Triggers all `getParameter` queries concurrently 110 | Promise.all(promiseCollection).then(function (success) { 111 | _this2.serverless.cli.log('> serverless-ssm-fetch: Get parameters success. Fetched SSM parameters:'); 112 | _this2.serverless.cli.log(JSON.stringify(Object.keys(_this2.serverless.variables.serverlessSsmFetch))); 113 | return resolve(success); 114 | }).catch(function (error) { 115 | _this2.serverless.cli.log('> serverless-ssm-fetch: Get parameter: ERROR'); 116 | _this2.serverless.cli.log(error); 117 | return reject(error); 118 | }); 119 | }); 120 | } 121 | }, { 122 | key: 'assignParameter', 123 | value: function assignParameter() { 124 | var _this3 = this; 125 | 126 | // forEach function to deploy 127 | Object.keys(this.serverless.service.functions).forEach(function (functionName) { 128 | // Aliases of the current function path and the got ssm parameters path 129 | var currentFunction = _this3.serverless.service.functions[functionName]; 130 | var fetchedSsmParameters = _this3.serverless.variables.serverlessSsmFetch; 131 | 132 | if (_this3.isSet(currentFunction.ssmToEnvironment)) { 133 | // If the property `ssmToEnvironment` has been set at the function level 134 | 135 | // Creates the function `environment` property if it doesn't already exist 136 | if (!_this3.isSet(currentFunction.environment)) { 137 | currentFunction.environment = {}; 138 | } 139 | 140 | // forEach ssmParameter assigned over `ssmToEnvironment` function property... 141 | currentFunction.ssmToEnvironment.forEach(function (ssmParameterToAssign) { 142 | if (_this3.isSet(fetchedSsmParameters[ssmParameterToAssign])) { 143 | // merges it into the function `environment` property 144 | currentFunction.environment[ssmParameterToAssign] = fetchedSsmParameters[ssmParameterToAssign]; 145 | } 146 | }); 147 | } else { 148 | // Else, the property `ssmToEnvironment` has NOT been set at the function level 149 | 150 | // Creates the function `environment` property if it doesn't already exist 151 | if (!_this3.isSet(currentFunction.environment)) { 152 | currentFunction.environment = {}; 153 | } 154 | 155 | // Merges ALL the fetched ssmParameters 156 | Object.keys(fetchedSsmParameters).forEach(function (ssmParameterToAssign) { 157 | currentFunction.environment[ssmParameterToAssign] = fetchedSsmParameters[ssmParameterToAssign]; 158 | }); 159 | } 160 | 161 | _this3.serverless.cli.log('> serverless-ssm-fetch: Function "' + functionName + '" set environment variables: ' + JSON.stringify(Object.keys(currentFunction.environment))); 162 | }); 163 | } 164 | }, { 165 | key: 'validate', 166 | value: function validate() { 167 | var _this4 = this; 168 | 169 | setTimeout(function () { 170 | if (_this4.serverless.service.provider.name !== 'aws') { 171 | throw new _this4.serverless.classes.Error('> serverless-ssm-fetch: The plugin "serverless-ssm-fetch" is only available for `aws` provider.'); 172 | } 173 | 174 | if (!_this4.isSet(_this4.serverless.service.custom) || !_this4.isSet(_this4.serverless.service.custom['serverlessSsmFetch'])) { 175 | throw new _this4.serverless.classes.Error('> serverless-ssm-fetch: You are using the plugin "serverless-ssm-fetch". You must set a `custom.serverlessSsmFetch` element in your serverless conf file.'); 176 | } 177 | }, 5000); 178 | } 179 | }, { 180 | key: 'isSet', 181 | value: function isSet(attribute) { 182 | var bool = true; 183 | if (typeof attribute === 'undefined' || attribute === null) { 184 | bool = false; 185 | } 186 | return bool; 187 | } 188 | }]); 189 | 190 | return SsmFetch; 191 | }(); 192 | 193 | module.exports = SsmFetch; 194 | -------------------------------------------------------------------------------- /dist/serverless-ssm-fetch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 4 | 5 | var _bluebird = require('bluebird'); 6 | 7 | var BbPromise = _interopRequireWildcard(_bluebird); 8 | 9 | var _awsSdk = require('aws-sdk'); 10 | 11 | var _awsSdk2 = _interopRequireDefault(_awsSdk); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } 16 | 17 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 18 | 19 | var SsmFetch = function () { 20 | function SsmFetch(serverless, options, _ref) { 21 | var _this = this; 22 | 23 | var log = _ref.log; 24 | 25 | _classCallCheck(this, SsmFetch); 26 | 27 | this.serverless = serverless; 28 | this.options = options; 29 | 30 | serverless.configSchemaHandler.defineFunctionProperties('aws', { 31 | properties: { 32 | ssmToEnvironment: { type: 'array' } 33 | } 34 | }); 35 | 36 | this.validate(); 37 | 38 | this.commands = { 39 | 'serverless-ssm-fetch': { 40 | commands: { 41 | parameter: { 42 | usage: 'Internal use only!', 43 | lifecycleEvents: ['validate', 'get'] 44 | } 45 | } 46 | } 47 | }; 48 | 49 | this.hooks = { 50 | 'after:package:cleanup': function afterPackageCleanup() { 51 | _this._triggeredFromHook = true; 52 | return _this.serverless.pluginManager.run(['serverless-ssm-fetch', 'parameter']); 53 | }, 54 | 'serverless-ssm-fetch:parameter:validate': function serverlessSsmFetchParameterValidate() { 55 | return _this._triggeredFromHook ? BbPromise.resolve() : BbPromise.reject(new Error('Internal use only')); 56 | }, 57 | 'serverless-ssm-fetch:parameter:get': function serverlessSsmFetchParameterGet() { 58 | return BbPromise.bind(_this).then(function () { 59 | return _this.getParameter(log); 60 | }).then(function () { 61 | return _this.assignParameter(log); 62 | }); 63 | } 64 | }; 65 | } 66 | 67 | _createClass(SsmFetch, [{ 68 | key: 'getParameter', 69 | value: function getParameter(log) { 70 | var _this2 = this; 71 | 72 | return new Promise(function (resolve, reject) { 73 | 74 | log.info('> serverless-ssm-fetch: Get parameters...'); 75 | 76 | // Instantiate an AWS.SSM client() 77 | var ssmClient = new _awsSdk2.default.SSM({ region: _this2.serverless.service.provider.region }); 78 | 79 | // Get the SSM Parameters set in serverless.yml 80 | var ssmParameters = _this2.serverless.service.custom['serverlessSsmFetch']; 81 | 82 | // Init an empty collection of Promises that will be populated by 83 | // the needed calls to AWS.SSM to get all parameters 84 | var promiseCollection = []; 85 | 86 | // Make this as self to access it from the following promise 87 | var self = _this2; 88 | 89 | // Init serverless variable that will store fetched data 90 | _this2.serverless.serverlessSsmFetch = {}; 91 | 92 | // For each SSM parameters to retrieve 93 | Object.keys(ssmParameters).forEach(function (parameter) { 94 | 95 | // Populate promiseCollection with the request to do to AWS.SSM 96 | promiseCollection.push(new Promise(function (resolve, reject) { 97 | 98 | // Splits the parameter string to check if encryption is needed or not 99 | var splitParameterEncryptionOption = ssmParameters[parameter].split('~'); 100 | 101 | // Builds AWS.SSM request payload 102 | var params = { 103 | Name: splitParameterEncryptionOption[0], 104 | WithDecryption: splitParameterEncryptionOption[1] == 'true' 105 | }; 106 | 107 | // Triggers the `getParameter`request to AWS.SSM 108 | ssmClient.getParameter(params, function (err, data) { 109 | log.info('> serverless-ssm-fetch: Fetching "' + parameter + ': ' + ssmParameters[parameter] + '"...'); 110 | if (err) { 111 | log.error('> serverless-ssm-fetch: ' + err); 112 | reject(err); 113 | } else { 114 | self.serverless.serverlessSsmFetch[parameter] = data.Parameter.Value; 115 | resolve(data); 116 | } 117 | }); 118 | })); 119 | }); 120 | 121 | // Triggers all `getParameter` queries concurrently 122 | Promise.all(promiseCollection).then(function (success) { 123 | log.info('> serverless-ssm-fetch: Get parameters success. Fetched SSM parameters:'); 124 | log.info(JSON.stringify(Object.keys(_this2.serverless.serverlessSsmFetch), null, 2)); 125 | return resolve(success); 126 | }).catch(function (error) { 127 | log.error('> serverless-ssm-fetch: Get parameter: ERROR'); 128 | log.error(error); 129 | return reject(error); 130 | }); 131 | }); 132 | } 133 | }, { 134 | key: 'assignParameter', 135 | value: function assignParameter(log) { 136 | var _this3 = this; 137 | 138 | // forEach function to deploy 139 | Object.keys(this.serverless.service.functions).forEach(function (functionName) { 140 | // Aliases of the current function path and the got ssm parameters path 141 | var currentFunction = _this3.serverless.service.functions[functionName]; 142 | var fetchedSsmParameters = _this3.serverless.serverlessSsmFetch; 143 | 144 | if (_this3.isSet(currentFunction.ssmToEnvironment)) { 145 | // If the property `ssmToEnvironment` has been set at the function level 146 | 147 | // Creates the function `environment` property if it doesn't already exist 148 | if (!_this3.isSet(currentFunction.environment)) { 149 | currentFunction.environment = {}; 150 | } 151 | 152 | // forEach ssmParameter assigned over `ssmToEnvironment` function property... 153 | currentFunction.ssmToEnvironment.forEach(function (ssmParameterToAssign) { 154 | if (_this3.isSet(fetchedSsmParameters[ssmParameterToAssign])) { 155 | // merges it into the function `environment` property 156 | currentFunction.environment[ssmParameterToAssign] = fetchedSsmParameters[ssmParameterToAssign]; 157 | } 158 | }); 159 | } else { 160 | // Else, the property `ssmToEnvironment` has NOT been set at the function level 161 | 162 | // Creates the function `environment` property if it doesn't already exist 163 | if (!_this3.isSet(currentFunction.environment)) { 164 | currentFunction.environment = {}; 165 | } 166 | 167 | // Merges ALL the fetched ssmParameters 168 | Object.keys(fetchedSsmParameters).forEach(function (ssmParameterToAssign) { 169 | currentFunction.environment[ssmParameterToAssign] = fetchedSsmParameters[ssmParameterToAssign]; 170 | }); 171 | } 172 | 173 | log.info('> serverless-ssm-fetch: Function "' + functionName + '" set environment variables:'); 174 | log.info(JSON.stringify(Object.keys(currentFunction.environment), null, 2)); 175 | }); 176 | } 177 | }, { 178 | key: 'validate', 179 | value: function validate() { 180 | var _this4 = this; 181 | 182 | setTimeout(function () { 183 | if (_this4.serverless.service.provider.name !== 'aws') { 184 | throw new _this4.serverless.classes.Error('> serverless-ssm-fetch: The plugin "serverless-ssm-fetch" is only available for `aws` provider.'); 185 | } 186 | 187 | if (!_this4.isSet(_this4.serverless.service.custom) || !_this4.isSet(_this4.serverless.service.custom['serverlessSsmFetch'])) { 188 | throw new _this4.serverless.classes.Error('> serverless-ssm-fetch: You are using the plugin "serverless-ssm-fetch". You must set a `custom.serverlessSsmFetch` element in your serverless conf file.'); 189 | } 190 | }, 5000); 191 | } 192 | }, { 193 | key: 'isSet', 194 | value: function isSet(attribute) { 195 | var bool = true; 196 | if (typeof attribute === 'undefined' || attribute === null) { 197 | bool = false; 198 | } 199 | return bool; 200 | } 201 | }]); 202 | 203 | return SsmFetch; 204 | }(); 205 | 206 | module.exports = SsmFetch; 207 | --------------------------------------------------------------------------------