[pack installer] modified install action

This commit is contained in:
Jim Unger 2016-03-03 13:31:58 -06:00
parent 8f4ea5678f
commit e50a79166a
26 changed files with 904 additions and 874 deletions

View file

@ -3,8 +3,8 @@ import sinon from 'sinon';
import fs from 'fs';
import rimraf from 'rimraf';
import pluginCleaner from '../plugin_cleaner';
import pluginLogger from '../plugin_logger';
import { cleanPrevious, cleanError } from '../cleanup';
import Logger from '../../lib/logger';
describe('kibana cli', function () {
@ -24,8 +24,7 @@ describe('kibana cli', function () {
beforeEach(function () {
errorStub = sinon.stub();
logger = pluginLogger(false);
cleaner = pluginCleaner(settings, logger);
logger = new Logger(settings);
sinon.stub(logger, 'log');
sinon.stub(logger, 'error');
request = {
@ -49,7 +48,7 @@ describe('kibana cli', function () {
throw error;
});
return cleaner.cleanPrevious(logger)
return cleanPrevious(settings, logger)
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
@ -64,7 +63,7 @@ describe('kibana cli', function () {
});
errorStub = sinon.stub();
return cleaner.cleanPrevious(logger)
return cleanPrevious(settings, logger)
.catch(errorStub)
.then(function () {
expect(errorStub.called).to.be(true);
@ -75,7 +74,7 @@ describe('kibana cli', function () {
sinon.stub(rimraf, 'sync');
sinon.stub(fs, 'statSync');
return cleaner.cleanPrevious(logger)
return cleanPrevious(settings, logger)
.catch(errorStub)
.then(function (data) {
expect(logger.log.calledWith('Found previous install attempt. Deleting...')).to.be(true);
@ -89,7 +88,7 @@ describe('kibana cli', function () {
});
errorStub = sinon.stub();
return cleaner.cleanPrevious(logger)
return cleanPrevious(settings, logger)
.catch(errorStub)
.then(function () {
expect(errorStub.called).to.be(true);
@ -100,7 +99,7 @@ describe('kibana cli', function () {
sinon.stub(rimraf, 'sync');
sinon.stub(fs, 'statSync');
return cleaner.cleanPrevious(logger)
return cleanPrevious(settings, logger)
.catch(errorStub)
.then(function (data) {
expect(errorStub.called).to.be(false);
@ -110,12 +109,10 @@ describe('kibana cli', function () {
});
describe('cleanError', function () {
let cleaner;
let logger;
beforeEach(function () {
logger = pluginLogger(false);
cleaner = pluginCleaner(settings, logger);
logger = new Logger(settings);
});
afterEach(function () {
@ -125,7 +122,7 @@ describe('kibana cli', function () {
it('should attempt to delete the working directory', function () {
sinon.stub(rimraf, 'sync');
cleaner.cleanError();
cleanError(settings);
expect(rimraf.sync.calledWith(settings.workingPath)).to.be(true);
});
@ -134,7 +131,7 @@ describe('kibana cli', function () {
throw new Error('Something bad happened.');
});
expect(cleaner.cleanError).withArgs(settings).to.not.throwError();
expect(cleanError).withArgs(settings).to.not.throwError();
});
});

View file

@ -1,11 +1,11 @@
import expect from 'expect.js';
import sinon from 'sinon';
import nock from 'nock';
import glob from 'glob-all';
import glob from 'glob';
import rimraf from 'rimraf';
import mkdirp from 'mkdirp';
import pluginLogger from '../plugin_logger';
import pluginDownloader from '../plugin_downloader';
import Logger from '../../lib/logger';
import { download, _downloadSingle } from '../download';
import { join } from 'path';
describe('kibana cli', function () {
@ -13,8 +13,14 @@ describe('kibana cli', function () {
describe('plugin downloader', function () {
const testWorkingPath = join(__dirname, '.test.data');
const tempArchiveFilePath = join(testWorkingPath, 'archive.part');
let logger;
let downloader;
const settings = {
urls: [],
workingPath: testWorkingPath,
tempArchiveFile: tempArchiveFilePath,
timeout: 0
};
const logger = new Logger(settings);
function expectWorkingPathEmpty() {
const files = glob.sync('**/*', { cwd: testWorkingPath });
@ -35,7 +41,6 @@ describe('kibana cli', function () {
}
beforeEach(function () {
logger = pluginLogger(false);
sinon.stub(logger, 'log');
sinon.stub(logger, 'error');
rimraf.sync(testWorkingPath);
@ -51,35 +56,10 @@ describe('kibana cli', function () {
describe('_downloadSingle', function () {
beforeEach(function () {
const settings = {
urls: [],
workingPath: testWorkingPath,
tempArchiveFile: tempArchiveFilePath,
timeout: 0
};
downloader = pluginDownloader(settings, logger);
});
describe('http downloader', function () {
it('should download an unsupported file type, but return undefined for archiveType', function () {
const filePath = join(__dirname, 'replies/banana.jpg');
const couchdb = nock('http://www.files.com')
.defaultReplyHeaders({
'content-length': '10',
'content-type': 'image/jpeg'
})
.get('/banana.jpg')
.replyWithFile(200, filePath);
const sourceUrl = 'http://www.files.com/banana.jpg';
return downloader._downloadSingle(sourceUrl)
.then(function (data) {
expect(data.archiveType).to.be(undefined);
expectWorkingPathNotEmpty();
});
});
it('should throw an ENOTFOUND error for a http ulr that returns 404', function () {
const couchdb = nock('http://www.files.com')
.get('/plugin.tar.gz')
@ -87,7 +67,7 @@ describe('kibana cli', function () {
const sourceUrl = 'http://www.files.com/plugin.tar.gz';
return downloader._downloadSingle(sourceUrl)
return _downloadSingle(settings, logger, sourceUrl)
.then(shouldReject, function (err) {
expect(err.message).to.match(/ENOTFOUND/);
expectWorkingPathEmpty();
@ -97,54 +77,15 @@ describe('kibana cli', function () {
it('should throw an ENOTFOUND error for an invalid url', function () {
const sourceUrl = 'i am an invalid url';
return downloader._downloadSingle(sourceUrl)
return _downloadSingle(settings, logger, sourceUrl)
.then(shouldReject, function (err) {
expect(err.message).to.match(/ENOTFOUND/);
expectWorkingPathEmpty();
});
});
it('should download a tarball from a valid http url', function () {
const filePath = join(__dirname, 'replies/test_plugin_master.tar.gz');
const couchdb = nock('http://www.files.com')
.defaultReplyHeaders({
'content-length': '10',
'content-type': 'application/x-gzip'
})
.get('/plugin.tar.gz')
.replyWithFile(200, filePath);
const sourceUrl = 'http://www.files.com/plugin.tar.gz';
return downloader._downloadSingle(sourceUrl)
.then(function (data) {
expect(data.archiveType).to.be('.tar.gz');
expectWorkingPathNotEmpty();
});
});
it('should consider .tgz files as archive type .tar.gz', function () {
const filePath = join(__dirname, 'replies/test_plugin_master.tar.gz');
const couchdb = nock('http://www.files.com')
.defaultReplyHeaders({
'content-length': '10'
})
.get('/plugin.tgz')
.replyWithFile(200, filePath);
const sourceUrl = 'http://www.files.com/plugin.tgz';
return downloader._downloadSingle(sourceUrl)
.then(function (data) {
expect(data.archiveType).to.be('.tar.gz');
expectWorkingPathNotEmpty();
});
});
it('should download a zip from a valid http url', function () {
const filePath = join(__dirname, 'replies/test_plugin_master.zip');
it('should download a file from a valid http url', function () {
const filePath = join(__dirname, 'replies/banana.jpg');
const couchdb = nock('http://www.files.com')
.defaultReplyHeaders({
@ -156,9 +97,8 @@ describe('kibana cli', function () {
const sourceUrl = 'http://www.files.com/plugin.zip';
return downloader._downloadSingle(sourceUrl)
.then(function (data) {
expect(data.archiveType).to.be('.zip');
return _downloadSingle(settings, logger, sourceUrl)
.then(function () {
expectWorkingPathNotEmpty();
});
});
@ -167,54 +107,23 @@ describe('kibana cli', function () {
describe('local file downloader', function () {
it('should copy an unsupported file type, but return undefined for archiveType', function () {
const filePath = join(__dirname, 'replies/banana.jpg');
const sourceUrl = 'file://' + filePath.replace(/\\/g, '/');
const couchdb = nock('http://www.files.com')
.defaultReplyHeaders({
'content-length': '10',
'content-type': 'image/jpeg'
})
.get('/banana.jpg')
.replyWithFile(200, filePath);
return downloader._downloadSingle(sourceUrl)
.then(function (data) {
expect(data.archiveType).to.be(undefined);
expectWorkingPathNotEmpty();
});
});
it('should throw an ENOTFOUND error for an invalid local file', function () {
const filePath = join(__dirname, 'replies/i-am-not-there.tar.gz');
const filePath = join(__dirname, 'replies/i-am-not-there.zip');
const sourceUrl = 'file://' + filePath.replace(/\\/g, '/');
return downloader._downloadSingle(sourceUrl)
return _downloadSingle(settings, logger, sourceUrl)
.then(shouldReject, function (err) {
expect(err.message).to.match(/ENOTFOUND/);
expectWorkingPathEmpty();
});
});
it('should copy a tarball from a valid local file', function () {
const filePath = join(__dirname, 'replies/test_plugin_master.tar.gz');
it('should copy a valid local file', function () {
const filePath = join(__dirname, 'replies/banana.jpg');
const sourceUrl = 'file://' + filePath.replace(/\\/g, '/');
return downloader._downloadSingle(sourceUrl)
.then(function (data) {
expect(data.archiveType).to.be('.tar.gz');
expectWorkingPathNotEmpty();
});
});
it('should copy a zip from a valid local file', function () {
const filePath = join(__dirname, 'replies/test_plugin_master.zip');
const sourceUrl = 'file://' + filePath.replace(/\\/g, '/');
return downloader._downloadSingle(sourceUrl)
.then(function (data) {
expect(data.archiveType).to.be('.zip');
return _downloadSingle(settings, logger, sourceUrl)
.then(function () {
expectWorkingPathNotEmpty();
});
});
@ -225,19 +134,13 @@ describe('kibana cli', function () {
describe('download', function () {
it('should loop through bad urls until it finds a good one.', function () {
const filePath = join(__dirname, 'replies/test_plugin_master.tar.gz');
const settings = {
urls: [
'http://www.files.com/badfile1.tar.gz',
'http://www.files.com/badfile2.tar.gz',
'I am a bad uri',
'http://www.files.com/goodfile.tar.gz'
],
workingPath: testWorkingPath,
tempArchiveFile: tempArchiveFilePath,
timeout: 0
};
downloader = pluginDownloader(settings, logger);
const filePath = join(__dirname, 'replies/test_plugin.zip');
settings.urls = [
'http://www.files.com/badfile1.tar.gz',
'http://www.files.com/badfile2.tar.gz',
'I am a bad uri',
'http://www.files.com/goodfile.tar.gz'
];
const couchdb = nock('http://www.files.com')
.defaultReplyHeaders({
@ -250,8 +153,8 @@ describe('kibana cli', function () {
.get('/goodfile.tar.gz')
.replyWithFile(200, filePath);
return downloader.download(settings, logger)
.then(function (data) {
return download(settings, logger)
.then(function () {
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(/I am a bad uri/);
@ -261,19 +164,13 @@ describe('kibana cli', function () {
});
it('should stop looping through urls when it finds a good one.', function () {
const filePath = join(__dirname, 'replies/test_plugin_master.tar.gz');
const 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,
tempArchiveFile: tempArchiveFilePath,
timeout: 0
};
downloader = pluginDownloader(settings, logger);
const filePath = join(__dirname, 'replies/test_plugin.zip');
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'
];
const couchdb = nock('http://www.files.com')
.defaultReplyHeaders({
@ -288,8 +185,8 @@ describe('kibana cli', function () {
.get('/badfile3.tar.gz')
.reply(404);
return downloader.download(settings, logger)
.then(function (data) {
return download(settings, logger)
.then(function () {
for (let i = 0; i < logger.log.callCount; i++) {
expect(logger.log.getCall(i).args[0]).to.not.match(/badfile3.tar.gz/);
}
@ -298,17 +195,11 @@ describe('kibana cli', function () {
});
it('should throw an error when it doesn\'t find a good url.', function () {
const 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,
tempArchiveFile: tempArchiveFilePath,
timeout: 0
};
downloader = pluginDownloader(settings, logger);
settings.urls = [
'http://www.files.com/badfile1.tar.gz',
'http://www.files.com/badfile2.tar.gz',
'http://www.files.com/badfile3.tar.gz'
];
const couchdb = nock('http://www.files.com')
.defaultReplyHeaders({
@ -321,7 +212,7 @@ describe('kibana cli', function () {
.get('/badfile3.tar.gz')
.reply(404);
return downloader.download(settings, logger)
return download(settings, logger)
.then(shouldReject, function (err) {
expect(err.message).to.match(/no valid url specified/i);
expectWorkingPathEmpty();

View file

@ -1,6 +1,6 @@
import expect from 'expect.js';
import sinon from 'sinon';
import plugin from '../plugin';
import index from '../index';
describe('kibana cli', function () {
@ -18,8 +18,8 @@ describe('kibana cli', function () {
it('should define the command', function () {
sinon.spy(program, 'command');
plugin(program);
expect(program.command.calledWith('plugin')).to.be(true);
index(program);
expect(program.command.calledWith('install <plugin/url>')).to.be(true);
program.command.restore();
});
@ -27,8 +27,8 @@ describe('kibana cli', function () {
it('should define the description', function () {
sinon.spy(program, 'description');
plugin(program);
expect(program.description.calledWith('Maintain Plugins')).to.be(true);
index(program);
expect(program.description.calledWith('Install a plugin')).to.be(true);
program.description.restore();
});
@ -37,14 +37,14 @@ describe('kibana cli', function () {
const spy = sinon.spy(program, 'option');
const options = [
/-i/,
/-r/,
/-q/,
/-s/,
/-u/,
/-t/
/-c/,
/-t/,
/-d/
];
plugin(program);
index(program);
for (let i = 0; i < spy.callCount; i++) {
const call = spy.getCall(i);
@ -63,7 +63,7 @@ describe('kibana cli', function () {
it('should call the action function', function () {
sinon.spy(program, 'action');
plugin(program);
index(program);
expect(program.action.calledOnce).to.be(true);
program.action.restore();

View file

@ -1,51 +1,104 @@
import expect from 'expect.js';
import sinon from 'sinon';
import rimraf from 'rimraf';
import pluginLogger from '../plugin_logger';
import pluginInstaller from '../plugin_installer';
import { mkdirSync } from 'fs';
import Logger from '../../lib/logger';
import { join } from 'path';
import rimraf from 'rimraf';
import mkdirp from 'mkdirp';
import { existingInstall, checkVersion } from '../kibana';
describe('kibana cli', function () {
describe('plugin installer', function () {
describe('pluginInstaller', function () {
let logger;
let testWorkingPath;
let processExitStub;
describe('kibana', function () {
const testWorkingPath = join(__dirname, '.test.data');
const tempArchiveFilePath = join(testWorkingPath, 'archive.part');
beforeEach(function () {
processExitStub = undefined;
logger = pluginLogger(false);
testWorkingPath = join(__dirname, '.test.data');
rimraf.sync(testWorkingPath);
sinon.stub(logger, 'log');
sinon.stub(logger, 'error');
const settings = {
workingPath: testWorkingPath,
tempArchiveFile: tempArchiveFilePath,
plugin: 'test-plugin',
version: '1.0.0',
plugins: [ { name: 'foo', path: join(testWorkingPath, 'foo') } ]
};
const logger = new Logger(settings);
describe('checkVersion', function () {
beforeEach(function () {
rimraf.sync(testWorkingPath);
mkdirp.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 plugin does contain a version.', function () {
const errorStub = sinon.stub();
try {
checkVersion(settings);
}
catch (err) {
errorStub(err);
}
expect(errorStub.firstCall.args[0]).to.match(/plugin version not found/i);
});
it('should throw an error if plugin version does does not match kibana version', function () {
const errorStub = sinon.stub();
settings.plugins[0].version = '1.2.3.4';
try {
checkVersion(settings);
}
catch (err) {
errorStub(err);
}
expect(errorStub.firstCall.args[0]).to.match(/incorrect version/i);
});
});
afterEach(function () {
if (processExitStub) processExitStub.restore();
logger.log.restore();
logger.error.restore();
rimraf.sync(testWorkingPath);
});
describe('existingInstall', function () {
let testWorkingPath;
let processExitStub;
it('should throw an error if the workingPath already exists.', function () {
processExitStub = sinon.stub(process, 'exit');
mkdirSync(testWorkingPath);
beforeEach(function () {
processExitStub = sinon.stub(process, 'exit');
testWorkingPath = join(__dirname, '.test.data');
rimraf.sync(testWorkingPath);
sinon.stub(logger, 'log');
sinon.stub(logger, 'error');
});
let settings = {
pluginPath: testWorkingPath
};
afterEach(function () {
processExitStub.restore();
logger.log.restore();
logger.error.restore();
rimraf.sync(testWorkingPath);
});
it('should throw an error if the workingPath already exists.', function () {
mkdirp.sync(settings.plugins[0].path);
existingInstall(settings, logger);
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);
});
it('should not throw an error if the workingPath does not exist.', function () {
existingInstall(settings, logger);
expect(logger.error.called).to.be(false);
});
});
});

View file

@ -0,0 +1,164 @@
import expect from 'expect.js';
import sinon from 'sinon';
import glob from 'glob';
import rimraf from 'rimraf';
import mkdirp from 'mkdirp';
import Logger from '../../lib/logger';
import { extract, getPackData } from '../pack';
import { _downloadSingle } from '../download';
import { join } from 'path';
describe('kibana cli', function () {
describe('plugin extractor', function () {
const testWorkingPath = join(__dirname, '.test.data');
const tempArchiveFilePath = join(testWorkingPath, 'archive.part');
const testPluginPath = join(testWorkingPath, '.installedPlugins');
let logger;
const settings = {
workingPath: testWorkingPath,
tempArchiveFile: tempArchiveFilePath,
pluginDir: testPluginPath,
plugin: 'test-plugin'
};
beforeEach(function () {
logger = new Logger(settings);
sinon.stub(logger, 'log');
sinon.stub(logger, 'error');
rimraf.sync(testWorkingPath);
mkdirp.sync(testWorkingPath);
});
afterEach(function () {
logger.log.restore();
logger.error.restore();
rimraf.sync(testWorkingPath);
});
function copyReplyFile(filename) {
const filePath = join(__dirname, 'replies', filename);
const sourceUrl = 'file://' + filePath.replace(/\\/g, '/');
return _downloadSingle(settings, logger, sourceUrl);
}
function shouldReject() {
throw new Error('expected the promise to reject');
}
describe('extract', function () {
//Also only extracts the content from the kibana folder.
//Ignores the others.
it('successfully extract a valid zip', function () {
return copyReplyFile('test_plugin.zip')
.then(() => {
return getPackData(settings, logger);
})
.then(() => {
return extract(settings, logger);
})
.then(() => {
const files = glob.sync('**/*', { cwd: testWorkingPath });
const expected = [
'archive.part',
'README.md',
'index.js',
'package.json',
'public',
'public/app.js',
'extra file only in zip.txt'
];
expect(files.sort()).to.eql(expected.sort());
});
});
});
describe('getPackData', function () {
it('populate settings.plugins', function () {
return copyReplyFile('test_plugin.zip')
.then(() => {
return getPackData(settings, logger);
})
.then(() => {
expect(settings.plugins[0].name).to.be('test-plugin');
expect(settings.plugins[0].folder).to.be('test-plugin');
expect(settings.plugins[0].version).to.be('1.0.0');
expect(settings.plugins[0].platform).to.be(undefined);
});
});
it('populate settings.plugins with multiple plugins', function () {
return copyReplyFile('test_plugin_many.zip')
.then(() => {
return getPackData(settings, logger);
})
.then(() => {
expect(settings.plugins[0].name).to.be('funger-plugin');
expect(settings.plugins[0].file).to.be('kibana/funger-plugin/package.json');
expect(settings.plugins[0].folder).to.be('funger-plugin');
expect(settings.plugins[0].version).to.be('1.0.0');
expect(settings.plugins[0].platform).to.be(undefined);
expect(settings.plugins[1].name).to.be('pdf');
expect(settings.plugins[1].file).to.be('kibana/pdf-linux/package.json');
expect(settings.plugins[1].folder).to.be('pdf-linux');
expect(settings.plugins[1].version).to.be('1.0.0');
expect(settings.plugins[1].platform).to.be('linux');
expect(settings.plugins[2].name).to.be('pdf');
expect(settings.plugins[2].file).to.be('kibana/pdf-win32/package.json');
expect(settings.plugins[2].folder).to.be('pdf-win32');
expect(settings.plugins[2].version).to.be('1.0.0');
expect(settings.plugins[2].platform).to.be('win32');
expect(settings.plugins[3].name).to.be('pdf');
expect(settings.plugins[3].file).to.be('kibana/pdf-win64/package.json');
expect(settings.plugins[3].folder).to.be('pdf-win64');
expect(settings.plugins[3].version).to.be('1.0.0');
expect(settings.plugins[3].platform).to.be('win64');
expect(settings.plugins[4].name).to.be('pdf');
expect(settings.plugins[4].file).to.be('kibana/pdf/package.json');
expect(settings.plugins[4].folder).to.be('pdf');
expect(settings.plugins[4].version).to.be('1.0.0');
expect(settings.plugins[4].platform).to.be(undefined);
expect(settings.plugins[5].name).to.be('test-plugin');
expect(settings.plugins[5].file).to.be('kibana/test-plugin/package.json');
expect(settings.plugins[5].folder).to.be('test-plugin');
expect(settings.plugins[5].version).to.be('1.0.0');
expect(settings.plugins[5].platform).to.be(undefined);
});
});
it('throw an error if there is no kibana plugin', function () {
return copyReplyFile('test_plugin_no_kibana.zip')
.then((data) => {
return getPackData(settings, logger);
})
.then(shouldReject, (err) => {
expect(err.message).to.match(/No kibana plugins found in archive/i);
});
});
it('throw an error with a corrupt zip', function () {
return copyReplyFile('corrupt.zip')
.then((data) => {
return getPackData(settings, logger);
})
.then(shouldReject, (err) => {
expect(err.message).to.match(/error retrieving/i);
});
});
});
});
});

View file

@ -1,23 +1,22 @@
import expect from 'expect.js';
import sinon from 'sinon';
import progressReporter from '../progress_reporter';
import pluginLogger from '../plugin_logger';
import Progress from '../progress';
import Logger from '../../lib/logger';
describe('kibana cli', function () {
describe('plugin installer', function () {
describe('progressReporter', function () {
let logger;
let progress;
let request;
beforeEach(function () {
logger = pluginLogger({ silent: false, quiet: false });
logger = new Logger({ silent: false, quiet: false });
sinon.stub(logger, 'log');
sinon.stub(logger, 'error');
progress = progressReporter(logger);
progress = new Progress(logger);
});
afterEach(function () {

View file

@ -0,0 +1,3 @@
{
"name": "test-plugin",
}

View file

@ -1,8 +1,8 @@
import path from 'path';
import expect from 'expect.js';
import fromRoot from '../../../utils/from_root';
import settingParser from '../setting_parser';
import { resolve } from 'path';
import { parseMilliseconds, parse, getPlatform } from '../settings';
describe('kibana cli', function () {
@ -12,56 +12,53 @@ describe('kibana cli', function () {
describe('parseMilliseconds function', function () {
var parser = settingParser();
it('should return 0 for an empty string', function () {
var value = '';
var result = parser.parseMilliseconds(value);
const value = '';
const result = parseMilliseconds(value);
expect(result).to.be(0);
});
it('should return 0 for a number with an invalid unit of measure', function () {
var result = parser.parseMilliseconds('1gigablasts');
const result = parseMilliseconds('1gigablasts');
expect(result).to.be(0);
});
it('should assume a number with no unit of measure is specified as milliseconds', function () {
var result = parser.parseMilliseconds(1);
const result = parseMilliseconds(1);
expect(result).to.be(1);
result = parser.parseMilliseconds('1');
expect(result).to.be(1);
const result2 = parseMilliseconds('1');
expect(result2).to.be(1);
});
it('should interpret a number with "s" as the unit of measure as seconds', function () {
var result = parser.parseMilliseconds('5s');
const result = 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 = parser.parseMilliseconds('5second');
const result = 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 = parser.parseMilliseconds('5seconds');
const result = 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 = parser.parseMilliseconds('9m');
const result = 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 = parser.parseMilliseconds('9minute');
const result = 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 = parser.parseMilliseconds('9minutes');
const result = parseMilliseconds('9minutes');
expect(result).to.be(9 * 1000 * 60);
});
@ -69,65 +66,41 @@ describe('kibana cli', function () {
describe('parse function', function () {
var options;
var parser;
const command = 'plugin name';
let options = {};
const kbnPackage = { version: 1234 };
beforeEach(function () {
options = { install: 'dummy/dummy', pluginDir: fromRoot('installedPlugins') };
options = { pluginDir: fromRoot('installedPlugins') };
});
it('should require the user to specify either install, remove, or list', function () {
options.install = null;
parser = settingParser(options);
describe('timeout option', function () {
expect(parser.parse).withArgs().to.throwError(/Please specify either --install, --remove, or --list./);
});
it('should default to 0 (milliseconds)', function () {
const settings = parse(command, options, kbnPackage);
it('should not allow the user to specify both install and remove', function () {
options.remove = 'package';
options.install = 'org/package/version';
parser = settingParser(options);
expect(settings.timeout).to.be(0);
});
expect(parser.parse).withArgs().to.throwError(/Please specify either --install, --remove, or --list./);
});
it('should set settings.timeout property', function () {
options.timeout = 1234;
const settings = parse(command, options, kbnPackage);
it('should not allow the user to specify both install and list', function () {
options.list = true;
options.install = 'org/package/version';
parser = settingParser(options);
expect(settings.timeout).to.be(1234);
});
expect(parser.parse).withArgs().to.throwError(/Please specify either --install, --remove, or --list./);
});
it('should not allow the user to specify both remove and list', function () {
options.list = true;
options.remove = 'package';
parser = settingParser(options);
expect(parser.parse).withArgs().to.throwError(/Please specify either --install, --remove, or --list./);
});
it('should not allow the user to specify install, remove, and list', function () {
options.list = true;
options.install = 'org/package/version';
options.remove = 'package';
parser = settingParser(options);
expect(parser.parse).withArgs().to.throwError(/Please specify either --install, --remove, or --list./);
});
describe('quiet option', function () {
it('should default to false', function () {
parser = settingParser(options);
var settings = parser.parse(options);
const settings = parse(command, options, kbnPackage);
expect(settings.quiet).to.be(false);
});
it('should set settings.quiet property to true', function () {
options.parent = { quiet: true };
parser = settingParser(options);
var settings = parser.parse(options);
options.quiet = true;
const settings = parse(command, options, kbnPackage);
expect(settings.quiet).to.be(true);
});
@ -137,238 +110,111 @@ describe('kibana cli', function () {
describe('silent option', function () {
it('should default to false', function () {
parser = settingParser(options);
var settings = parser.parse(options);
const settings = parse(command, options, kbnPackage);
expect(settings).to.have.property('silent', false);
expect(settings.silent).to.be(false);
});
it('should set settings.silent property to true', function () {
options.silent = true;
parser = settingParser(options);
var settings = parser.parse(options);
const settings = parse(command, options, kbnPackage);
expect(settings).to.have.property('silent', true);
expect(settings.silent).to.be(true);
});
});
describe('config option', function () {
describe('timeout option', function () {
it('should default to ZLS', function () {
const settings = parse(command, options, kbnPackage);
it('should default to 0 (milliseconds)', function () {
parser = settingParser(options);
var settings = parser.parse(options);
expect(settings).to.have.property('timeout', 0);
expect(settings.config).to.be('');
});
it('should set settings.timeout property to specified value', function () {
options.timeout = 1234;
parser = settingParser(options);
var settings = parser.parse(options);
it('should set settings.config property', function () {
options.config = 'foo bar baz';
const settings = parse(command, options, kbnPackage);
expect(settings).to.have.property('timeout', 1234);
expect(settings.config).to.be('foo bar baz');
});
});
describe('install option', function () {
describe('pluginDir option', function () {
it('should set settings.action property to "install"', function () {
options.install = 'org/package/version';
parser = settingParser(options);
var settings = parser.parse(options);
it('should default to installedPlugins', function () {
const settings = parse(command, options, kbnPackage);
expect(settings).to.have.property('action', 'install');
expect(settings.pluginDir).to.be(fromRoot('installedPlugins'));
});
it('should allow two parts to the install parameter', function () {
options.install = 'kibana/test-plugin';
parser = settingParser(options);
expect(parser.parse).withArgs().to.not.throwError();
var settings = parser.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';
parser = settingParser(options);
expect(parser.parse).withArgs().to.not.throwError();
var settings = parser.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';
parser = settingParser(options);
expect(parser.parse).withArgs().to.throwError(/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';
parser = settingParser(options);
expect(parser.parse).withArgs().to.throwError(/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';
parser = settingParser(options);
var settings = parser.parse();
expect(settings.urls).to.have.property('length', 1);
expect(settings.urls).to.contain('https://download.elastic.co/kibana/test-plugin/test-plugin-latest.tar.gz');
});
it('should populate the urls collection properly version specified', function () {
options.install = 'kibana/test-plugin/v1.1.1';
parser = settingParser(options);
var settings = parser.parse();
expect(settings.urls).to.have.property('length', 1);
expect(settings.urls).to.contain('https://download.elastic.co/kibana/test-plugin/test-plugin-v1.1.1.tar.gz');
});
it('should populate the pluginPath', function () {
options.install = 'kibana/test-plugin';
parser = settingParser(options);
var settings = parser.parse();
var expected = fromRoot('installedPlugins/test-plugin');
expect(settings).to.have.property('pluginPath', expected);
});
it('should populate the workingPath', function () {
options.install = 'kibana/test-plugin';
parser = settingParser(options);
var settings = parser.parse();
var expected = fromRoot('installedPlugins/.plugin.installing');
expect(settings).to.have.property('workingPath', expected);
});
it('should populate the tempArchiveFile', function () {
options.install = 'kibana/test-plugin';
parser = settingParser(options);
var settings = parser.parse();
var expected = fromRoot('installedPlugins/.plugin.installing/archive.part');
expect(settings).to.have.property('tempArchiveFile', 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';
parser = settingParser(options);
expect(parser.parse).withArgs().to.not.throwError();
var settings = parser.parse();
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';
parser = settingParser(options);
expect(parser.parse).withArgs()
.to.throwError(/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;
parser = settingParser(options);
var settings = parser.parse();
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);
});
it('should set settings.config property', function () {
options.pluginDir = 'foo bar baz';
const settings = parse(command, options, kbnPackage);
expect(settings.pluginDir).to.be('foo bar baz');
});
});
describe('remove option', function () {
describe('command value', function () {
it('should set settings.action property to "remove"', function () {
delete options.install;
options.remove = 'package';
parser = settingParser(options);
it('should set settings.plugin property', function () {
const settings = parse(command, options, kbnPackage);
var settings = parser.parse();
expect(settings).to.have.property('action', 'remove');
});
it('should allow one part to the remove parameter', function () {
delete options.install;
options.remove = 'test-plugin';
parser = settingParser(options);
var settings = parser.parse();
expect(settings).to.have.property('package', 'test-plugin');
});
it('should not allow more than one part to the remove parameter', function () {
delete options.install;
options.remove = 'kibana/test-plugin';
parser = settingParser(options);
expect(parser.parse).withArgs()
.to.throwError(/Invalid remove option. Please use the format <plugin>./);
});
it('should populate the pluginPath', function () {
delete options.install;
options.remove = 'test-plugin';
parser = settingParser(options);
var settings = parser.parse();
var expected = fromRoot('installedPlugins/test-plugin');
expect(settings).to.have.property('pluginPath', expected);
expect(settings.plugin).to.be(command);
});
});
describe('list option', function () {
describe('urls collection', function () {
it('should set settings.action property to "list"', function () {
delete options.install;
delete options.remove;
options.list = true;
parser = settingParser(options);
it('should populate the settings.urls property', function () {
const settings = parse(command, options, kbnPackage);
var settings = parser.parse();
const expected = [
command,
`https://download.elastic.co/packs/${command}/${command}-1234.zip`
];
expect(settings).to.have.property('action', 'list');
expect(settings.urls).to.eql(expected);
});
});
describe('workingPath value', function () {
it('should set settings.workingPath property', function () {
options.pluginDir = 'foo/bar/baz';
const settings = parse(command, options, kbnPackage);
const expected = resolve('foo/bar/baz', '.plugin.installing');
expect(settings.workingPath).to.be(expected);
});
});
describe('tempArchiveFile value', function () {
it('should set settings.tempArchiveFile property', function () {
options.pluginDir = 'foo/bar/baz';
const settings = parse(command, options, kbnPackage);
const expected = resolve('foo/bar/baz', '.plugin.installing', 'archive.part');
expect(settings.tempArchiveFile).to.be(expected);
});
});
describe('tempPackageFile value', function () {
it('should set settings.tempPackageFile property', function () {
options.pluginDir = 'foo/bar/baz';
const settings = parse(command, options, kbnPackage);
const expected = resolve('foo/bar/baz', '.plugin.installing', 'package.json');
expect(settings.tempPackageFile).to.be(expected);
});
});

View file

@ -1,13 +1,12 @@
import expect from 'expect.js';
import sinon from 'sinon';
import glob from 'glob-all';
import glob from 'glob';
import rimraf from 'rimraf';
import mkdirp from 'mkdirp';
import pluginLogger from '../plugin_logger';
import extract from '../plugin_extractor';
import pluginDownloader from '../plugin_downloader';
import Logger from '../../lib/logger';
import { _downloadSingle } from '../download';
import { join } from 'path';
import { listFiles, extractFiles } from '../zip';
describe('kibana cli', function () {
@ -16,11 +15,12 @@ describe('kibana cli', function () {
const testWorkingPath = join(__dirname, '.test.data');
const tempArchiveFilePath = join(testWorkingPath, 'archive.part');
let logger;
let downloader;
const settings = {
workingPath: testWorkingPath,
tempArchiveFile: tempArchiveFilePath
tempArchiveFile: tempArchiveFilePath,
plugin: 'test-plugin',
setPlugin: function (plugin) {}
};
function shouldReject() {
@ -28,17 +28,18 @@ describe('kibana cli', function () {
}
beforeEach(function () {
logger = pluginLogger(false);
logger = new Logger(settings);
sinon.stub(logger, 'log');
sinon.stub(logger, 'error');
sinon.stub(settings, 'setPlugin');
rimraf.sync(testWorkingPath);
mkdirp.sync(testWorkingPath);
downloader = pluginDownloader(settings, logger);
});
afterEach(function () {
logger.log.restore();
logger.error.restore();
settings.setPlugin.restore();
rimraf.sync(testWorkingPath);
});
@ -46,86 +47,99 @@ describe('kibana cli', function () {
const filePath = join(__dirname, 'replies', filename);
const sourceUrl = 'file://' + filePath.replace(/\\/g, '/');
return downloader._downloadSingle(sourceUrl);
return _downloadSingle(settings, logger, sourceUrl);
}
function shouldReject() {
throw new Error('expected the promise to reject');
}
describe('listFiles', function () {
describe('extractArchive', function () {
it('successfully extract a valid tarball', function () {
return copyReplyFile('test_plugin_master.tar.gz')
.then((data) => {
return extract(settings, logger, data.archiveType);
})
it('lists the files in the zip', function () {
return copyReplyFile('test_plugin.zip')
.then(() => {
const files = glob.sync('**/*', { cwd: testWorkingPath });
return listFiles(settings.tempArchiveFile);
})
.then((actual) => {
const expected = [
'archive.part',
'README.md',
'index.js',
'package.json',
'public',
'public/app.js'
'elasticsearch\\',
'kibana\\',
'kibana\\test-plugin\\',
'kibana\\test-plugin\\.gitignore',
'kibana\\test-plugin\\extra file only in zip.txt',
'kibana\\test-plugin\\index.js',
'kibana\\test-plugin\\package.json',
'kibana\\test-plugin\\public\\',
'kibana\\test-plugin\\public\\app.js',
'kibana\\test-plugin\\README.md',
'logstash\\'
];
expect(files.sort()).to.eql(expected.sort());
});
});
it('successfully extract a valid zip', function () {
return copyReplyFile('test_plugin_master.zip')
.then((data) => {
return extract(settings, logger, data.archiveType);
})
.then(() => {
const files = glob.sync('**/*', { cwd: testWorkingPath });
const expected = [
'archive.part',
'README.md',
'index.js',
'package.json',
'public',
'public/app.js',
'extra file only in zip.txt'
];
expect(files.sort()).to.eql(expected.sort());
});
});
it('throw an error when extracting a corrupt zip', function () {
return copyReplyFile('corrupt.zip')
.then((data) => {
return extract(settings, logger, data.archiveType);
})
.then(shouldReject, (err) => {
expect(err.message).to.match(/error extracting/i);
});
});
it('throw an error when extracting a corrupt tarball', function () {
return copyReplyFile('corrupt.tar.gz')
.then((data) => {
return extract(settings, logger, data.archiveType);
})
.then(shouldReject, (err) => {
expect(err.message).to.match(/error extracting/i);
});
});
it('throw an error when passed an unknown archive type', function () {
return copyReplyFile('banana.jpg')
.then((data) => {
return extract(settings, logger, data.archiveType);
})
.then(shouldReject, (err) => {
expect(err.message).to.match(/unsupported archive format/i);
expect(actual).to.eql(expected);
});
});
});
describe('extractFiles', function () {
it('extracts files using the files filter', function () {
return copyReplyFile('test_plugin_many.zip')
.then(() => {
const filter = {
files: [
'kibana/funger-plugin/extra file only in zip.txt',
'kibana/funger-plugin/index.js',
'kibana\\funger-plugin\\package.json'
]
};
return extractFiles(settings.tempArchiveFile, settings.workingPath, 0, filter);
})
.then(() => {
const files = glob.sync('**/*', { cwd: testWorkingPath });
const expected = [
'kibana',
'kibana/funger-plugin',
'kibana/funger-plugin/extra file only in zip.txt',
'kibana/funger-plugin/index.js',
'kibana/funger-plugin/package.json',
'archive.part'
];
expect(files.sort()).to.eql(expected.sort());
});
});
it('extracts files using the paths filter', function () {
return copyReplyFile('test_plugin_many.zip')
.then(() => {
const filter = {
paths: [
'kibana/funger-plugin',
'kibana/test-plugin/public'
]
};
return extractFiles(settings.tempArchiveFile, settings.workingPath, 0, filter);
})
.then(() => {
const files = glob.sync('**/*', { cwd: testWorkingPath });
const expected = [
'archive.part',
'kibana',
'kibana/funger-plugin',
'kibana/funger-plugin/README.md',
'kibana/funger-plugin/extra file only in zip.txt',
'kibana/funger-plugin/index.js',
'kibana/funger-plugin/package.json',
'kibana/funger-plugin/public',
'kibana/funger-plugin/public/app.js',
'kibana/test-plugin',
'kibana/test-plugin/public',
'kibana/test-plugin/public/app.js'
];
expect(files.sort()).to.eql(expected.sort());
});
});
});
});
});

View file

@ -1,39 +1,32 @@
import rimraf from 'rimraf';
import fs from 'fs';
export default function createPluginCleaner(settings, logger) {
function cleanPrevious() {
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() {
// delete the working directory.
// At this point we're bailing, so swallow any errors on delete.
export function cleanPrevious(settings, logger) {
return new Promise(function (resolve, reject) {
try {
rimraf.sync(settings.workingPath);
rimraf.sync(settings.pluginPath);
}
catch (e) {} // eslint-disable-line no-empty
}
fs.statSync(settings.workingPath);
return {
cleanPrevious: cleanPrevious,
cleanError: cleanError
};
logger.log('Found previous install attempt. Deleting...');
try {
rimraf.sync(settings.workingPath);
} catch (e) {
reject(e);
}
resolve();
} catch (e) {
if (e.code !== 'ENOENT') reject(e);
resolve();
}
});
};
export function cleanError(settings) {
// delete the working directory.
// At this point we're bailing, so swallow any errors on delete.
try {
rimraf.sync(settings.workingPath);
rimraf.sync(settings.plugins[0].path);
}
catch (e) {} // eslint-disable-line no-empty
};

View file

@ -1,51 +1,41 @@
import _ from 'lodash';
import downloadHttpFile from './downloaders/http';
import downloadLocalFile from './downloaders/file';
import { parse as urlParse } from 'url';
import { parse } from 'url';
export default function createPluginDownloader(settings, logger) {
let archiveType;
let sourceType;
export function _downloadSingle(settings, logger, sourceUrl) {
const urlInfo = parse(sourceUrl);
let downloadPromise;
//Attempts to download each url in turn until one is successful
function download() {
const urls = settings.urls.slice(0);
if (/^file/.test(urlInfo.protocol)) {
downloadPromise = downloadLocalFile(logger, decodeURI(urlInfo.path), settings.tempArchiveFile);
} else {
downloadPromise = downloadHttpFile(logger, sourceUrl, settings.tempArchiveFile, settings.timeout);
}
function tryNext() {
const sourceUrl = urls.shift();
if (!sourceUrl) {
throw new Error('No valid url specified.');
return downloadPromise;
}
//Attempts to download each url in turn until one is successful
export function download(settings, logger) {
const urls = settings.urls.slice(0);
function tryNext() {
const sourceUrl = urls.shift();
if (!sourceUrl) {
throw new Error('No valid url specified.');
}
logger.log(`Attempting to transfer from ${sourceUrl}`);
return _downloadSingle(settings, logger, sourceUrl)
.catch((err) => {
if (err.message === 'ENOTFOUND') {
return tryNext();
}
logger.log(`Attempting to transfer from ${sourceUrl}`);
return downloadSingle(sourceUrl)
.catch((err) => {
if (err.message === 'ENOTFOUND') {
return tryNext();
}
throw (err);
});
}
return tryNext();
throw (err);
});
}
function downloadSingle(sourceUrl) {
const urlInfo = urlParse(sourceUrl);
let downloadPromise;
if (/^file/.test(urlInfo.protocol)) {
downloadPromise = downloadLocalFile(logger, urlInfo.path, settings.tempArchiveFile);
} else {
downloadPromise = downloadHttpFile(logger, sourceUrl, settings.tempArchiveFile, settings.timeout);
}
return downloadPromise;
}
return {
download: download,
_downloadSingle: downloadSingle
};
return tryNext();
};

View file

@ -1,6 +1,5 @@
import getProgressReporter from '../progress_reporter';
import Progress from '../progress';
import { createWriteStream, createReadStream, unlinkSync, statSync } from 'fs';
import fileType from '../file_type';
function openSourceFile({ sourcePath }) {
try {
@ -18,7 +17,7 @@ function openSourceFile({ sourcePath }) {
}
}
async function copyFile({ readStream, writeStream, progressReporter }) {
async function copyFile({ readStream, writeStream, progress }) {
await new Promise((resolve, reject) => {
// if either stream errors, fail quickly
readStream.on('error', reject);
@ -26,7 +25,7 @@ async function copyFile({ readStream, writeStream, progressReporter }) {
// report progress as we transfer
readStream.on('data', (chunk) => {
progressReporter.progress(chunk.length);
progress.progress(chunk.length);
});
// write the download to the file system
@ -46,21 +45,17 @@ export default async function copyLocalFile(logger, sourcePath, targetPath) {
const writeStream = createWriteStream(targetPath);
try {
const progressReporter = getProgressReporter(logger);
progressReporter.init(fileInfo.size);
const progress = new Progress(logger);
progress.init(fileInfo.size);
await copyFile({ readStream, writeStream, progressReporter });
await copyFile({ readStream, writeStream, progress });
progressReporter.complete();
progress.complete();
} catch (err) {
readStream.close();
writeStream.close();
throw err;
}
// all is well, return our archive type
const archiveType = fileType(sourcePath);
return { archiveType };
} catch (err) {
logger.error(err);
throw err;

View file

@ -1,8 +1,7 @@
import Wreck from 'wreck';
import getProgressReporter from '../progress_reporter';
import Progress from '../progress';
import { fromNode as fn } from 'bluebird';
import { createWriteStream, unlinkSync } from 'fs';
import fileType, { ZIP, TAR } from '../file_type';
function sendRequest({ sourceUrl, timeout }) {
const maxRedirects = 11; //Because this one goes to 11.
@ -25,7 +24,7 @@ function sendRequest({ sourceUrl, timeout }) {
});
}
function downloadResponse({ resp, targetPath, progressReporter }) {
function downloadResponse({ resp, targetPath, progress }) {
return new Promise((resolve, reject) => {
const writeStream = createWriteStream(targetPath);
@ -35,7 +34,7 @@ function downloadResponse({ resp, targetPath, progressReporter }) {
// report progress as we download
resp.on('data', (chunk) => {
progressReporter.progress(chunk.length);
progress.progress(chunk.length);
});
// write the download to the file system
@ -46,19 +45,6 @@ function downloadResponse({ resp, targetPath, progressReporter }) {
});
}
function getArchiveTypeFromResponse(resp, sourceUrl) {
const contentType = (resp.headers['content-type'] || '');
switch (contentType.toLowerCase()) {
case 'application/zip': return ZIP;
case 'application/x-gzip': return TAR;
default:
//If we can't infer the archive type from the content-type header,
//fall back to checking the extension in the url
return fileType(sourceUrl);
}
}
/*
Responsible for managing http transfers
*/
@ -68,20 +54,16 @@ export default async function downloadUrl(logger, sourceUrl, targetPath, timeout
try {
let totalSize = parseFloat(resp.headers['content-length']) || 0;
const progressReporter = getProgressReporter(logger);
progressReporter.init(totalSize);
const progress = new Progress(logger);
progress.init(totalSize);
await downloadResponse({ resp, targetPath, progressReporter });
await downloadResponse({ resp, targetPath, progress });
progressReporter.complete();
progress.complete();
} catch (err) {
req.abort();
throw err;
}
// all is well, return our archive type
const archiveType = getArchiveTypeFromResponse(resp, sourceUrl);
return { archiveType };
} catch (err) {
if (err.message !== 'ENOTFOUND') {
logger.error(err);

View file

@ -1,86 +1,40 @@
import _ from 'lodash';
import fromRoot from '../../utils/from_root';
import pluginDownloader from './plugin_downloader';
import pluginCleaner from './plugin_cleaner';
import pluginExtractor from './plugin_extractor';
import KbnServer from '../../server/kbn_server';
import readYamlConfig from '../serve/read_yaml_config';
import { download } from './download';
import Promise from 'bluebird';
import { cleanPrevious, cleanError } from './cleanup';
import { extract, getPackData } from './pack';
import { sync as rimrafSync } from 'rimraf';
import { statSync, renameSync } from 'fs';
import { existingInstall, rebuildCache, checkVersion } from './kibana';
const mkdirp = Promise.promisify(require('mkdirp'));
export default {
install: install
};
function checkForExistingInstall(settings, logger) {
export default async function install(settings, logger) {
try {
statSync(settings.pluginPath);
logger.error(`Plugin ${settings.package} already exists, please remove before installing a new version`);
process.exit(70); // eslint-disable-line no-process-exit
} catch (e) {
if (e.code !== 'ENOENT') throw e;
}
}
async function rebuildKibanaCache(settings, logger) {
logger.log('Optimizing and caching browser bundles...');
const serverConfig = _.merge(
readYamlConfig(settings.config),
{
env: 'production',
logging: {
silent: settings.silent,
quiet: !settings.silent,
verbose: false
},
optimize: {
useBundleCache: false
},
server: {
autoListen: false
},
plugins: {
initialize: false,
scanDirs: [settings.pluginDir, fromRoot('src/plugins')]
}
}
);
const kbnServer = new KbnServer(serverConfig);
await kbnServer.ready();
await kbnServer.close();
}
async function install(settings, logger) {
logger.log(`Installing ${settings.package}`);
const cleaner = pluginCleaner(settings, logger);
try {
checkForExistingInstall(settings, logger);
await cleaner.cleanPrevious();
await cleanPrevious(settings, logger);
await mkdirp(settings.workingPath);
const downloader = pluginDownloader(settings, logger);
const { archiveType } = await downloader.download();
await download(settings, logger);
await pluginExtractor (settings, logger, archiveType);
await getPackData(settings, logger);
await extract (settings, logger);
rimrafSync(settings.tempArchiveFile);
renameSync(settings.workingPath, settings.pluginPath);
existingInstall(settings, logger);
await rebuildKibanaCache(settings, logger);
checkVersion(settings);
renameSync(settings.workingPath, settings.plugins[0].path);
await rebuildCache(settings, logger);
logger.log('Plugin installation complete');
} catch (err) {
logger.error(`Plugin installation was unsuccessful due to error "${err.message}"`);
cleaner.cleanError();
cleanError(settings);
process.exit(70); // eslint-disable-line no-process-exit
}
}

View file

@ -0,0 +1,56 @@
import _ from 'lodash';
import fromRoot from '../../utils/fromRoot';
import KbnServer from '../../server/KbnServer';
import readYamlConfig from '../../cli/serve/read_yaml_config';
import { statSync } from 'fs';
export function existingInstall(settings, logger) {
try {
statSync(settings.plugins[0].path);
logger.error(`Plugin ${settings.plugins[0].name} already exists, please remove before installing a new version`);
process.exit(70); // eslint-disable-line no-process-exit
} catch (e) {
if (e.code !== 'ENOENT') throw e;
}
}
export async function rebuildCache(settings, logger) {
logger.log('Optimizing and caching browser bundles...');
const serverConfig = _.merge(
readYamlConfig(settings.config),
{
env: 'production',
logging: {
silent: settings.silent,
quiet: !settings.silent,
verbose: false
},
optimize: {
useBundleCache: false
},
server: {
autoListen: false
},
plugins: {
initialize: false,
scanDirs: [settings.pluginDir, fromRoot('src/plugins')]
}
}
);
const kbnServer = new KbnServer(serverConfig);
await kbnServer.ready();
await kbnServer.close();
}
export function checkVersion(settings) {
if (!settings.plugins[0].version) {
throw new Error (`Plugin version not found. Check package.json in archive`);
}
if (settings.plugins[0].version !== settings.version) {
throw new Error (`Incorrect version in plugin [${settings.plugins[0].name}]. ` +
`Expected [${settings.version}]; found [${settings.plugins[0].version}]`);
}
}

View file

@ -0,0 +1,119 @@
import _ from 'lodash';
import { listFiles, extractFiles } from './zip';
import { resolve } from 'path';
import { sync as rimrafSync } from 'rimraf';
//*****************************************
//Return a list of package.json files in the archive
//*****************************************
async function listPackages(settings) {
const regExp = new RegExp('(kibana/([^/]+))/package.json', 'i');
const archiveFiles = await listFiles(settings.tempArchiveFile);
let packages = archiveFiles.map((file) => {
file = file.replace(/\\/g, '/');
const matches = file.match(regExp);
if (matches) {
return {
file: matches[0],
folder: matches[2]
};
}
});
packages = _.chain(packages).compact().uniq().value();
return packages;
}
//*****************************************
//Extract the package.json files into the workingPath
//*****************************************
async function extractPackageFiles(settings, packages) {
const filter = {
files: packages.map((pkg) => pkg.file)
};
await extractFiles(settings.tempArchiveFile, settings.workingPath, 0, filter);
}
//*****************************************
//Extract the package.json files into the workingPath
//*****************************************
function deletePackageFiles(settings, packages) {
packages.forEach((pkg) => {
const fullPath = resolve(settings.workingPath, 'kibana');
rimrafSync(fullPath);
});
}
//*****************************************
//Examine each package.json file to determine the plugin name,
//version, and platform.
//*****************************************
async function readPackageData(settings, packages) {
return packages.map((pkg) => {
const fullPath = resolve(settings.workingPath, pkg.file);
const packageInfo = require(fullPath);
pkg.version = _.get(packageInfo, 'version');
pkg.name = _.get(packageInfo, 'name');
pkg.path = resolve(settings.pluginDir, pkg.name);
const regExp = new RegExp(`${pkg.name}-(.+)`, 'i');
const matches = pkg.folder.match(regExp);
pkg.platform = (matches) ? matches[1] : undefined;
return pkg;
});
}
//*****************************************
//Extracts the first plugin in the archive.
//This will need to be changed in later versions of the pack installer
//that allow for the installation of more than one plugin at once.
//*****************************************
async function extractArchive(settings) {
const filter = {
paths: [ settings.plugins[0].folder ]
};
await extractFiles(settings.tempArchiveFile, settings.workingPath, 2, filter);
}
//*****************************************
//Returns the detailed information about each kibana plugin in the
//pack.
//TODO: If there are platform specific folders, determine which one to use.
//*****************************************
export async function getPackData(settings, logger) {
let packages;
try {
logger.log('Retrieving metadata from plugin archive');
packages = await listPackages(settings);
await extractPackageFiles(settings, packages);
await readPackageData(settings, packages);
await deletePackageFiles(settings, packages);
} catch (err) {
logger.error(err);
throw new Error('Error retrieving metadata from plugin archive');
}
if (packages.length === 0) {
throw new Error('No kibana plugins found in archive');
}
settings.plugins = packages;
}
export async function extract(settings, logger) {
try {
logger.log('Extracting plugin archive');
await extractArchive(settings);
logger.log('Extraction complete');
} catch (err) {
logger.error(err);
throw new Error('Error extracting plugin archive');
}
};

View file

@ -1,38 +1,39 @@
/*
Generates file transfer progress messages
*/
export default function createProgressReporter(logger) {
let dotCount = 0;
let runningTotal = 0;
let totalSize = 0;
export default function Progress(logger) {
const self = this;
function init(size) {
totalSize = size;
let totalDesc = totalSize || 'unknown number of';
self.dotCount = 0;
self.runningTotal = 0;
self.totalSize = 0;
self.logger = logger;
}
logger.log(`Transferring ${totalDesc} bytes`, true);
}
Progress.prototype.init = function (size) {
const self = this;
//Should log a dot for every 5% of progress
function progress(size) {
if (!totalSize) return;
self.totalSize = size;
const totalDesc = self.totalSize || 'unknown number of';
runningTotal += size;
let newDotCount = Math.round(runningTotal / totalSize * 100 / 5);
if (newDotCount > 20) newDotCount = 20;
for (let i = 0; i < (newDotCount - dotCount); i++) {
logger.log('.', true);
}
dotCount = newDotCount;
}
function complete() {
logger.log(`Transfer complete`, false);
}
return {
init: init,
progress: progress,
complete: complete
};
self.logger.log(`Transferring ${totalDesc} bytes`, true);
};
Progress.prototype.progress = function (size) {
const self = this;
if (!self.totalSize) return;
self.runningTotal += size;
let newDotCount = Math.round(self.runningTotal / self.totalSize * 100 / 5);
if (newDotCount > 20) newDotCount = 20;
for (let i = 0; i < (newDotCount - self.dotCount); i++) {
self.logger.log('.', true);
}
self.dotCount = newDotCount;
};
Progress.prototype.complete = function () {
const self = this;
self.logger.log(`Transfer complete`, false);
};

View file

@ -1,114 +1,48 @@
import expiry from 'expiry-js';
import { intersection } from 'lodash';
import { resolve } from 'path';
import { arch, platform } from 'os';
export default function createSettingParser(options) {
function parseMilliseconds(val) {
let result;
function generateUrls(settings) {
const { version, plugin } = settings;
return [
plugin,
`https://download.elastic.co/packs/${plugin}/${plugin}-${version}.zip`
];
}
try {
let timeVal = expiry(val);
result = timeVal.asMilliseconds();
} catch (ex) {
result = 0;
}
export function parseMilliseconds(val) {
let result;
return result;
try {
let timeVal = expiry(val);
result = timeVal.asMilliseconds();
} catch (ex) {
result = 0;
}
function generateDownloadUrl(settings) {
const version = (settings.version) || 'latest';
const filename = settings.package + '-' + version + '.tar.gz';
return 'https://download.elastic.co/' + settings.organization + '/' + settings.package + '/' + filename;
}
function areMultipleOptionsChosen(options, choices) {
return intersection(Object.keys(options), choices).length > 1;
}
function parse() {
let parts;
let settings = {
timeout: 0,
silent: false,
quiet: false,
urls: []
};
if (options.timeout) {
settings.timeout = options.timeout;
}
if (options.parent && options.parent.quiet) {
settings.quiet = options.parent.quiet;
}
if (options.silent) {
settings.silent = options.silent;
}
if (options.url) {
settings.urls.push(options.url);
}
if (options.config) {
settings.config = options.config;
}
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(generateDownloadUrl(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 (options.list) {
settings.action = 'list';
}
if (!settings.action || areMultipleOptionsChosen(options, [ 'install', 'remove', 'list' ])) {
throw new Error('Please specify either --install, --remove, or --list.');
}
settings.pluginDir = options.pluginDir;
if (settings.package) {
settings.pluginPath = resolve(settings.pluginDir, settings.package);
settings.workingPath = resolve(settings.pluginDir, '.plugin.installing');
settings.tempArchiveFile = resolve(settings.workingPath, 'archive.part');
}
return settings;
}
return {
parse: parse,
parseMilliseconds: parseMilliseconds
};
return result;
};
export function parse(command, options, kbnPackage) {
const settings = {
timeout: options.timeout ? options.timeout : 0,
quiet: options.quiet ? options.quiet : false,
silent: options.silent ? options.silent : false,
config: options.config ? options.config : '',
plugin: command,
version: kbnPackage.version,
pluginDir: options.pluginDir ? options.pluginDir : ''
};
settings.urls = generateUrls(settings);
settings.workingPath = resolve(settings.pluginDir, '.plugin.installing');
settings.tempArchiveFile = resolve(settings.workingPath, 'archive.part');
settings.tempPackageFile = resolve(settings.workingPath, 'package.json');
settings.setPlugin = function (plugin) {
settings.plugin = plugin;
settings.pluginPath = resolve(settings.pluginDir, settings.plugin.name);
};
return settings;
};

View file

@ -1,32 +1,71 @@
import _ from 'lodash';
import DecompressZip from '@bigfunger/decompress-zip';
async function extractArchive(settings) {
//***********************************************
//Creates a filter function to be consumed by extractFiles
//
//filter: an object with either a files or paths property.
//filter.files: an array of full file paths to extract. Should match
// exactly a value from listFiles
//filter.paths: an array of root paths from the archive. All files and
// folders will be extracted recursively using these paths as roots.
//***********************************************
function extractFilter(filter) {
if (filter.files) {
const filterFiles = filter.files.map((file) => file.replace(/\\/g, '/'));
return function filterByFiles(file) {
if (file.type === 'SymbolicLink') return false;
const path = file.path.replace(/\\/g, '/');
return !!(_.indexOf(filterFiles, path) !== -1);
};
}
if (filter.paths) {
return function filterByRootPath(file) {
if (file.type === 'SymbolicLink') return false;
let include = false;
filter.paths.forEach((path) => {
const regex = new RegExp(`${path}($|/)`, 'i');
if ((file.parent.match(regex)) && file.type !== 'SymbolicLink') {
include = true;
}
});
return include;
};
}
return _.noop;
}
export async function extractFiles(zipPath, targetPath, strip, filter) {
await new Promise((resolve, reject) => {
const unzipper = new DecompressZip(settings.tempArchiveFile);
const unzipper = new DecompressZip(zipPath);
unzipper.on('error', reject);
unzipper.extract({
path: settings.workingPath,
strip: 1,
filter(file) {
return file.type !== 'SymbolicLink';
}
path: targetPath,
strip: strip,
filter: extractFilter(filter)
});
unzipper.on('extract', resolve);
});
}
export default async function extractZip(settings, logger) {
try {
logger.log('Extracting plugin archive');
export async function listFiles(zipPath) {
return await new Promise((resolve, reject) => {
const unzipper = new DecompressZip(zipPath);
await extractArchive(settings);
unzipper.on('error', reject);
logger.log('Extraction complete');
} catch (err) {
logger.error(err);
throw new Error('Error extracting plugin archive');
}
};
unzipper.on('list', (files) => {
resolve(files);
});
unzipper.list();
});
}