diff --git a/index.js b/index.js index 1c797b0..8b763ec 100644 --- a/index.js +++ b/index.js @@ -26,13 +26,20 @@ module.exports = { }); } - function _beginMessage(ui, indexPath) { + function _beginUploadMessage(ui, indexPath) { ui.write(blue('| ')); ui.write(blue('- Uploading `' + indexPath + '`\n')); return Promise.resolve(); } + function _beginActivateMessage(ui, revisionKey) { + ui.write(blue('| ')); + ui.write(blue('- Activating revision `' + revisionKey + '`\n')); + + return Promise.resolve(); + } + function _successMessage(ui, key) { ui.write(blue('| ')); ui.write(blue('- Uploaded with key `' + key + '`\n')); @@ -40,6 +47,13 @@ module.exports = { return Promise.resolve(key); } + function _activationSuccessMessage(ui, revisionKey) { + ui.write(blue('| ')); + ui.write(blue('- ✔ Activated revision `' + revisionKey + '`\n')); + + return Promise.resolve(); + } + function _errorMessage(ui, error) { ui.write(blue('| ')); ui.write(red('- ' + error + '`\n')); @@ -56,30 +70,56 @@ module.exports = { var config = deployment.config[this.name] = deployment.config[this.name] || {}; var projectName = deployment.project.name(); - return validateConfig(ui, config, projectName) - .then(function() { - ui.write(blue('| ')); - ui.writeLine(blue('- config ok')); - }); + return this._resolvePipelineData(config, context) + .then(validateConfig.bind(this, ui, config, projectName)); }, upload: function(context) { - var deployment = context.deployment; - var ui = deployment.ui; - var config = deployment.config[this.name] || {}; - var redis = context.redisClient || new Redis(config); - var tag = context.tag; + var deployment = context.deployment; + var ui = deployment.ui; + var config = deployment.config[this.name] || {}; + var redis = context.redisClient || new Redis(config); + var revisionKey = this._resolveConfigValue('revisionKey', config, context); var filePattern = config.filePattern; - return _beginMessage(ui, filePattern) + return _beginUploadMessage(ui, filePattern) .then(_readFileContents.bind(this, filePattern)) - .then(redis.upload.bind(redis, config.keyPrefix, tag)) + .then(redis.upload.bind(redis, config.keyPrefix, revisionKey)) .then(_successMessage.bind(this, ui)) .then(function(key) { return { redisKey: key } }) .catch(_errorMessage.bind(this, ui)); + }, + + activate: function(context) { + var deployment = context.deployment; + var ui = deployment.ui; + var config = deployment.config[this.name] || {}; + var redis = context.redisClient || new Redis(config); + var revisionKey = this._resolveConfigValue('revisionKey', config, context); + + return _beginActivateMessage(ui, revisionKey) + .then(redis.activate.bind(redis, config.keyPrefix, revisionKey)) + .then(_activationSuccessMessage.bind(this, ui, revisionKey)) + .catch(_errorMessage.bind(this, ui)); + }, + + _resolvePipelineData: function(config, context) { + config.revisionKey = config.revisionKey || function(context) { + return context.deployment.commandLineArgs.revisionKey || context.revisionKey; + }; + + return Promise.resolve(); + }, + + _resolveConfigValue: function(key, config, context) { + if(typeof config[key] === 'function') { + return config[key](context); + } + + return config[key]; } }; } diff --git a/lib/redis.js b/lib/redis.js index 57ae880..7202257 100644 --- a/lib/redis.js +++ b/lib/redis.js @@ -23,27 +23,50 @@ module.exports = CoreObject.extend({ this._allowOverwrite = options.allowOverwrite; }, - upload: function(/*keyPrefix, tag, value*/) { + upload: function(/*keyPrefix, revisionKey, value*/) { var args = Array.prototype.slice.call(arguments); - var keyPrefix = args.shift(); - var value = args.pop(); - var tag = args[0] || 'default'; - var redisKey = keyPrefix + ':' + tag; + var keyPrefix = args.shift(); + var value = args.pop(); + var revisionKey = args[0] || 'default'; + var redisKey = keyPrefix + ':' + revisionKey; var maxEntries = this._maxNumberOfRecentUploads; return Promise.resolve() .then(this._uploadIfKeyDoesNotExist.bind(this, redisKey, value)) - .then(this._updateRecentUploadsList.bind(this, keyPrefix, tag)) + .then(this._updateRecentUploadsList.bind(this, keyPrefix, revisionKey)) .then(this._trimRecentUploadsList.bind(this, keyPrefix, maxEntries)) .then(function() { return redisKey; }); }, - _uploadIfKeyDoesNotExist: function(redisKey, value) { + activate: function(keyPrefix, revisionKey) { + var currentKey = keyPrefix + ':current'; + + return Promise.resolve() + .then(this._listRevisions.bind(this, keyPrefix)) + .then(this._validateRevisionKey.bind(this, revisionKey)) + .then(this._activateRevisionKey.bind(this, currentKey, revisionKey)); + }, + + _listRevisions: function(keyPrefix) { var client = this._client; + return client.lrange(keyPrefix, 0, this._maxNumberOfRecentUploads - 1); + }, + + _validateRevisionKey: function(revisionKey, revisions) { + return revisions.indexOf(revisionKey) > -1 ? Promise.resolve() : Promise.reject('`' + revisionKey + '` is not a valid revision key'); + }, + + _activateRevisionKey: function(currentKey, revisionKey) { + var client = this._client; + return client.set(currentKey, revisionKey); + }, + + _uploadIfKeyDoesNotExist: function(redisKey, value) { + var client = this._client; var allowOverwrite = !!this._allowOverwrite; return Promise.resolve() @@ -60,9 +83,9 @@ module.exports = CoreObject.extend({ }) }, - _updateRecentUploadsList: function(keyPrefix, tag) { + _updateRecentUploadsList: function(keyPrefix, revisionKey) { var client = this._client; - return client.lpush(keyPrefix, tag); + return client.lpush(keyPrefix, revisionKey); }, _trimRecentUploadsList: function(keyPrefix, maxEntries) { diff --git a/lib/utilities/validate-config.js b/lib/utilities/validate-config.js index d379d50..d420ac0 100644 --- a/lib/utilities/validate-config.js +++ b/lib/utilities/validate-config.js @@ -33,5 +33,8 @@ module.exports = function(ui, config, projectName) { applyDefaultConfigIfNecessary(config, configKey, defaultConfig, ui); }); + ui.write(blue('| ')); + ui.writeLine(blue('- config ok')); + return Promise.resolve(); } diff --git a/tests/unit/index-nodetest.js b/tests/unit/index-nodetest.js index 58b72e5..714ab06 100644 --- a/tests/unit/index-nodetest.js +++ b/tests/unit/index-nodetest.js @@ -4,6 +4,19 @@ var Promise = require('ember-cli/lib/ext/promise'); var assert = require('ember-cli/tests/helpers/assert'); +function hooks(plugin) { + return Object.keys(plugin).filter(function(key) { + return (key !== 'name') && (key.charAt(0) !== '_') && (typeof plugin[key] === 'function'); + }); +} + +var stubUi = { write: function() {}, writeLine: function() {} }; +var stubProject = { + name: function(){ + return 'my-project'; + } +}; + describe('redis plugin', function() { var subject; @@ -20,15 +33,15 @@ describe('redis plugin', function() { }); it('implements the correct hooks', function() { - var result = subject.createDeployPlugin({ + var plugin = subject.createDeployPlugin({ name: 'test-plugin' }); - assert.equal(typeof result.configure, 'function'); - assert.equal(typeof result.upload, 'function'); + assert.equal(hooks(plugin).length, 3); + assert.sameMembers(hooks(plugin), ['configure', 'upload', 'activate']); }); - describe('willDeploy hook', function() { + describe('configure hook', function() { it('resolves if config is ok', function() { var plugin = subject.createDeployPlugin({ name: 'redis' @@ -36,12 +49,8 @@ describe('redis plugin', function() { var context = { deployment: { - ui: { write: function() {}, writeLine: function() {} }, - project: { - name: function(){ - return 'my-project'; - } - }, + ui: stubUi, + project: stubProject, config: { redis: { host: 'somehost', @@ -53,6 +62,96 @@ describe('redis plugin', function() { return assert.isFulfilled(plugin.configure.call(plugin, context)) }); + + describe('resolving data from the pipeline', function() { + it('uses the config data if it already exists', function() { + var plugin = subject.createDeployPlugin({ + name: 'redis' + }); + + var config = { + host: 'somehost', + port: 1234, + revisionKey: '12345' + }; + var context = { + deployment: { + ui: stubUi, + project: stubProject, + config: { + redis: config + } + }, + + revisionKey: 'something-else' + }; + + return assert.isFulfilled(plugin.configure.call(plugin, context)) + .then(function() { + assert.equal(config.revisionKey, '12345'); + }); + }); + + it('uses the commandLineArgs value if it exists', function() { + var plugin = subject.createDeployPlugin({ + name: 'redis' + }); + + var config = { + host: 'somehost', + port: 1234 + }; + var context = { + deployment: { + ui: stubUi, + project: stubProject, + config: { + redis: config + }, + commandLineArgs: { + revisionKey: 'abcd' + } + }, + + revisionKey: 'something-else' + }; + + return assert.isFulfilled(plugin.configure.call(plugin, context)) + .then(function() { + assert.typeOf(config.revisionKey, 'function'); + assert.equal(config.revisionKey(context), 'abcd'); + }); + }) + + it('uses the context value if it exists and commandLineArgs don\'t', function() { + var plugin = subject.createDeployPlugin({ + name: 'redis' + }); + + var config = { + host: 'somehost', + port: 1234 + }; + var context = { + deployment: { + ui: stubUi, + project: stubProject, + config: { + redis: config + }, + commandLineArgs: { } + }, + + revisionKey: 'something-else' + }; + + return assert.isFulfilled(plugin.configure.call(plugin, context)) + .then(function() { + assert.typeOf(config.revisionKey, 'function'); + assert.equal(config.revisionKey(context), 'something-else'); + }); + }) + }); }); describe('upload hook', function() { @@ -66,34 +165,95 @@ describe('redis plugin', function() { context = { redisClient: { - upload: function() { - return Promise.resolve('redis-key'); + upload: function(keyPrefix, revisionKey) { + return Promise.resolve(keyPrefix + ':' + revisionKey); } }, tag: 'some-tag', deployment: { - ui: { write: function() {} }, - project: { name: function() { return 'test-project'; } }, + ui: stubUi, + project: stubProject, config: { redis: { + keyPrefix: 'test-prefix', filePattern: 'tests/index.html', + revisionKey: '123abc' } } - } + }, }; }); - it('uploads the index to redis', function() { + it('uploads the index', function() { return assert.isFulfilled(plugin.upload.call(plugin, context)) .then(function(result) { - assert.deepEqual(result, { redisKey: 'redis-key' }); + assert.deepEqual(result, { redisKey: 'test-prefix:123abc' }); }); }); + }); - it('returns the uploaded key', function() { - return assert.isFulfilled(plugin.upload.call(plugin, context)) - .then(function(result) { - assert.deepEqual(result.redisKey, 'redis-key'); + describe('activate hook', function() { + it('activates revision', function() { + var activateCalled = false; + + var plugin = subject.createDeployPlugin({ + name: 'redis' + }); + + var context = { + redisClient: { + activate: function() { + activateCalled = true; + } + }, + tag: 'some-tag', + deployment: { + ui: stubUi, + project: stubProject, + config: { + redis: { + keyPrefix: 'test-prefix', + filePattern: 'tests/index.html', + revisionKey: '123abc' + } + } + } + }; + + return assert.isFulfilled(plugin.activate.call(plugin, context)) + .then(function() { + assert.ok(activateCalled); + }); + }); + + it('rejects if an error is thrown when activating', function() { + var plugin = subject.createDeployPlugin({ + name: 'redis' + }); + + var context = { + redisClient: { + activate: function() { + return Promise.reject('some-error'); + } + }, + tag: 'some-tag', + deployment: { + ui: stubUi, + project: stubProject, + config: { + redis: { + keyPrefix: 'test-prefix', + filePattern: 'tests/index.html', + revisionKey: '123abc' + } + } + } + }; + + return assert.isRejected(plugin.activate.call(plugin, context)) + .then(function(error) { + assert.equal(error, 'some-error'); }); }); }); diff --git a/tests/unit/lib/redis-nodetest.js b/tests/unit/lib/redis-nodetest.js index 5b4ff80..b9a5a05 100644 --- a/tests/unit/lib/redis-nodetest.js +++ b/tests/unit/lib/redis-nodetest.js @@ -169,4 +169,48 @@ describe('redis', function() { }); }); }); + + describe('#activate', function() { + it('rejects if the revisionKey doesn\t exist in list of uploaded revisions', function() { + var recentRevisions = ['a', 'b', 'c']; + + var redis = new Redis({ + redisClient: { + lrange: function() { + return recentRevisions; + } + } + }); + + var promise = redis.activate('key-prefix', 'revision-key'); + return assert.isRejected(promise) + .then(function(error) { + assert.equal(error, '`revision-key` is not a valid revision key'); + }); + }); + + it('resolves and sets the current revision to the revision key provided', function() { + var recentRevisions = ['a', 'b', 'c']; + var redisKey, redisValue; + + var redis = new Redis({ + redisClient: { + lrange: function() { + return recentRevisions; + }, + set: function(key, value) { + redisKey = key; + redisValue = value; + } + } + }); + + var promise = redis.activate('key-prefix', 'c'); + return assert.isFulfilled(promise) + .then(function() { + assert.equal(redisKey, 'key-prefix:current'); + assert.equal(redisValue, 'c'); + }); + }); + }); });