From 163c7e85655060facd1654e47b898fba699dd7b9 Mon Sep 17 00:00:00 2001 From: Glenn Meuth Date: Thu, 9 Mar 2017 23:43:25 -0700 Subject: [PATCH] Initial commit - still in alpha testing --- .eslintignore | 5 ++++ .eslintrc.js | 16 ++++++++++ .gitignore | 50 +++++++++++++++++++++++++++++++ .npmignore | 23 +++++++++++++++ CHANGELOG.md | 3 ++ LICENSE.txt | 22 ++++++++++++++ README.md | 0 event.json | 5 ++++ index.js | 46 +++++++++++++++++++++++++++++ lib/queues.js | 78 +++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 50 +++++++++++++++++++++++++++++++ test/encrypt.js | 65 +++++++++++++++++++++++++++++++++++++++++ 12 files changed, 363 insertions(+) create mode 100644 .eslintignore create mode 100644 .eslintrc.js create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 event.json create mode 100644 index.js create mode 100644 lib/queues.js create mode 100644 package.json create mode 100644 test/encrypt.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..24d5c6e --- /dev/null +++ b/.eslintignore @@ -0,0 +1,5 @@ +coverage +node_modules +tmp +tmpdirs-serverless +handler.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..80d5254 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,16 @@ +module.exports = { + "extends": "airbnb", + "plugins": [], + "rules": { + "func-names": "off", + + // doesn't work in node v4 :( + "strict": "off", + "prefer-rest-params": "off", + "react/require-extension" : "off", + "import/no-extraneous-dependencies" : "off" + }, + "env": { + "mocha": true + } +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6862ae7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ + +# Created by https://www.gitignore.io/api/node + +### Node ### +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +.serverless diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..98697be --- /dev/null +++ b/.npmignore @@ -0,0 +1,23 @@ +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Serverless directories +.serverless + +# node_modules +node_modules diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..339d42b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +- Initial release diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..7224291 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2016 Masashi Terui + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/event.json b/event.json new file mode 100644 index 0000000..2ac50a4 --- /dev/null +++ b/event.json @@ -0,0 +1,5 @@ +{ + "key3": "value3", + "key2": "value2", + "key1": "value1" +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..2d5f7ee --- /dev/null +++ b/index.js @@ -0,0 +1,46 @@ +'use strict'; + +const BbPromise = require('bluebird'); +const validate = require('serverless/lib/plugins/aws/lib/validate'); +const queues = require('./lib/queues'); + +class SqsFifo { + constructor(serverless, options) { + this.serverless = serverless; + this.options = options || {}; + this.provider = this.serverless.getProvider('aws'); + + Object.assign( + this, + validate, + queues + ); + + this.queueStack = []; + if(this.serverless.service.custom && + this.serverless.service.custom.sqs && + this.serverless.service.custom.sqs.queues + ) { + let queuesConfig = this.serverless.service.custom.sqs.queues + for (var i in queuesConfig) { + queuesConfig[i]["logical_name"] = `custom.sqs.queues.${i}` + this.queueStack[this.queueStack.length] = queuesConfig[i]; + } + } + + this.hooks = { + 'before:deploy:initialize': () => + BbPromise.bind(this) + .then(this.validate) + .then(this.create) + .then(this.decorate), + 'before:deploy:remove': () => + BbPromise.bind(this) + .then(this.validate) + .then(this.remove), + }; + + } +} + +module.exports = SqsFifo; diff --git a/lib/queues.js b/lib/queues.js new file mode 100644 index 0000000..505edd1 --- /dev/null +++ b/lib/queues.js @@ -0,0 +1,78 @@ +'use strict'; + +const aws = require('aws-sdk'); +const BbPromise = require('bluebird'); +aws.config.setPromisesDependency(BbPromise); + +module.exports = { + create() { + let _this = this; + if(_this.queueStack.length === 0) return; + let queue = _this.queueStack.shift(); + let sqs = new aws.SQS({ + 'apiVersion': '2012-11-05', + 'region': _this.options.region + }); + let params = { + 'QueueName': queue.QueueName, + 'Attributes': {} + } + for (var p in queue.Properties) { + if ("RedrivePolicy" === p) { + params.Attributes[p] = JSON.stringify(queue.Properties[p]); + } else { + params.Attributes[p] = queue.Properties[p].toString(); + } + } + return sqs.createQueue(params).promise().then((data) => { + queue.url = data.QueueUrl; + // Get the arn + return sqs.getQueueAttributes({ + 'AttributeNames': ["QueueArn"], + 'QueueUrl': queue.url + }).promise().catch((err) => { + _this.serverless.cli.log(`Error in serverless-sqs-fifo fetching queue attributes for ${queue.url}: ${err}`); + throw err; + }).then((q) => { + queue.arn = q.Attributes.QueueArn; + // Iterate the rest of the queue's and replace the arn reference if present for dead letter queues + for (var iq in _this.queueStack) { + let nq = _this.queueStack[iq]; + if ("RedrivePolicy" in nq.Properties) { + if (nq.Properties.RedrivePolicy.deadLetterTargetArn === queue.logical_name) + nq.Properties.RedrivePolicy.deadLetterTargetArn = queue.arn; + } + } + // Recurse into create method, it will return when the stack is empty + return _this.queueStack.length; + }).catch((err) => { + _this.serverless.cli.log(`Error in serverless-sqs-fifo replacing queue arn for ${queue.arn}: ${err}`); + throw err; + }).then((count) => { + if (count === 0) { + let queues = _this.serverless.service.custom.sqs.queues; + for (var i in queues) + _this.serverless.cli.log(JSON.stringify(queues[i], null, 2)); + } else { + return _this.create(); + } + }); + }); + }, + decorate(val) { + // Trigger serverless to evaluate variables again to pick up the new data we added + // If we break serverless, this is probably the place we did it + console.log("Calling populateService again"); + this.serverless.variables.populateService(this.serverless.pluginManager.cliOptions); + }, + update(data) { + console.log(`Calling update ${JSON.stringify(data, null, 2)}`); + this.serverless.update(data); + }, + remove() { + this.serverless.cli.log(`Stub queues remove`); + queues = this.serverless.service.custom.sqs.fifo.queues; + for (var i in queues) + console.log("Stub queue removing " + queues[i].arn); + } +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..01bdda8 --- /dev/null +++ b/package.json @@ -0,0 +1,50 @@ +{ + "name": "serverless-sqs-fifo", + "version": "1.0.1", + "description": "Augmenting support for SQS Fifo Queue's with Serverless", + "main": "index.js", + "scripts": { + "test": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha", + "lint": "./node_modules/.bin/eslint ." + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vortarian/serverless-sqs-fifo.git" + }, + "files": [ + "index.js", + "package.json", + "README.md", + "LICENSE.txt", + "CHANGELOG.md", + "lib" + ], + "author": "Glenn Meuth", + "license": "MIT", + "bugs": { + "url": "https://github.com/vortarian/serverless-sqs-fifo/issues" + }, + "homepage": "https://github.com/vortarian/serverless-sqs-fifo#readme", + "devDependencies": { + "chai": "^3.5.0", + "coveralls": "^2.11.14", + "eslint": "^3.9.1", + "eslint-config-airbnb": "^12.0.0", + "eslint-config-airbnb-base": "^9.0.0", + "eslint-plugin-import": "^2.0.1", + "eslint-plugin-jsx-a11y": "^2.2.3", + "eslint-plugin-react": "^6.5.0", + "istanbul": "^0.4.5", + "mocha": "^3.1.2", + "mocha-lcov-reporter": "^1.2.0", + "serverless": "^1.0.3", + "sinon": "^1.17.6", + "tap": "^8.0.0" + }, + "dependencies": { + "aws-sdk": "^2.22.0", + "bluebird": "^3.4.6", + "eslint-plugin-import": "^2.2.0", + "fs-extra": "^1.0.0" + } +} diff --git a/test/encrypt.js b/test/encrypt.js new file mode 100644 index 0000000..c2cac0a --- /dev/null +++ b/test/encrypt.js @@ -0,0 +1,65 @@ +'use strict'; + +const expect = require('chai').expect; +const sinon = require('sinon'); +const BbPromise = require('bluebird'); +const Serverless = require('serverless/lib/Serverless'); +const AwsProvider = require('serverless/lib/plugins/aws/provider/awsProvider'); +const encrypt = require('./../lib/encrypt'); + +describe('encrypt()', () => { + let serverless; + + beforeEach(() => { + serverless = new Serverless(); + serverless.servicePath = true; + serverless.service.environment = { + vars: {}, + stages: { + dev: { + vars: {}, + regions: { + 'us-east-1': { + vars: {}, + }, + }, + }, + }, + }; + serverless.service.custom = { + cryptKeyId: 'foo', + }; + const options = { + stage: 'dev', + region: 'us-east-1', + name: 'test', + text: 'bar', + }; + serverless.init(); + encrypt.serverless = serverless; + encrypt.provider = new AwsProvider(serverless); + encrypt.options = options; + }); + + it('should encrypt the secret', () => { + const encryptStub = sinon + .stub(encrypt.provider, 'request').returns(BbPromise.resolve({ CiphertextBlob: 'foo' })); + + const params = { + KeyId: encrypt.serverless.service.custom.cryptKeyId, + Plaintext: encrypt.options.text, + }; + + return encrypt.encrypt().then(() => { + expect(encryptStub.calledOnce).to.be.equal(true); + expect(encryptStub.calledWithExactly( + 'KMS', + 'encrypt', + params, + encrypt.options.stage, + encrypt.options.region + )).to.be.equal(true); + encrypt.provider.request.restore(); + }); + }); +});