Initial version of plugin installer

This commit is contained in:
Jim Unger 2015-06-30 16:09:13 -05:00
parent 015d3abd83
commit 56d3eba858
27 changed files with 2286 additions and 206 deletions

1
.gitignore vendored
View file

@ -13,3 +13,4 @@ target
*.log
esvm
.htpasswd
src/server/bin/plugins

View file

@ -58,12 +58,14 @@
"http-auth": "^2.2.5",
"joi": "^6.4.3",
"js-yaml": "^3.2.5",
"lodash": "^3.9.3",
"json-stringify-safe": "^5.0.1",
"lodash": "^3.9.3",
"mockery": "^1.4.0",
"moment": "^2.10.3",
"numeral": "^1.5.3",
"request": "^2.40.0",
"requirefrom": "^0.2.0",
"rimraf": "^2.4.0",
"semver": "^4.3.6",
"serve-favicon": "^2.2.0",
"through": "^2.3.6"
@ -95,14 +97,14 @@
"husky": "^0.8.1",
"istanbul": "^0.3.15",
"jade": "^1.8.2",
"license-checker": "3.0.3",
"libesvm": "^1.0.1",
"license-checker": "3.0.3",
"load-grunt-config": "^0.7.0",
"marked": "^0.3.3",
"marked-text-renderer": "^0.1.0",
"mkdirp": "^0.5.0",
"mocha": "^2.2.5",
"nock": "^1.6.0",
"nock": "^2.7.0",
"npm": "^2.11.0",
"opn": "^1.0.0",
"path-browserify": "0.0.0",

View file

@ -9,6 +9,7 @@ set NODE=%DIR%\node\node.exe
set SERVER=%DIR%\src\bin\kibana.js
set NODE_ENV="production"
set CONFIG_PATH=%DIR%\config\kibana.yml
REM set NPM (TODO: Need to define the env variable to the install of npm. TALK TO CHRIS/JOE)
TITLE Kibana Server @@version
@ -16,6 +17,4 @@ TITLE Kibana Server @@version
:finally
ENDLOCAL
ENDLOCAL

View file

@ -1,5 +1,8 @@
#!/usr/bin/env node
//process.env.NODE_DEBUG = 'net';
//process.env.NODE_DEBUG = 'request';
var _ = require('lodash');
var program = require('commander');
require('../lib/commanderExtensions.js')(program);

View file

@ -1,49 +0,0 @@
var _ = require('lodash');
var zlib = require('zlib');
var Promise = require('bluebird');
var request = require('request');
var tar = require('tar');
var Path = require('path');
var fs = Promise.promisifyAll(require('fs'));
var EventEmitter = require('events').EventEmitter;
module.exports = function (source, dest, downloadLogger) {
downloadLogger = downloadLogger || _.noop;
return new Promise(function (resolve, reject) {
var gunzip = zlib.createGunzip();
var progress = new EventEmitter();
var tarExtract = tar.Extract({ path: dest, strip: 1 });
request.get(source)
.on('response', function (resp) {
var total = parseInt(resp.headers['content-length'], 10);
var docInfo = {
level: 'INFO',
type: 'progress',
op: 'downloading',
total: total,
timestamp: new Date(),
message: 'Downloading ' + total + ' bytes'
};
downloadLogger(progress, docInfo);
})
.on('data', function (buffer) {
progress.emit('progress', buffer.length);
})
.on('error', reject)
.on('end', function () {
progress.emit('message', '\nDownload Complete.\n');
progress.emit('message', 'Extracting archive.\n');
})
.pipe(gunzip)
.on('error', reject)
.pipe(tarExtract)
.on('end', function () {
progress.emit('message', 'Extraction complete.\n');
resolve();
})
.on('error', reject);
});
};

View file

@ -1,23 +1,37 @@
var npm = require('npm');
var Promise = require('bluebird');
var fs = require('fs');
var path = require('path');
var Promise = require('bluebird');
var exec = require('child_process').exec;
module.exports = function (dest) {
module.exports = function (dest, logger) {
return new Promise(function (resolve, reject) {
//var cwd = process.cwd();
npm.load(function (err) {
process.chdir(dest);
//throw an exception if package.json does not exist
try {
var packageFile = path.join(dest, 'package.json');
fs.statSync(packageFile);
} catch (e) {
if (e.code !== 'ENOENT')
throw e;
var blah = path.join(dest, 'package.json');
npm.commands.install([blah], function (er, data) {
if (er) {
console.error(er);
}
//process.chdir(cwd);
});
npm.on('log', function (message) {
console.log(message);
});
reject(new Error('Plugin does not contain package.json file'));
}
var cmd = (process.env.NPM) ? process.env.NPM : 'npm';
cmd += ' install';
var child = exec(cmd, { cwd: dest });
child.on('error', function (err) {
reject(err);
});
child.on('exit', function (code, signal) {
if (code === 0) {
resolve();
} else {
reject(new Error('npm install failed.'));
}
});
logger.error(child.stderr);
logger.log(child.stdout);
});
};

View file

@ -1,109 +1,36 @@
module.exports = function (program) {
var expiry = require('expiry-js');
var downloadAndExpand = require('./downloadAndExpand.js');
var npmInstall = require('./npmInstall.js');
var baseUrl = 'https://s3.amazonaws.com/jimtars/';
var settings;
function parseSeconds(val) {
var result;
try {
//Is there is no unit specified, assume seconds
var re = /([a-zA-Z]+)/g;
if (!re.exec(val)) {
val += 's';
}
var timeVal = expiry(val);
result = timeVal.asSeconds();
} catch (ex) { }
return result;
}
function parseSettings(options) {
var settings = {
timeout: 0,
silent: false
};
if (options.timeout) {
settings.timeout = options.timeout;
}
if (options.silent) {
settings.silent = options.silent;
}
if (options.install) {
settings.action = 'install';
settings.plugin = options.install;
}
if (options.remove) {
settings.action = 'remove';
settings.plugin = options.remove;
}
return settings;
}
function log(message) {
if (settings.silent) return;
process.stdout.write(message);
}
function downloadLogger(progress, docInfo) {
var totalBytes = docInfo.total;
var runningTotal = 0;
log(docInfo.message + '\n');
progress.on('progress', function (data) {
runningTotal += data;
var percent = Math.round(runningTotal / totalBytes * 100);
if (percent % 10 === 0) {
log('.');
}
});
progress.on('message', function (message) {
log(message);
});
}
var settingParser = require('./settingParser');
var installer = require('./pluginInstaller');
var remover = require('./pluginRemover');
var pluginLogger = require('./pluginLogger');
module.exports = function (program) {
function processCommand(command, options) {
settings = parseSettings(command);
if (!settings.action) {
console.error('Please specify either --install or --remove.');
process.exit(1);
var settings;
try {
settings = settingParser.parse(command);
} catch (ex) {
//The logger has not yet been initialized.
console.error(ex.message);
process.exit(64);
}
var logger = pluginLogger(settings.silent);
if (settings.action === 'install') {
//require('./plugin_install.js')(settings);
log('Running download and install.\n');
var sourceUrl = 'https://download.elastic.co/kibana/plugins/test-plugin-1.0.0.tgz';
var destPath = './plugins/' + settings.plugin;
downloadAndExpand(sourceUrl, destPath, downloadLogger)
.catch(function (e) {
console.error('Error installing plugin: ' + e);
})
.then(function () {
npmInstall(destPath);
});
installer.install(settings, logger);
}
if (settings.action === 'remove') {
remover.remove(settings, logger);
}
}
program
.command('plugin')
.description('Maintain Plugins')
.option('-i, --install <plugin>', 'The plugin to install')
.option('-i, --install <org>/<plugin>/<version>', 'The plugin to install')
.option('-r, --remove <plugin>', 'The plugin to remove')
.option('-s, --silent', 'Disable process messaging')
.option('-t, --timeout <duration>', 'Length of time before failing; 0 for never fail', parseSeconds)
.option('-u, --url <url>', 'Specify download url')
.option('-t, --timeout <duration>', 'Length of time before failing; 0 for never fail', settingParser.parseMilliseconds)
.action(processCommand);
};

View file

@ -0,0 +1,39 @@
var rimraf = require('rimraf');
var fs = require('fs');
var Promise = require('bluebird');
module.exports = {
cleanPrevious: cleanPrevious,
cleanError: cleanError
};
function cleanPrevious(settings, logger) {
return new Promise(function (resolve, reject) {
try {
fs.statSync(settings.workingPath);
logger.log('Found previous install attempt. Deleting...');
try {
rimraf.sync(settings.workingPath);
} catch (e) {
return reject(e);
}
return resolve();
} catch (e) {
if (e.code !== 'ENOENT')
return reject(e);
return resolve();
}
});
}
function cleanError(settings) {
//delete the working directory.
//At this point we're bailing, so swallow any errors on delete.
try {
rimraf.sync(settings.workingPath);
} catch (e) {
console.log(e.message);
}
}

View file

@ -0,0 +1,65 @@
var _ = require('lodash');
var zlib = require('zlib');
var Promise = require('bluebird');
var request = require('request');
var tar = require('tar');
var path = require('path');
var progressReporter = require('./progressReporter.js');
module.exports = {
download: download,
_downloadSingle: downloadSingle
};
//Attempts to download each url in turn until one is successful
function download(settings, logger) {
var urls = settings.urls;
function tryNext() {
var sourceUrl = urls.shift();
if (!sourceUrl) {
throw new Error('Not a valid url.');
}
logger.log('attempting to download ' + sourceUrl);
return downloadSingle(sourceUrl, settings.workingPath, settings.timeout, logger)
.catch(function (err) {
if (err.message === 'ENOTFOUND') {
return tryNext();
}
if (err.message === 'EEXTRACT') {
throw (new Error('Error extracting the plugin archive'));
}
throw (err);
});
}
return tryNext();
}
//Attempts to download a single url
function downloadSingle(source, dest, timeout, logger) {
var gunzip = zlib.createGunzip();
var tarExtract = tar.Extract({ path: dest, strip: 1 });
var requestOptions = { url: source };
if (timeout !== 0) {
requestOptions.timeout = timeout;
}
var req = request.get(requestOptions);
var reporter = progressReporter(logger, req);
req
.on('response', reporter.handleResponse)
.on('data', reporter.handleData)
.on('error', _.partial(reporter.handleError, 'ENOTFOUND'))
.pipe(gunzip)
.on('error', _.partial(reporter.handleError, 'EEXTRACT'))
.pipe(tarExtract)
.on('error', _.partial(reporter.handleError, 'EEXTRACT'))
.on('end', reporter.handleEnd);
return reporter.deferred;
}

View file

@ -0,0 +1,40 @@
var downloader = require('./pluginDownloader.js');
var cleaner = require('./pluginCleaner.js');
var npmInstall = require('./npmInstall.js');
var fs = require('fs');
module.exports = {
install: install
};
function install(settings, logger) {
logger.log('installing ' + settings.package);
try {
fs.statSync(settings.pluginPath);
logger.error('Plugin ' + settings.package + ' already exists. Please remove before installing a new version.');
process.exit(70);
} catch (e) {
if (e.code !== 'ENOENT')
throw e;
}
return cleaner.cleanPrevious(settings, logger)
.then(function () {
return downloader.download(settings, logger);
})
.then(function () {
return npmInstall(settings.workingPath, logger);
})
.then(function (curious) {
fs.renameSync(settings.workingPath, settings.pluginPath);
logger.log('Plugin installation complete!');
})
.catch(function (e) {
logger.error('Plugin installation was unsuccessful.');
logger.error(e.message);
cleaner.cleanError(settings);
process.exit(70);
});
}

View file

@ -0,0 +1,41 @@
module.exports = function (silent) {
var previousLineEnded = true;
silent = !!silent;
function log(data, sameLine) {
if (silent) return;
if (!sameLine && !previousLineEnded) {
process.stdout.write('\n');
}
//if data is a stream, pipe it.
if (data.readable) {
data.pipe(process.stdout);
return;
}
if (!sameLine) data += '\n';
process.stdout.write(data);
previousLineEnded = !sameLine;
}
function error(data) {
if (!previousLineEnded) {
process.stderr.write('\n');
}
//if data is a stream, pipe it.
if (data.readable) {
data.pipe(process.stderr);
return;
}
process.stderr.write(data + '\n');
previousLineEnded = true;
}
return {
log: log,
error: error
};
};

View file

@ -0,0 +1,25 @@
var fs = require('fs');
var rimraf = require('rimraf');
module.exports = {
remove: remove
};
function remove(settings, logger) {
try {
try {
fs.statSync(settings.pluginPath);
}
catch (e) {
logger.log('Plugin ' + settings.package + ' does not exist.');
return;
}
logger.log('Removing ' + settings.package + '...');
rimraf.sync(settings.pluginPath);
} catch (ex) {
logger.error(ex.message);
process.exit(74);
}
}

View file

@ -1,41 +0,0 @@
module.exports = function (settings) {
var downloadAndExpand = require('./downloadAndExpand.js');
var npmInstall = require('./npmInstall.js');
function log(message) {
if (settings.silent) return;
process.stdout.write(message);
}
function downloadLogger(progress, docInfo) {
var totalBytes = docInfo.total;
var runningTotal = 0;
log(docInfo.message + '\n');
progress.on('progress', function (data) {
runningTotal += data;
var percent = Math.round(runningTotal / totalBytes * 100);
if (percent % 10 === 0) {
log('.');
}
});
progress.on('message', function (message) {
log(message);
});
}
log('Running download and install.\n');
var sourceUrl = 'https://download.elastic.co/kibana/plugins/test-plugin-1.0.0.tgz';
var destPath = './plugins/' + settings.plugin;
downloadAndExpand(sourceUrl, destPath, downloadLogger)
.catch(function (e) {
console.error('Error installing plugin: ' + e);
})
.then(function () {
npmInstall(destPath);
});
};

View file

@ -0,0 +1,71 @@
var Promise = require('bluebird');
/*
Responsible for reporting the progress of the file request stream
*/
module.exports = function (logger, request) {
var oldDotCount = 0;
var runningTotal = 0;
var totalSize = 0;
var hasError = false;
var _resolve;
var _reject;
var _resp;
var promise = new Promise(function (resolve, reject) {
_resolve = resolve;
_reject = reject;
});
function handleError(errorMessage, err) {
if (hasError) return;
if (err) logger.error(err);
hasError = true;
request.abort();
_reject(new Error(errorMessage));
}
function handleResponse(resp) {
_resp = resp;
if (resp.statusCode >= 400) {
handleError('ENOTFOUND', null);
} else {
totalSize = parseInt(resp.headers['content-length'], 10) || 0;
var totalDesc = totalSize || 'unknown number of';
logger.log('Downloading ' + totalDesc + ' bytes', true);
}
}
//Should log a dot for every 5% of progress
//Note: no progress is logged if the plugin is downloaded in a single packet
function handleData(buffer) {
if (hasError) return;
if (!totalSize) return;
runningTotal += buffer.length;
var dotCount = Math.round(runningTotal / totalSize * 100 / 5);
if (dotCount > 20) dotCount = 20;
for (var i = 0; i < (dotCount - oldDotCount) ; i++) {
logger.log('.', true);
}
oldDotCount = dotCount;
}
function handleEnd() {
if (hasError) return;
logger.log('Download Complete.');
_resolve();
}
return {
deferred: promise,
handleResponse: handleResponse,
handleError: handleError,
handleData: handleData,
handleEnd: handleEnd,
hasError: function () { return hasError; }
};
};

View file

@ -0,0 +1,116 @@
var path = require('path');
var expiry = require('expiry-js');
module.exports = {
parse: parse,
parseMilliseconds: parseMilliseconds
};
function parseMilliseconds(val) {
var result;
try {
var timeVal = expiry(val);
result = timeVal.asMilliseconds();
} catch (ex) {
result = 0;
}
return result;
}
function generateDownloadUrl(settings) {
var version = (settings.version) || 'latest';
var filename = settings.package + '-' + version + '.tar.gz';
return 'https://download.elastic.co/' + settings.organization + '/' + settings.package + '/' + filename;
}
function generateGithubUrl(settings) {
var version = (settings.version) || 'master';
var filename = version + '.tar.gz';
return 'https://github.com/' + settings.organization + '/' + settings.package + '/archive/' + filename;
}
function parse(options) {
var parts;
var settings = {
timeout: 0,
silent: false,
urls: []
};
settings.workingPath = path.resolve(__dirname, '..', 'plugins', '.plugin.installing');
if (options.timeout) {
settings.timeout = options.timeout;
}
if (options.silent) {
settings.silent = options.silent;
}
if (options.url) {
settings.urls.push(options.url);
}
if (options.install) {
settings.action = 'install';
parts = options.install.split('/');
if (options.url) {
if (parts.length !== 1) {
throw new Error('Invalid install option. When providing a url, please use the format <plugin>.');
}
settings.package = parts.shift();
} else {
if (parts.length < 2 || parts.length > 3) {
throw new Error('Invalid install option. Please use the format <org>/<plugin>/<version>.');
}
settings.organization = parts.shift();
settings.package = parts.shift();
settings.version = parts.shift();
//settings.urls.push('http://www.piyrwljgdambcz.edu/blah.zip');
//settings.urls.push('https://s3.amazonaws.com/jimtars/badurl1.tar.gz');
//settings.urls.push('https://s3.amazonaws.com/jimtars/badurl2.tar.gz');
//settings.urls.push('I should break everything!!!');
settings.urls.push('https://s3.amazonaws.com/jimtars/badurl3.tar.gz');
settings.urls.push('https://s3.amazonaws.com/jimtars/badurl4.tar.gz');
//settings.urls.push('https://s3.amazonaws.com/jimtars/badurl5.tar.gz');
//settings.urls.push('http://localhost:3000/corrupt.tar.gz');
//settings.urls.push('https://github.com/spalger/test-plugin/archive/master.tar.gz');
//settings.urls.push('https://s3.amazonaws.com/jimtars/bowling.tar.gz');
settings.urls.push('https://s3.amazonaws.com/jimtars/BowlingFull.tar.gz');
//settings.urls.push('https://s3.amazonaws.com/jimtars/badurl6.tar.gz');
//settings.urls.push('https://s3.amazonaws.com/jimtars/badurl7.tar.gz');
//settings.urls.push(generateDownloadUrl(settings));
//settings.urls.push(generateGithubUrl(settings));
}
}
if (options.remove) {
settings.action = 'remove';
parts = options.remove.split('/');
if (parts.length !== 1) {
throw new Error('Invalid remove option. Please use the format <plugin>.');
}
settings.package = parts.shift();
}
if (!settings.action || (options.install && options.remove)) {
throw new Error('Please specify either --install or --remove.');
}
if (settings.package) {
settings.pluginPath = path.resolve(__dirname, '..', 'plugins', settings.package);
}
return settings;
}

View file

@ -0,0 +1,72 @@
var root = require('requirefrom')('');
var expect = require('expect.js');
var sinon = require('sinon');
var nock = require('nock');
var glob = require('glob');
var rimraf = require('rimraf');
var fs = require('fs');
var join = require('path').join;
var pluginLogger = root('src/server/bin/plugin/pluginLogger');
var npmInstall = root('src/server/bin/plugin/npmInstall');
describe('kibana cli', function () {
describe('plugin installer', function () {
describe('npmInstall', function () {
var logger;
var testWorkingPath;
beforeEach(function () {
logger = pluginLogger(false);
testWorkingPath = join(__dirname, '.test.data');
rimraf.sync(testWorkingPath);
sinon.stub(logger, 'log', function (data, sameLine) {
data.pipe(process.stdout);
});
sinon.stub(logger, 'error', function (data) {
data.pipe(process.stderr);
});
});
afterEach(function () {
logger.log.restore();
logger.error.restore();
rimraf.sync(testWorkingPath);
});
it('should throw an error if there is no package.json file in the archive', function () {
fs.mkdir(testWorkingPath, function (e) { });
var errorStub = sinon.stub();
return npmInstall(testWorkingPath, logger)
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/package.json/);
});
});
it('should rethrow any errors other than "ENOENT" from fs.statSync', function () {
fs.mkdir(testWorkingPath, function (e) {});
sinon.stub(fs, 'statSync', function () {
throw new Error('This is unexpected.');
});
var errorStub = sinon.stub();
return npmInstall(testWorkingPath, logger)
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/This is unexpected./);
fs.statSync.restore();
});
});
});
});
});

View file

@ -0,0 +1,130 @@
var root = require('requirefrom')('');
var plugin = root('src/server/bin/plugin/plugin');
var expect = require('expect.js');
var sinon = require('sinon');
var installer = root('src/server/bin/plugin/pluginInstaller');
var remover = root('src/server/bin/plugin/pluginRemover');
var settingParser = root('src/server/bin/plugin/settingParser');
describe('kibana cli', function () {
describe('plugin installer', function () {
describe('settings.action', function () {
var program = {
command: function () { return program; },
description: function () { return program; },
option: function () { return program; },
action: function (processCommand) {
processCommand();
}
};
beforeEach(function () {
sinon.stub(remover, 'remove');
sinon.stub(installer, 'install');
});
afterEach(function () {
remover.remove.restore();
installer.install.restore();
settingParser.parse.restore();
});
it('should call remove if settings.action is "remove"', function () {
sinon.stub(settingParser, 'parse', function () {
return {
action: 'remove'
};
});
plugin(program);
expect(remover.remove.called).to.be(true);
expect(installer.install.called).to.be(false);
});
it('should call install if settings.action is "install"', function () {
sinon.stub(settingParser, 'parse', function () {
return {
action: 'install'
};
});
plugin(program);
expect(remover.remove.called).to.be(false);
expect(installer.install.called).to.be(true);
});
});
describe('commander options', function () {
var program = {
command: function () { return program; },
description: function () { return program; },
option: function () { return program; },
action: function () { return program; }
};
it('should define the command', function () {
sinon.spy(program, 'command');
plugin(program);
expect(program.command.calledWith('plugin')).to.be(true);
program.command.restore();
});
it('should define the description', function () {
sinon.spy(program, 'description');
plugin(program);
expect(program.description.calledWith('Maintain Plugins')).to.be(true);
program.description.restore();
});
it('should define the command line options', function () {
var spy = sinon.spy(program, 'option');
var options = [
/-i/,
/-r/,
/-s/,
/-u/,
/-t/
];
plugin(program);
for (var i = 0; i < spy.callCount; i++) {
var call = spy.getCall(i);
for (var o = 0; o < options.length; o++) {
var option = options[o];
if (call.args[0].match(option)) {
options.splice(o, 1);
break;
}
}
}
expect(options).to.have.length(0);
});
it('should call the action function', function () {
sinon.spy(program, 'action');
plugin(program);
expect(program.action.calledOnce).to.be(true);
program.action.restore();
});
});
});
});

View file

@ -0,0 +1,137 @@
var root = require('requirefrom')('');
var expect = require('expect.js');
var sinon = require('sinon');
var fs = require('fs');
var rimraf = require('rimraf');
var cleaner = root('src/server/bin/plugin/pluginCleaner');
var pluginLogger = root('src/server/bin/plugin/pluginLogger');
describe('kibana cli', function () {
describe('plugin installer', function () {
describe('pluginCleaner', function () {
var settings = {
workingPath: 'dummy'
};
describe('cleanPrevious', function () {
var errorStub;
var logger;
var progress;
var request;
beforeEach(function () {
errorStub = sinon.stub();
logger = pluginLogger(false);
sinon.stub(logger, 'log');
sinon.stub(logger, 'error');
request = {
abort: sinon.stub(),
emit: sinon.stub()
};
});
afterEach(function () {
logger.log.restore();
logger.error.restore();
fs.statSync.restore();
rimraf.sync.restore();
});
it('should resolve if the working path does not exist', function () {
sinon.stub(rimraf, 'sync');
sinon.stub(fs, 'statSync', function () {
var error = new Error('ENOENT');
error.code = 'ENOENT';
throw error;
});
return cleaner.cleanPrevious(settings, logger)
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
});
});
it('should rethrow any exception except ENOENT from fs.statSync', function () {
sinon.stub(rimraf, 'sync');
sinon.stub(fs, 'statSync', function () {
var error = new Error('An Unhandled Error');
throw error;
});
var errorStub = sinon.stub();
return cleaner.cleanPrevious(settings, logger)
.catch(errorStub)
.then(function () {
expect(errorStub.called).to.be(true);
});
});
it('should log a message if there was a working directory', function () {
sinon.stub(rimraf, 'sync');
sinon.stub(fs, 'statSync');
return cleaner.cleanPrevious(settings, logger)
.catch(errorStub)
.then(function (data) {
expect(logger.log.calledWith('Found previous install attempt. Deleting...')).to.be(true);
});
});
it('should rethrow any exception from rimraf.sync', function () {
sinon.stub(fs, 'statSync');
sinon.stub(rimraf, 'sync', function () {
throw new Error('I am an error thrown by rimraf');
});
var errorStub = sinon.stub();
return cleaner.cleanPrevious(settings, logger)
.catch(errorStub)
.then(function () {
expect(errorStub.called).to.be(true);
});
});
it('should resolve if the working path is deleted', function () {
sinon.stub(rimraf, 'sync');
sinon.stub(fs, 'statSync');
return cleaner.cleanPrevious(settings, logger)
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
});
});
});
describe('cleanError', function () {
afterEach(function () {
rimraf.sync.restore();
});
it('should attempt to delete the working directory', function () {
sinon.stub(rimraf, 'sync');
cleaner.cleanError(settings);
expect(rimraf.sync.calledWith(settings.workingPath)).to.be(true);
});
it('should swallow any errors thrown by rimraf.sync', function () {
sinon.stub(rimraf, 'sync', function () {
throw new Error('Something bad happened.');
});
expect(cleaner.cleanError).withArgs(settings).to.not.throwError();
});
});
});
});
});

View file

@ -0,0 +1,278 @@
var root = require('requirefrom')('');
var expect = require('expect.js');
var sinon = require('sinon');
var nock = require('nock');
var glob = require('glob');
var rimraf = require('rimraf');
var join = require('path').join;
var pluginLogger = root('src/server/bin/plugin/pluginLogger');
var downloader = root('src/server/bin/plugin/pluginDownloader');
describe('kibana cli', function () {
describe('plugin downloader', function () {
var testWorkingPath;
var logger;
describe('_downloadSingle', function () {
beforeEach(function () {
logger = pluginLogger(false);
testWorkingPath = join(__dirname, '.test.data');
rimraf.sync(testWorkingPath);
sinon.stub(logger, 'log');
sinon.stub(logger, 'error');
});
afterEach(function () {
logger.log.restore();
logger.error.restore();
rimraf.sync(testWorkingPath);
});
it('should throw an ENOTFOUND error for a 404 error', function () {
var couchdb = nock('http://www.files.com')
.get('/plugin.tar.gz')
.reply(404);
var source = 'http://www.files.com/plugin.tar.gz';
var errorStub = sinon.stub();
return downloader._downloadSingle(source, testWorkingPath, 0, logger)
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
var files = glob.sync('**/*', { cwd: testWorkingPath });
expect(files).to.eql([]);
});
});
it('download and extract a valid plugin', function () {
var couchdb = nock('http://www.files.com')
.defaultReplyHeaders({
'content-length': '10'
})
.get('/plugin.tar.gz')
.replyWithFile(200, __dirname + '/replies/test-plugin-master.tar.gz');
var source = 'http://www.files.com/plugin.tar.gz';
return downloader._downloadSingle(source, testWorkingPath, 0, logger)
.then(function (data) {
var files = glob.sync('**/*', { cwd: testWorkingPath });
expect(files).to.eql([
'README.md',
'index.js',
'package.json',
'public',
'public/app.js'
]);
});
});
it('should abort the download and extraction for a corrupt archive.', function () {
var couchdb = nock('http://www.files.com')
.get('/plugin.tar.gz')
.replyWithFile(200, __dirname + '/replies/corrupt.tar.gz');
var source = 'http://www.files.com/plugin.tar.gz';
var errorStub = sinon.stub();
return downloader._downloadSingle(source, testWorkingPath, 0, logger)
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
var files = glob.sync('**/*', { cwd: testWorkingPath });
expect(files).to.eql([]);
});
});
});
describe('download', function () {
beforeEach(function () {
logger = pluginLogger(false);
sinon.stub(logger, 'log');
sinon.stub(logger, 'error');
testWorkingPath = join(__dirname, '.test.data');
rimraf.sync(testWorkingPath);
});
afterEach(function () {
logger.log.restore();
logger.error.restore();
rimraf.sync(testWorkingPath);
});
it('loop through bad urls until it finds a good one.', function () {
var settings = {
urls: [
'http://www.files.com/badfile1.tar.gz',
'http://www.files.com/badfile2.tar.gz',
'http://www.files.com/goodfile.tar.gz'
],
workingPath: testWorkingPath,
timeout: 0
};
var couchdb = nock('http://www.files.com')
.defaultReplyHeaders({
'content-length': '10'
})
.get('/badfile1.tar.gz')
.reply(404)
.get('/badfile2.tar.gz')
.reply(404)
.get('/goodfile.tar.gz')
.replyWithFile(200, __dirname + '/replies/test-plugin-master.tar.gz');
var errorStub = sinon.stub();
return downloader.download(settings, logger)
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(0).args[0]).to.match(/badfile1.tar.gz/);
expect(logger.log.getCall(1).args[0]).to.match(/badfile2.tar.gz/);
expect(logger.log.getCall(2).args[0]).to.match(/goodfile.tar.gz/);
expect(logger.log.lastCall.args[0]).to.match(/complete/i);
var files = glob.sync('**/*', { cwd: testWorkingPath });
expect(files).to.eql([
'README.md',
'index.js',
'package.json',
'public',
'public/app.js'
]);
});
});
it('stop looping through urls when it finds a good one.', function () {
var settings = {
urls: [
'http://www.files.com/badfile1.tar.gz',
'http://www.files.com/badfile2.tar.gz',
'http://www.files.com/goodfile.tar.gz',
'http://www.files.com/badfile3.tar.gz'
],
workingPath: testWorkingPath,
timeout: 0
};
var couchdb = nock('http://www.files.com')
.defaultReplyHeaders({
'content-length': '10'
})
.get('/badfile1.tar.gz')
.reply(404)
.get('/badfile2.tar.gz')
.reply(404)
.get('/goodfile.tar.gz')
.replyWithFile(200, __dirname + '/replies/test-plugin-master.tar.gz')
.get('/badfile3.tar.gz')
.reply(404);
var errorStub = sinon.stub();
return downloader.download(settings, logger)
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
for (var i = 0; i < logger.log.callCount; i++) {
expect(logger.log.getCall(i).args[0]).to.not.match(/badfile3.tar.gz/);
}
var files = glob.sync('**/*', { cwd: testWorkingPath });
expect(files).to.eql([
'README.md',
'index.js',
'package.json',
'public',
'public/app.js'
]);
});
});
it('Throw an error when it doesn\'t find a good url.', function () {
var settings = {
urls: [
'http://www.files.com/badfile1.tar.gz',
'http://www.files.com/badfile2.tar.gz',
'http://www.files.com/badfile3.tar.gz'
],
workingPath: testWorkingPath,
timeout: 0
};
var couchdb = nock('http://www.files.com')
.defaultReplyHeaders({
'content-length': '10'
})
.get('/badfile1.tar.gz')
.reply(404)
.get('/badfile2.tar.gz')
.reply(404)
.get('/badfile3.tar.gz')
.reply(404);
var errorStub = sinon.stub();
return downloader.download(settings, logger)
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/not a valid/i);
var files = glob.sync('**/*', { cwd: testWorkingPath });
expect(files).to.eql([]);
});
});
it('Throw an error when it tries to use an invalid url.', function () {
var settings = {
urls: [
'http://www.files.com/badfile1.tar.gz',
'http://www.files.com/badfile2.tar.gz',
'I should break everything',
'http://www.files.com/badfile3.tar.gz'
],
workingPath: testWorkingPath,
timeout: 0
};
var couchdb = nock('http://www.files.com')
.defaultReplyHeaders({
'content-length': '10'
})
.get('/badfile1.tar.gz')
.reply(404)
.get('/badfile2.tar.gz')
.reply(404)
.get('/badfile3.tar.gz')
.reply(404);
var errorStub = sinon.stub();
return downloader.download(settings, logger)
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/invalid/i);
for (var i = 0; i < logger.log.callCount; i++) {
expect(logger.log.getCall(i).args[0]).to.not.match(/badfile3.tar.gz/);
}
var files = glob.sync('**/*', { cwd: testWorkingPath });
expect(files).to.eql([]);
});
});
});
});
});

View file

@ -0,0 +1,70 @@
var root = require('requirefrom')('');
var expect = require('expect.js');
var sinon = require('sinon');
var nock = require('nock');
var glob = require('glob');
var rimraf = require('rimraf');
var fs = require('fs');
var join = require('path').join;
var pluginLogger = root('src/server/bin/plugin/pluginLogger');
var pluginInstaller = root('src/server/bin/plugin/pluginInstaller');
var cleaner = root('src/server/bin/plugin/pluginCleaner');
var Promise = require('bluebird');
describe('kibana cli', function () {
describe('plugin installer', function () {
describe('pluginInstaller', function () {
var logger;
var testWorkingPath;
beforeEach(function () {
logger = pluginLogger(false);
testWorkingPath = join(__dirname, '.test.data');
rimraf.sync(testWorkingPath);
sinon.stub(logger, 'log');
sinon.stub(logger, 'error');
});
afterEach(function () {
logger.log.restore();
logger.error.restore();
rimraf.sync(testWorkingPath);
});
it('should throw an error if the workingPath already exists.', function () {
sinon.stub(process, 'exit');
fs.mkdir(testWorkingPath, function (e) {});
var settings = {
pluginPath: testWorkingPath
};
var errorStub = sinon.stub();
return pluginInstaller.install(settings, logger)
.catch(errorStub)
.then(function (data) {
expect(logger.error.firstCall.args[0]).to.match(/already exists/);
expect(process.exit.called).to.be(true);
process.exit.restore();
});
});
it('should rethrow any non "ENOENT" error from fs.', function () {
sinon.stub(fs, 'statSync', function () {
throw new Error('This is unexpected.');
});
var settings = {
pluginPath: testWorkingPath
};
expect(pluginInstaller.install).withArgs(settings, logger).to.throwException(/this is unexpected/i);
});
});
});
});

View file

@ -0,0 +1,102 @@
var root = require('requirefrom')('');
var pluginLogger = root('src/server/bin/plugin/pluginLogger');
var expect = require('expect.js');
var sinon = require('sinon');
describe('kibana cli', function () {
describe('plugin installer', function () {
describe('logger', function () {
var logger;
describe('logger.log', function () {
beforeEach(function () {
logger = pluginLogger(false);
sinon.spy(process.stdout, 'write');
});
afterEach(function () {
process.stdout.write.restore();
});
it('should log messages to the console and append a new line', function () {
var message = 'this is my message';
logger.log(message);
expect(process.stdout.write.calledWith(message + '\n')).to.be(true);
});
it('should log messages to the console and append not append a new line', function () {
for (var i = 0; i < 10; i++) {
logger.log('.', true);
}
logger.log('Done!');
expect(process.stdout.write.callCount).to.be(12);
expect(process.stdout.write.getCall(0).args[0]).to.be('.');
expect(process.stdout.write.getCall(1).args[0]).to.be('.');
expect(process.stdout.write.getCall(2).args[0]).to.be('.');
expect(process.stdout.write.getCall(3).args[0]).to.be('.');
expect(process.stdout.write.getCall(4).args[0]).to.be('.');
expect(process.stdout.write.getCall(5).args[0]).to.be('.');
expect(process.stdout.write.getCall(6).args[0]).to.be('.');
expect(process.stdout.write.getCall(7).args[0]).to.be('.');
expect(process.stdout.write.getCall(8).args[0]).to.be('.');
expect(process.stdout.write.getCall(9).args[0]).to.be('.');
expect(process.stdout.write.getCall(10).args[0]).to.be('\n');
expect(process.stdout.write.getCall(11).args[0]).to.be('Done!\n');
});
it('should not log any messages when silent is set', function () {
logger = pluginLogger(true);
var message = 'this is my message';
logger.log(message);
for (var i = 0; i < 10; i++) {
logger.log('.', true);
}
logger.log('Done!');
expect(process.stdout.write.callCount).to.be(0);
});
});
describe('logger.error', function () {
beforeEach(function () {
logger = pluginLogger(false);
sinon.spy(process.stderr, 'write');
});
afterEach(function () {
process.stderr.write.restore();
});
it('should log error messages to the console and append a new line', function () {
var message = 'this is my error';
logger.error(message);
expect(process.stderr.write.calledWith(message + '\n')).to.be(true);
});
it('should log error messages to the console regardless of silent setting', function () {
logger = pluginLogger(true);
var message = 'this is my error';
logger.error(message);
expect(process.stderr.write.calledWith(message + '\n')).to.be(true);
});
});
});
});
});

View file

@ -0,0 +1,738 @@
var root = require('requirefrom')('');
var expect = require('expect.js');
var sinon = require('sinon');
var progressReporter = root('src/server/bin/plugin/progressReporter');
var pluginLogger = root('src/server/bin/plugin/pluginLogger');
describe('kibana cli', function () {
describe('plugin installer', function () {
describe('progressReporter', function () {
var logger;
var progress;
var request;
beforeEach(function () {
logger = pluginLogger(false);
sinon.stub(logger, 'log');
sinon.stub(logger, 'error');
request = {
abort: sinon.stub(),
emit: sinon.stub()
};
progress = progressReporter(logger, request);
});
afterEach(function () {
logger.log.restore();
logger.error.restore();
});
describe('handleResponse', function () {
describe('bad response codes', function () {
it('should set the state to error for response code = 400', function () {
progress.handleResponse({ statusCode: 400 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 401', function () {
progress.handleResponse({ statusCode: 401 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 402', function () {
progress.handleResponse({ statusCode: 402 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 403', function () {
progress.handleResponse({ statusCode: 403 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 404', function () {
progress.handleResponse({ statusCode: 404 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 405', function () {
progress.handleResponse({ statusCode: 405 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 406', function () {
progress.handleResponse({ statusCode: 406 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 407', function () {
progress.handleResponse({ statusCode: 407 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 408', function () {
progress.handleResponse({ statusCode: 408 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 409', function () {
progress.handleResponse({ statusCode: 409 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 410', function () {
progress.handleResponse({ statusCode: 410 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 411', function () {
progress.handleResponse({ statusCode: 411 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 412', function () {
progress.handleResponse({ statusCode: 412 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 413', function () {
progress.handleResponse({ statusCode: 413 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 414', function () {
progress.handleResponse({ statusCode: 414 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 415', function () {
progress.handleResponse({ statusCode: 415 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 416', function () {
progress.handleResponse({ statusCode: 416 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 417', function () {
progress.handleResponse({ statusCode: 417 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 500', function () {
progress.handleResponse({ statusCode: 500 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 501', function () {
progress.handleResponse({ statusCode: 501 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 502', function () {
progress.handleResponse({ statusCode: 502 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 503', function () {
progress.handleResponse({ statusCode: 503 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 504', function () {
progress.handleResponse({ statusCode: 504 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
it('should set the state to error for response code = 505', function () {
progress.handleResponse({ statusCode: 505 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(errorStub.lastCall.args[0].message).to.match(/ENOTFOUND/);
});
});
});
describe('good response codes', function () {
it('should set the state to error for response code = 200', function () {
progress.handleResponse({ statusCode: 200, headers: { 'content-length': 1000 } });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(logger.log.callCount - 2).args[0]).to.match(/1000/);
});
});
it('should set the state to error for response code = 201', function () {
progress.handleResponse({ statusCode: 201, headers: { 'content-length': 1000 } });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(logger.log.callCount - 2).args[0]).to.match(/1000/);
});
});
it('should set the state to error for response code = 202', function () {
progress.handleResponse({ statusCode: 202, headers: { 'content-length': 1000 } });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(logger.log.callCount - 2).args[0]).to.match(/1000/);
});
});
it('should set the state to error for response code = 203', function () {
progress.handleResponse({ statusCode: 203, headers: { 'content-length': 1000 } });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(logger.log.callCount - 2).args[0]).to.match(/1000/);
});
});
it('should set the state to error for response code = 204', function () {
progress.handleResponse({ statusCode: 204, headers: { 'content-length': 1000 } });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(logger.log.callCount - 2).args[0]).to.match(/1000/);
});
});
it('should set the state to error for response code = 205', function () {
progress.handleResponse({ statusCode: 205, headers: { 'content-length': 1000 } });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(logger.log.callCount - 2).args[0]).to.match(/1000/);
});
});
it('should set the state to error for response code = 206', function () {
progress.handleResponse({ statusCode: 206, headers: { 'content-length': 1000 } });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(logger.log.callCount - 2).args[0]).to.match(/1000/);
});
});
it('should set the state to error for response code = 300', function () {
progress.handleResponse({ statusCode: 300, headers: { 'content-length': 1000 } });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(logger.log.callCount - 2).args[0]).to.match(/1000/);
});
});
it('should set the state to error for response code = 301', function () {
progress.handleResponse({ statusCode: 301, headers: { 'content-length': 1000 } });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(logger.log.callCount - 2).args[0]).to.match(/1000/);
});
});
it('should set the state to error for response code = 302', function () {
progress.handleResponse({ statusCode: 302, headers: { 'content-length': 1000 } });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(logger.log.callCount - 2).args[0]).to.match(/1000/);
});
});
it('should set the state to error for response code = 303', function () {
progress.handleResponse({ statusCode: 303, headers: { 'content-length': 1000 } });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(logger.log.callCount - 2).args[0]).to.match(/1000/);
});
});
it('should set the state to error for response code = 304', function () {
progress.handleResponse({ statusCode: 304, headers: { 'content-length': 1000 } });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(logger.log.callCount - 2).args[0]).to.match(/1000/);
});
});
it('should set the state to error for response code = 305', function () {
progress.handleResponse({ statusCode: 305, headers: { 'content-length': 1000 } });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(logger.log.callCount - 2).args[0]).to.match(/1000/);
});
});
it('should set the state to error for response code = 306', function () {
progress.handleResponse({ statusCode: 306, headers: { 'content-length': 1000 } });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(logger.log.callCount - 2).args[0]).to.match(/1000/);
});
});
it('should set the state to error for response code = 307', function () {
progress.handleResponse({ statusCode: 307, headers: { 'content-length': 1000 } });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(logger.log.callCount - 2).args[0]).to.match(/1000/);
});
});
it('should log "unknown number of" for response codes < 400 without content-length header', function () {
progress.handleResponse({ statusCode: 200, headers: {} });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(logger.log.callCount - 2).args[0]).to.match(/unknown number/);
});
});
});
});
describe('handleData', function () {
it('should do nothing if the reporter is in an error state', function () {
progress.handleResponse({ statusCode: 400 });
progress.handleData({ length: 100 });
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(progress.hasError()).to.be(true);
expect(request.abort.called).to.be(true);
expect(logger.log.callCount).to.be(0);
});
});
it('should do nothing if handleResponse hasn\'t successfully executed yet', function () {
progress.handleData({ length: 100 });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(logger.log.callCount).to.be(1);
expect(logger.log.lastCall.args[0]).to.match(/complete/i);
});
});
it('should do nothing if handleResponse was called without a content-length header', function () {
progress.handleResponse({ statusCode: 200, headers: {} });
progress.handleData({ length: 100 });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(logger.log.callCount).to.be(2);
expect(logger.log.getCall(0).args[0]).to.match(/downloading/i);
expect(logger.log.getCall(1).args[0]).to.match(/complete/i);
});
});
it('should show a max of 20 dots for full prgress', function () {
progress.handleResponse({ statusCode: 200, headers: { 'content-length': 1000 } });
progress.handleData({ length: 1000 });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(logger.log.callCount).to.be(22);
expect(logger.log.getCall(0).args[0]).to.match(/downloading/i);
expect(logger.log.getCall(1).args[0]).to.be('.');
expect(logger.log.getCall(2).args[0]).to.be('.');
expect(logger.log.getCall(3).args[0]).to.be('.');
expect(logger.log.getCall(4).args[0]).to.be('.');
expect(logger.log.getCall(5).args[0]).to.be('.');
expect(logger.log.getCall(6).args[0]).to.be('.');
expect(logger.log.getCall(7).args[0]).to.be('.');
expect(logger.log.getCall(8).args[0]).to.be('.');
expect(logger.log.getCall(9).args[0]).to.be('.');
expect(logger.log.getCall(10).args[0]).to.be('.');
expect(logger.log.getCall(11).args[0]).to.be('.');
expect(logger.log.getCall(12).args[0]).to.be('.');
expect(logger.log.getCall(13).args[0]).to.be('.');
expect(logger.log.getCall(14).args[0]).to.be('.');
expect(logger.log.getCall(15).args[0]).to.be('.');
expect(logger.log.getCall(16).args[0]).to.be('.');
expect(logger.log.getCall(17).args[0]).to.be('.');
expect(logger.log.getCall(18).args[0]).to.be('.');
expect(logger.log.getCall(19).args[0]).to.be('.');
expect(logger.log.getCall(20).args[0]).to.be('.');
expect(logger.log.getCall(21).args[0]).to.match(/complete/i);
});
});
it('should show dot for each 5% of completion', function () {
progress.handleResponse({ statusCode: 200, headers: { 'content-length': 1000 } });
expect(logger.log.callCount).to.be(1);
progress.handleData({ length: 50 }); //5%
expect(logger.log.callCount).to.be(2);
progress.handleData({ length: 100 }); //15%
expect(logger.log.callCount).to.be(4);
progress.handleData({ length: 200 }); //25%
expect(logger.log.callCount).to.be(8);
progress.handleData({ length: 590 }); //94%
expect(logger.log.callCount).to.be(20);
progress.handleData({ length: 60 }); //100%
expect(logger.log.callCount).to.be(21);
//Any progress over 100% should be ignored.
progress.handleData({ length: 9999 });
expect(logger.log.callCount).to.be(21);
progress.handleEnd();
expect(logger.log.callCount).to.be(22);
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.getCall(0).args[0]).to.match(/downloading/i);
expect(logger.log.getCall(21).args[0]).to.match(/complete/i);
});
});
});
describe('handleEnd', function () {
it('should reject the deferred with a ENOTFOUND error if the reporter is in an error state', function () {
progress.handleResponse({ statusCode: 400 });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.firstCall.args[0].message).to.match(/ENOTFOUND/);
expect(errorStub.called).to.be(true);
});
});
it('should resolve if the reporter is not in an error state', function () {
progress.handleResponse({ statusCode: 307, headers: { 'content-length': 1000 } });
progress.handleEnd();
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
expect(logger.log.lastCall.args[0]).to.match(/complete/i);
});
});
});
describe('handleError', function () {
it('should log any errors', function () {
progress.handleError('ERRORMESSAGE', new Error('oops!'));
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(logger.error.callCount).to.be(1);
expect(logger.error.lastCall.args[0]).to.match(/oops!/);
});
});
it('should set the error state of the reporter', function () {
progress.handleError('ERRORMESSAGE', new Error('oops!'));
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(progress.hasError()).to.be(true);
});
});
it('should ignore all errors except the first.', function () {
progress.handleError('ERRORMESSAGE', new Error('oops!'));
progress.handleError('ERRORMESSAGE', new Error('second error!'));
progress.handleError('ERRORMESSAGE', new Error('third error!'));
progress.handleError('ERRORMESSAGE', new Error('fourth error!'));
var errorStub = sinon.stub();
return progress.deferred
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(true);
expect(logger.error.callCount).to.be(1);
expect(logger.error.lastCall.args[0]).to.match(/oops!/);
});
});
});
});
});
});

Binary file not shown.

View file

@ -0,0 +1,13 @@
{
"name": "test-plugin",
"version": "1.0.0",
"description": "just a test plugin",
"repository": {
"type": "git",
"url": "http://website.git"
},
"dependencies": {
"bluebird": "2.9.30"
},
"license": "Apache-2.0"
}

View file

@ -0,0 +1,287 @@
var root = require('requirefrom')('');
var settingParser = root('src/server/bin/plugin/settingParser');
var path = require('path');
var expect = require('expect.js');
describe('kibana cli', function () {
describe('plugin installer', function () {
describe('command line option parsing', function () {
describe('parseMilliseconds function', function () {
it('should return 0 for an empty string', function () {
var value = '';
var result = settingParser.parseMilliseconds(value);
expect(result).to.be(0);
});
it('should return 0 for a number with an invalid unit of measure', function () {
var result = settingParser.parseMilliseconds('1gigablasts');
expect(result).to.be(0);
});
it('should assume a number with no unit of measure is specified as milliseconds', function () {
var result = settingParser.parseMilliseconds(1);
expect(result).to.be(1);
result = settingParser.parseMilliseconds('1');
expect(result).to.be(1);
});
it('should interpret a number with "s" as the unit of measure as seconds', function () {
var result = settingParser.parseMilliseconds('5s');
expect(result).to.be(5 * 1000);
});
it('should interpret a number with "second" as the unit of measure as seconds', function () {
var result = settingParser.parseMilliseconds('5second');
expect(result).to.be(5 * 1000);
});
it('should interpret a number with "seconds" as the unit of measure as seconds', function () {
var result = settingParser.parseMilliseconds('5seconds');
expect(result).to.be(5 * 1000);
});
it('should interpret a number with "m" as the unit of measure as minutes', function () {
var result = settingParser.parseMilliseconds('9m');
expect(result).to.be(9 * 1000 * 60);
});
it('should interpret a number with "minute" as the unit of measure as minutes', function () {
var result = settingParser.parseMilliseconds('9minute');
expect(result).to.be(9 * 1000 * 60);
});
it('should interpret a number with "minutes" as the unit of measure as minutes', function () {
var result = settingParser.parseMilliseconds('9minutes');
expect(result).to.be(9 * 1000 * 60);
});
});
describe('parse function', function () {
it('should require the user to specify either install and remove', function () {
options.install = null;
expect(settingParser.parse).withArgs(options)
.to.throwException(/Please specify either --install or --remove./);
});
it('should not allow the user to specify both install and remove', function () {
options.remove = 'package';
options.install = 'org/package/version';
expect(settingParser.parse).withArgs(options)
.to.throwException(/Please specify either --install or --remove./);
});
var options;
beforeEach(function () {
options = { install: 'dummy/dummy' };
});
describe('silent option', function () {
it('should default to false', function () {
var settings = settingParser.parse(options);
expect(settings).to.have.property('silent', false);
});
it('should set settings.silent property to true', function () {
options.silent = true;
var settings = settingParser.parse(options);
expect(settings).to.have.property('silent', true);
});
});
describe('timeout option', function () {
it('should default to 0 (milliseconds)', function () {
var settings = settingParser.parse(options);
expect(settings).to.have.property('timeout', 0);
});
it('should set settings.timeout property to specified value', function () {
options.timeout = 1234;
var settings = settingParser.parse(options);
expect(settings).to.have.property('timeout', 1234);
});
});
describe('install option', function () {
it('should set settings.action property to "install"', function () {
options.install = 'org/package/version';
var settings = settingParser.parse(options);
expect(settings).to.have.property('action', 'install');
});
it('should allow two parts to the install parameter', function () {
options.install = 'kibana/test-plugin';
expect(settingParser.parse).withArgs(options)
.to.not.throwException();
var settings = settingParser.parse(options);
expect(settings).to.have.property('organization', 'kibana');
expect(settings).to.have.property('package', 'test-plugin');
expect(settings).to.have.property('version', undefined);
});
it('should allow three parts to the install parameter', function () {
options.install = 'kibana/test-plugin/v1.0.1';
expect(settingParser.parse).withArgs(options)
.to.not.throwException();
var settings = settingParser.parse(options);
expect(settings).to.have.property('organization', 'kibana');
expect(settings).to.have.property('package', 'test-plugin');
expect(settings).to.have.property('version', 'v1.0.1');
});
it('should not allow one part to the install parameter', function () {
options.install = 'test-plugin';
expect(settingParser.parse).withArgs(options)
.to.throwException(/Invalid install option. Please use the format <org>\/<plugin>\/<version>./);
});
it('should not allow more than three parts to the install parameter', function () {
options.install = 'kibana/test-plugin/v1.0.1/dummy';
expect(settingParser.parse).withArgs(options)
.to.throwException(/Invalid install option. Please use the format <org>\/<plugin>\/<version>./);
});
it('should populate the urls collection properly when no version specified', function () {
options.install = 'kibana/test-plugin';
var settings = settingParser.parse(options);
expect(settings.urls).to.have.property('length', 2);
expect(settings.urls).to.contain('https://download.elastic.co/kibana/test-plugin/test-plugin-latest.tar.gz');
expect(settings.urls).to.contain('https://github.com/kibana/test-plugin/archive/master.tar.gz');
});
it('should populate the urls collection properly version specified', function () {
options.install = 'kibana/test-plugin/v1.1.1';
var settings = settingParser.parse(options);
expect(settings.urls).to.have.property('length', 2);
expect(settings.urls).to.contain('https://download.elastic.co/kibana/test-plugin/test-plugin-v1.1.1.tar.gz');
expect(settings.urls).to.contain('https://github.com/kibana/test-plugin/archive/v1.1.1.tar.gz');
});
it('should populate the pluginPath', function () {
options.install = 'kibana/test-plugin';
var settings = settingParser.parse(options);
var expected = path.resolve(__dirname, '..', '..', '..', '..', '..', 'src', 'server', 'bin', 'plugins', 'test-plugin');
expect(settings).to.have.property('pluginPath', expected);
});
describe('with url option', function () {
it('should allow one part to the install parameter', function () {
options.install = 'test-plugin';
options.url = 'http://www.google.com/plugin.tar.gz';
expect(settingParser.parse).withArgs(options)
.to.not.throwException();
var settings = settingParser.parse(options);
expect(settings).to.have.property('package', 'test-plugin');
});
it('should not allow more than one part to the install parameter', function () {
options.url = 'http://www.google.com/plugin.tar.gz';
options.install = 'kibana/test-plugin';
expect(settingParser.parse).withArgs(options)
.to.throwException(/Invalid install option. When providing a url, please use the format <plugin>./);
});
it('should result in only the specified url in urls collection', function () {
var url = 'http://www.google.com/plugin.tar.gz';
options.install = 'test-plugin';
options.url = url;
var settings = settingParser.parse(options);
expect(settings).to.have.property('urls');
expect(settings.urls).to.be.an('array');
expect(settings.urls).to.have.property('length', 1);
expect(settings.urls).to.contain(url);
});
});
});
describe('remove option', function () {
it('should set settings.action property to "remove"', function () {
options.install = null;
options.remove = 'package';
var settings = settingParser.parse(options);
expect(settings).to.have.property('action', 'remove');
});
it('should allow one part to the remove parameter', function () {
options.install = null;
options.remove = 'test-plugin';
var settings = settingParser.parse(options);
expect(settings).to.have.property('package', 'test-plugin');
});
it('should not allow more than one part to the install parameter', function () {
options.install = null;
options.remove = 'kibana/test-plugin';
expect(settingParser.parse).withArgs(options)
.to.throwException(/Invalid remove option. Please use the format <plugin>./);
});
it('should populate the pluginPath', function () {
options.install = null;
options.remove = 'test-plugin';
var settings = settingParser.parse(options);
var expected = path.resolve(__dirname, '..', '..', '..', '..', '..', 'src', 'server', 'bin', 'plugins', 'test-plugin');
expect(settings).to.have.property('pluginPath', expected);
});
});
});
});
});
});