mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[bin/kibana-plugin] support KP plugins instead (#74604)
* [cli/kibana-plugin] support KP plugins instead * fix some Logger imports from cli/kibana_keystore Co-authored-by: spalger <spalger@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
981fdda966
commit
a735a9f825
59 changed files with 477 additions and 991 deletions
|
@ -276,7 +276,6 @@
|
|||
"url-loader": "2.2.0",
|
||||
"uuid": "3.3.2",
|
||||
"val-loader": "^1.1.1",
|
||||
"validate-npm-package-name": "2.2.2",
|
||||
"vega": "^5.13.0",
|
||||
"vega-lite": "^4.13.1",
|
||||
"vega-schema-url-parser": "^1.1.0",
|
||||
|
|
72
packages/kbn-pm/dist/index.js
vendored
72
packages/kbn-pm/dist/index.js
vendored
|
@ -97,8 +97,6 @@ __webpack_require__.r(__webpack_exports__);
|
|||
/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(511);
|
||||
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildProductionProjects"]; });
|
||||
|
||||
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; });
|
||||
|
||||
/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(145);
|
||||
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getProjects", function() { return _utils_projects__WEBPACK_IMPORTED_MODULE_2__["getProjects"]; });
|
||||
|
||||
|
@ -59477,9 +59475,6 @@ __webpack_require__.r(__webpack_exports__);
|
|||
/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(512);
|
||||
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; });
|
||||
|
||||
/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(748);
|
||||
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; });
|
||||
|
||||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
|
@ -59500,7 +59495,6 @@ __webpack_require__.r(__webpack_exports__);
|
|||
*/
|
||||
|
||||
|
||||
|
||||
/***/ }),
|
||||
/* 512 */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
@ -90331,71 +90325,5 @@ NestedError.prototype.name = 'NestedError';
|
|||
module.exports = NestedError;
|
||||
|
||||
|
||||
/***/ }),
|
||||
/* 748 */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return prepareExternalProjectDependencies; });
|
||||
/* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(164);
|
||||
/* harmony import */ var _utils_project__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(163);
|
||||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* All external projects are located within `./plugins/{plugin}` relative
|
||||
* to the Kibana root directory or `../kibana-extra/{plugin}` relative
|
||||
* to Kibana itself.
|
||||
*/
|
||||
|
||||
const isKibanaDep = depVersion => // For ../kibana-extra/ directory (legacy only)
|
||||
depVersion.includes('../../kibana/packages/') || // For plugins/ directory
|
||||
depVersion.includes('../../packages/');
|
||||
/**
|
||||
* This prepares the dependencies for an _external_ project.
|
||||
*/
|
||||
|
||||
|
||||
async function prepareExternalProjectDependencies(projectPath) {
|
||||
const project = await _utils_project__WEBPACK_IMPORTED_MODULE_1__["Project"].fromPath(projectPath);
|
||||
|
||||
if (!project.hasDependencies()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deps = project.allDependencies;
|
||||
|
||||
for (const depName of Object.keys(deps)) {
|
||||
const depVersion = deps[depName]; // Kibana currently only supports `link:` dependencies on Kibana's own
|
||||
// packages, as these are packaged into the `node_modules` folder when
|
||||
// Kibana is built, so we don't need to take any action to enable
|
||||
// `require(...)` to resolve for these packages.
|
||||
|
||||
if (Object(_utils_package_json__WEBPACK_IMPORTED_MODULE_0__["isLinkDependency"])(depVersion) && !isKibanaDep(depVersion)) {
|
||||
// For non-Kibana packages we need to set up symlinks during the
|
||||
// installation process, but this is not something we support yet.
|
||||
throw new Error('This plugin is using `link:` dependencies for non-Kibana packages');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/***/ })
|
||||
/******/ ]);
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
export { run } from './cli';
|
||||
export { buildProductionProjects, prepareExternalProjectDependencies } from './production';
|
||||
export { buildProductionProjects } from './production';
|
||||
export { getProjects } from './utils/projects';
|
||||
export { Project } from './utils/project';
|
||||
export { copyWorkspacePackages } from './utils/workspaces';
|
||||
|
|
|
@ -18,4 +18,3 @@
|
|||
*/
|
||||
|
||||
export { buildProductionProjects } from './build_production_projects';
|
||||
export { prepareExternalProjectDependencies } from './prepare_project_dependencies';
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { join, resolve } from 'path';
|
||||
|
||||
import { prepareExternalProjectDependencies } from './prepare_project_dependencies';
|
||||
|
||||
const packagesFixtures = resolve(__dirname, '__fixtures__/external_packages');
|
||||
|
||||
test('does nothing when Kibana `link:` dependencies', async () => {
|
||||
const projectPath = join(packagesFixtures, 'with_kibana_link_deps');
|
||||
|
||||
// We're checking for undefined, but we don't really care about what's
|
||||
// returned, we only care about it resolving.
|
||||
await expect(prepareExternalProjectDependencies(projectPath)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
test('throws if non-Kibana `link` dependencies', async () => {
|
||||
const projectPath = join(packagesFixtures, 'with_other_link_deps');
|
||||
|
||||
await expect(prepareExternalProjectDependencies(projectPath)).rejects.toThrow(
|
||||
'This plugin is using `link:` dependencies for non-Kibana packages'
|
||||
);
|
||||
});
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { isLinkDependency } from '../utils/package_json';
|
||||
import { Project } from '../utils/project';
|
||||
|
||||
/**
|
||||
* All external projects are located within `./plugins/{plugin}` relative
|
||||
* to the Kibana root directory or `../kibana-extra/{plugin}` relative
|
||||
* to Kibana itself.
|
||||
*/
|
||||
const isKibanaDep = (depVersion: string) =>
|
||||
// For ../kibana-extra/ directory (legacy only)
|
||||
depVersion.includes('../../kibana/packages/') ||
|
||||
// For plugins/ directory
|
||||
depVersion.includes('../../packages/');
|
||||
|
||||
/**
|
||||
* This prepares the dependencies for an _external_ project.
|
||||
*/
|
||||
export async function prepareExternalProjectDependencies(projectPath: string) {
|
||||
const project = await Project.fromPath(projectPath);
|
||||
|
||||
if (!project.hasDependencies()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const deps = project.allDependencies;
|
||||
|
||||
for (const depName of Object.keys(deps)) {
|
||||
const depVersion = deps[depName];
|
||||
|
||||
// Kibana currently only supports `link:` dependencies on Kibana's own
|
||||
// packages, as these are packaged into the `node_modules` folder when
|
||||
// Kibana is built, so we don't need to take any action to enable
|
||||
// `require(...)` to resolve for these packages.
|
||||
if (isLinkDependency(depVersion) && !isKibanaDep(depVersion)) {
|
||||
// For non-Kibana packages we need to set up symlinks during the
|
||||
// installation process, but this is not something we support yet.
|
||||
throw new Error('This plugin is using `link:` dependencies for non-Kibana packages');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import Logger from '../cli_plugin/lib/logger';
|
||||
import { Logger } from '../cli_plugin/lib/logger';
|
||||
import { confirm, question } from '../legacy/server/utils';
|
||||
import { createPromiseFromStreams, createConcatStream } from '../legacy/utils';
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ import { PassThrough } from 'stream';
|
|||
|
||||
import { Keystore } from '../legacy/server/keystore';
|
||||
import { add } from './add';
|
||||
import Logger from '../cli_plugin/lib/logger';
|
||||
import { Logger } from '../cli_plugin/lib/logger';
|
||||
import * as prompt from '../legacy/server/utils/prompt';
|
||||
|
||||
describe('Kibana keystore', () => {
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import Logger from '../cli_plugin/lib/logger';
|
||||
import { Logger } from '../cli_plugin/lib/logger';
|
||||
import { confirm } from '../legacy/server/utils';
|
||||
|
||||
export async function create(keystore, command, options) {
|
||||
|
|
|
@ -40,7 +40,7 @@ import sinon from 'sinon';
|
|||
|
||||
import { Keystore } from '../legacy/server/keystore';
|
||||
import { create } from './create';
|
||||
import Logger from '../cli_plugin/lib/logger';
|
||||
import { Logger } from '../cli_plugin/lib/logger';
|
||||
import * as prompt from '../legacy/server/utils/prompt';
|
||||
|
||||
describe('Kibana keystore', () => {
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
import { existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import Logger from '../cli_plugin/lib/logger';
|
||||
import { Logger } from '../cli_plugin/lib/logger';
|
||||
import { getConfigDirectory, getDataPath } from '../core/server/path';
|
||||
|
||||
export function getKeystore() {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { getKeystore } from './get_keystore';
|
||||
import Logger from '../cli_plugin/lib/logger';
|
||||
import { Logger } from '../cli_plugin/lib/logger';
|
||||
import fs from 'fs';
|
||||
import sinon from 'sinon';
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import Logger from '../cli_plugin/lib/logger';
|
||||
import { Logger } from '../cli_plugin/lib/logger';
|
||||
|
||||
export function list(keystore, command, options = {}) {
|
||||
const logger = new Logger(options);
|
||||
|
|
|
@ -38,7 +38,7 @@ jest.mock('fs', () => ({
|
|||
import sinon from 'sinon';
|
||||
import { Keystore } from '../legacy/server/keystore';
|
||||
import { list } from './list';
|
||||
import Logger from '../cli_plugin/lib/logger';
|
||||
import { Logger } from '../cli_plugin/lib/logger';
|
||||
|
||||
describe('Kibana keystore', () => {
|
||||
describe('list', () => {
|
||||
|
|
|
@ -17,12 +17,11 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { pkg } from '../core/server/utils';
|
||||
import Command from '../cli/command';
|
||||
import listCommand from './list';
|
||||
import installCommand from './install';
|
||||
import removeCommand from './remove';
|
||||
import { listCommand } from './list';
|
||||
import { installCommand } from './install';
|
||||
import { removeCommand } from './remove';
|
||||
|
||||
const argv = process.env.kbnWorkerArgv
|
||||
? JSON.parse(process.env.kbnWorkerArgv)
|
||||
|
@ -44,8 +43,12 @@ program
|
|||
.command('help <command>')
|
||||
.description('get the help for a specific command')
|
||||
.action(function (cmdName) {
|
||||
const cmd = _.find(program.commands, { _name: cmdName });
|
||||
if (!cmd) return program.error(`unknown command ${cmdName}`);
|
||||
const cmd = program.commands.find((c) => c._name === cmdName);
|
||||
|
||||
if (!cmd) {
|
||||
return program.error(`unknown command ${cmdName}`);
|
||||
}
|
||||
|
||||
cmd.help();
|
||||
});
|
||||
|
||||
|
|
Binary file not shown.
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"name": "test-plugin"
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -45,6 +45,5 @@ export function cleanArtifacts(settings) {
|
|||
// At this point we're bailing, so swallow any errors on delete.
|
||||
try {
|
||||
del.sync(settings.workingPath);
|
||||
del.sync(settings.plugins[0].path);
|
||||
} catch (e) {} // eslint-disable-line no-empty
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import fs from 'fs';
|
|||
import del from 'del';
|
||||
|
||||
import { cleanPrevious, cleanArtifacts } from './cleanup';
|
||||
import Logger from '../lib/logger';
|
||||
import { Logger } from '../lib/logger';
|
||||
|
||||
describe('kibana cli', function () {
|
||||
describe('plugin installer', function () {
|
||||
|
|
|
@ -17,11 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import downloadHttpFile from './downloaders/http';
|
||||
import downloadLocalFile from './downloaders/file';
|
||||
import { UnsupportedProtocolError } from '../lib/errors';
|
||||
import { parse } from 'url';
|
||||
|
||||
import { UnsupportedProtocolError } from '../lib/errors';
|
||||
import { downloadHttpFile } from './downloaders/http';
|
||||
import { downloadLocalFile } from './downloaders/file';
|
||||
|
||||
function _isWindows() {
|
||||
return /^win/.test(process.platform);
|
||||
}
|
||||
|
|
|
@ -17,16 +17,18 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import Fs from 'fs';
|
||||
import { join } from 'path';
|
||||
import http from 'http';
|
||||
|
||||
import sinon from 'sinon';
|
||||
import nock from 'nock';
|
||||
import glob from 'glob-all';
|
||||
import del from 'del';
|
||||
import Fs from 'fs';
|
||||
import Logger from '../lib/logger';
|
||||
|
||||
import { Logger } from '../lib/logger';
|
||||
import { UnsupportedProtocolError } from '../lib/errors';
|
||||
import { download, _downloadSingle, _getFilePath, _checkFilePathDeprecation } from './download';
|
||||
import { join } from 'path';
|
||||
import http from 'http';
|
||||
|
||||
describe('kibana cli', function () {
|
||||
describe('plugin downloader', function () {
|
||||
|
|
|
@ -17,9 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import Progress from '../progress';
|
||||
import { createWriteStream, createReadStream, statSync } from 'fs';
|
||||
|
||||
import { Progress } from '../progress';
|
||||
|
||||
function openSourceFile({ sourcePath }) {
|
||||
try {
|
||||
const fileInfo = statSync(sourcePath);
|
||||
|
@ -58,7 +59,7 @@ async function copyFile({ readStream, writeStream, progress }) {
|
|||
/*
|
||||
// Responsible for managing local file transfers
|
||||
*/
|
||||
export default async function copyLocalFile(logger, sourcePath, targetPath) {
|
||||
export async function downloadLocalFile(logger, sourcePath, targetPath) {
|
||||
try {
|
||||
const { readStream, fileInfo } = openSourceFile({ sourcePath });
|
||||
const writeStream = createWriteStream(targetPath);
|
||||
|
|
|
@ -17,13 +17,15 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import Wreck from '@hapi/wreck';
|
||||
import Progress from '../progress';
|
||||
import { createWriteStream } from 'fs';
|
||||
|
||||
import Wreck from '@hapi/wreck';
|
||||
import HttpProxyAgent from 'http-proxy-agent';
|
||||
import HttpsProxyAgent from 'https-proxy-agent';
|
||||
import { getProxyForUrl } from 'proxy-from-env';
|
||||
|
||||
import { Progress } from '../progress';
|
||||
|
||||
function getProxyAgent(sourceUrl, logger) {
|
||||
const proxy = getProxyForUrl(sourceUrl);
|
||||
|
||||
|
@ -91,7 +93,7 @@ function downloadResponse({ resp, targetPath, progress }) {
|
|||
/*
|
||||
Responsible for managing http transfers
|
||||
*/
|
||||
export default async function downloadUrl(logger, sourceUrl, targetPath, timeout) {
|
||||
export async function downloadHttpFile(logger, sourceUrl, targetPath, timeout) {
|
||||
try {
|
||||
const { req, resp } = await sendRequest({ sourceUrl, timeout }, logger);
|
||||
|
||||
|
|
|
@ -17,13 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { fromRoot, pkg } from '../../core/server/utils';
|
||||
import install from './install';
|
||||
import Logger from '../lib/logger';
|
||||
import { pkg } from '../../core/server/utils';
|
||||
import { install } from './install';
|
||||
import { Logger } from '../lib/logger';
|
||||
import { getConfigPath } from '../../core/server/path';
|
||||
import { parse, parseMilliseconds } from './settings';
|
||||
import logWarnings from '../lib/log_warnings';
|
||||
import { warnIfUsingPluginDirOption } from '../lib/warn_if_plugin_dir_option';
|
||||
import { logWarnings } from '../lib/log_warnings';
|
||||
|
||||
function processCommand(command, options) {
|
||||
let settings;
|
||||
|
@ -37,12 +36,11 @@ function processCommand(command, options) {
|
|||
|
||||
const logger = new Logger(settings);
|
||||
|
||||
warnIfUsingPluginDirOption(settings, fromRoot('plugins'), logger);
|
||||
logWarnings(settings, logger);
|
||||
install(settings, logger);
|
||||
}
|
||||
|
||||
export default function pluginInstall(program) {
|
||||
export function installCommand(program) {
|
||||
program
|
||||
.command('install <plugin/url>')
|
||||
.option('-q, --quiet', 'disable all process messaging except errors')
|
||||
|
@ -53,15 +51,9 @@ export default function pluginInstall(program) {
|
|||
'length of time before failing; 0 for never fail',
|
||||
parseMilliseconds
|
||||
)
|
||||
.option(
|
||||
'-d, --plugin-dir <path>',
|
||||
'path to the directory where plugins are stored (DEPRECATED, known to not work for all plugins)',
|
||||
fromRoot('plugins')
|
||||
)
|
||||
.description(
|
||||
'install a plugin',
|
||||
`Common examples:
|
||||
install x-pack
|
||||
install file:///Path/to/my/x-pack.zip
|
||||
install https://path.to/my/x-pack.zip`
|
||||
)
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import index from './index';
|
||||
|
||||
import { installCommand } from './index';
|
||||
|
||||
describe('kibana cli', function () {
|
||||
describe('plugin installer', function () {
|
||||
|
@ -41,7 +42,7 @@ describe('kibana cli', function () {
|
|||
it('should define the command', function () {
|
||||
sinon.spy(program, 'command');
|
||||
|
||||
index(program);
|
||||
installCommand(program);
|
||||
expect(program.command.calledWith('install <plugin/url>')).toBe(true);
|
||||
|
||||
program.command.restore();
|
||||
|
@ -50,7 +51,7 @@ describe('kibana cli', function () {
|
|||
it('should define the description', function () {
|
||||
sinon.spy(program, 'description');
|
||||
|
||||
index(program);
|
||||
installCommand(program);
|
||||
expect(program.description.calledWith('install a plugin')).toBe(true);
|
||||
|
||||
program.description.restore();
|
||||
|
@ -59,9 +60,9 @@ describe('kibana cli', function () {
|
|||
it('should define the command line options', function () {
|
||||
const spy = sinon.spy(program, 'option');
|
||||
|
||||
const options = [/-q/, /-s/, /-c/, /-t/, /-d/];
|
||||
const options = [/-q/, /-s/, /-c/, /-t/];
|
||||
|
||||
index(program);
|
||||
installCommand(program);
|
||||
|
||||
for (let i = 0; i < spy.callCount; i++) {
|
||||
const call = spy.getCall(i);
|
||||
|
@ -80,7 +81,7 @@ describe('kibana cli', function () {
|
|||
it('should call the action function', function () {
|
||||
sinon.spy(program, 'action');
|
||||
|
||||
index(program);
|
||||
installCommand(program);
|
||||
expect(program.action.calledOnce).toBe(true);
|
||||
|
||||
program.action.restore();
|
||||
|
|
|
@ -19,20 +19,20 @@
|
|||
|
||||
import Fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import path from 'path';
|
||||
|
||||
import del from 'del';
|
||||
|
||||
import { download } from './download';
|
||||
import path from 'path';
|
||||
import { cleanPrevious, cleanArtifacts } from './cleanup';
|
||||
import { extract, getPackData } from './pack';
|
||||
import { renamePlugin } from './rename';
|
||||
import del from 'del';
|
||||
import { errorIfXPackInstall } from '../lib/error_if_x_pack';
|
||||
import { existingInstall, assertVersion } from './kibana';
|
||||
import { prepareExternalProjectDependencies } from '@kbn/pm';
|
||||
|
||||
const mkdir = promisify(Fs.mkdir);
|
||||
|
||||
export default async function install(settings, logger) {
|
||||
export async function install(settings, logger) {
|
||||
try {
|
||||
errorIfXPackInstall(settings, logger);
|
||||
|
||||
|
@ -52,12 +52,8 @@ export default async function install(settings, logger) {
|
|||
|
||||
assertVersion(settings);
|
||||
|
||||
await prepareExternalProjectDependencies(settings.workingPath);
|
||||
|
||||
await renamePlugin(
|
||||
settings.workingPath,
|
||||
path.join(settings.pluginDir, settings.plugins[0].name)
|
||||
);
|
||||
const targetDir = path.join(settings.pluginDir, settings.plugins[0].id);
|
||||
await renamePlugin(settings.workingPath, targetDir);
|
||||
|
||||
logger.log('Plugin installation complete');
|
||||
} catch (err) {
|
||||
|
|
|
@ -18,15 +18,16 @@
|
|||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { versionSatisfies, cleanVersion } from '../../legacy/utils/version';
|
||||
import { statSync } from 'fs';
|
||||
|
||||
import { versionSatisfies, cleanVersion } from '../../legacy/utils/version';
|
||||
|
||||
export function existingInstall(settings, logger) {
|
||||
try {
|
||||
statSync(path.join(settings.pluginDir, settings.plugins[0].name));
|
||||
statSync(path.join(settings.pluginDir, settings.plugins[0].id));
|
||||
|
||||
logger.error(
|
||||
`Plugin ${settings.plugins[0].name} already exists, please remove before installing a new version`
|
||||
`Plugin ${settings.plugins[0].id} already exists, please remove before installing a new version`
|
||||
);
|
||||
process.exit(70);
|
||||
} catch (e) {
|
||||
|
@ -37,7 +38,7 @@ export function existingInstall(settings, logger) {
|
|||
export function assertVersion(settings) {
|
||||
if (!settings.plugins[0].kibanaVersion) {
|
||||
throw new Error(
|
||||
`Plugin package.json is missing both a version property (required) and a kibana.version property (optional).`
|
||||
`Plugin kibana.json is missing both a version property (required) and a kibanaVersion property (optional).`
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -45,7 +46,7 @@ export function assertVersion(settings) {
|
|||
const expected = cleanVersion(settings.version);
|
||||
if (!versionSatisfies(actual, expected)) {
|
||||
throw new Error(
|
||||
`Plugin ${settings.plugins[0].name} [${actual}] is incompatible with Kibana [${expected}]`
|
||||
`Plugin ${settings.plugins[0].id} [${actual}] is incompatible with Kibana [${expected}]`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,12 +17,14 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import Logger from '../lib/logger';
|
||||
import { join } from 'path';
|
||||
import del from 'del';
|
||||
import fs from 'fs';
|
||||
|
||||
import sinon from 'sinon';
|
||||
import del from 'del';
|
||||
|
||||
import { existingInstall, assertVersion } from './kibana';
|
||||
import { Logger } from '../lib/logger';
|
||||
|
||||
jest.spyOn(fs, 'statSync');
|
||||
|
||||
|
@ -42,7 +44,7 @@ describe('kibana cli', function () {
|
|||
tempArchiveFile: tempArchiveFilePath,
|
||||
plugin: 'test-plugin',
|
||||
version: '1.0.0',
|
||||
plugins: [{ name: 'foo' }],
|
||||
plugins: [{ id: 'foo' }],
|
||||
pluginDir,
|
||||
};
|
||||
|
||||
|
@ -69,7 +71,10 @@ describe('kibana cli', function () {
|
|||
plugin: 'test-plugin',
|
||||
version: '5.0.0-SNAPSHOT',
|
||||
plugins: [
|
||||
{ name: 'foo', path: join(testWorkingPath, 'foo'), kibanaVersion: '5.0.0-SNAPSHOT' },
|
||||
{
|
||||
id: 'foo',
|
||||
kibanaVersion: '5.0.0-SNAPSHOT',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -77,15 +82,17 @@ describe('kibana cli', function () {
|
|||
});
|
||||
|
||||
it('should throw an error if plugin is missing a kibana version.', function () {
|
||||
expect(() => assertVersion(settings)).toThrow(
|
||||
/plugin package\.json is missing both a version property/i
|
||||
expect(() => assertVersion(settings)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Plugin kibana.json is missing both a version property (required) and a kibanaVersion property (optional)."`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if plugin kibanaVersion does not match kibana version', function () {
|
||||
settings.plugins[0].kibanaVersion = '1.2.3.4';
|
||||
|
||||
expect(() => assertVersion(settings)).toThrow(/incompatible with Kibana/i);
|
||||
expect(() => assertVersion(settings)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Plugin foo [1.2.3] is incompatible with Kibana [1.0.0]"`
|
||||
);
|
||||
});
|
||||
|
||||
it('should not throw an error if plugin kibanaVersion matches kibana version', function () {
|
||||
|
@ -103,7 +110,9 @@ describe('kibana cli', function () {
|
|||
it('should ignore version info after the dash in checks on invalid version', function () {
|
||||
settings.plugins[0].kibanaVersion = '2.0.0-foo-bar-version-1.2.3';
|
||||
|
||||
expect(() => assertVersion(settings)).toThrow(/incompatible with Kibana/i);
|
||||
expect(() => assertVersion(settings)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Plugin foo [2.0.0] is incompatible with Kibana [1.0.0]"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -18,7 +18,11 @@
|
|||
*/
|
||||
|
||||
import { analyzeArchive, extractArchive } from './zip';
|
||||
import validate from 'validate-npm-package-name';
|
||||
|
||||
const CAMEL_CASE_REG_EXP = /^[a-z]{1}([a-zA-Z0-9]{1,})$/;
|
||||
export function isCamelCase(candidate) {
|
||||
return CAMEL_CASE_REG_EXP.test(candidate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the plugin name. Will throw an exception if it does not meet
|
||||
|
@ -27,9 +31,10 @@ import validate from 'validate-npm-package-name';
|
|||
* @param {object} plugin - a package object from listPackages()
|
||||
*/
|
||||
function assertValidPackageName(plugin) {
|
||||
const validation = validate(plugin.name);
|
||||
if (!validation.validForNewPackages) {
|
||||
throw new Error(`Invalid plugin name [${plugin.name}] in package.json`);
|
||||
if (!isCamelCase(plugin.id)) {
|
||||
throw new Error(
|
||||
`Invalid plugin name [${plugin.id}] in kibana.json, expected it to be valid camelCase`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,17 +65,13 @@ export async function getPackData(settings, logger) {
|
|||
|
||||
/**
|
||||
* Extracts files from a zip archive to a file path using a filter function
|
||||
*
|
||||
* @param {string} archive - file path to a zip archive
|
||||
* @param {string} targetDir - directory path to where the files should
|
||||
* extracted
|
||||
*/
|
||||
export async function extract(settings, logger) {
|
||||
try {
|
||||
const plugin = settings.plugins[0];
|
||||
|
||||
logger.log('Extracting plugin archive');
|
||||
await extractArchive(settings.tempArchiveFile, settings.workingPath, plugin.archivePath);
|
||||
await extractArchive(settings.tempArchiveFile, settings.workingPath, plugin.stripPrefix);
|
||||
logger.log('Extraction complete');
|
||||
} catch (err) {
|
||||
logger.error(err.stack);
|
||||
|
|
|
@ -18,14 +18,15 @@
|
|||
*/
|
||||
|
||||
import Fs from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import sinon from 'sinon';
|
||||
import glob from 'glob-all';
|
||||
import del from 'del';
|
||||
import Logger from '../lib/logger';
|
||||
|
||||
import { Logger } from '../lib/logger';
|
||||
import { extract, getPackData } from './pack';
|
||||
import { _downloadSingle } from './download';
|
||||
import { join } from 'path';
|
||||
|
||||
describe('kibana cli', function () {
|
||||
describe('pack', function () {
|
||||
|
@ -73,133 +74,104 @@ describe('kibana cli', function () {
|
|||
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()).toEqual(expected.sort());
|
||||
});
|
||||
// Also only extracts the content from the kibana folder.
|
||||
// Ignores the others.
|
||||
it('successfully extract a valid zip', async () => {
|
||||
await copyReplyFile('test_plugin.zip');
|
||||
await getPackData(settings, logger);
|
||||
await extract(settings, logger);
|
||||
|
||||
expect(glob.sync('**/*', { cwd: testWorkingPath })).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"archive.part",
|
||||
"bin",
|
||||
"bin/executable",
|
||||
"bin/not-executable",
|
||||
"kibana.json",
|
||||
"node_modules",
|
||||
"node_modules/some-package",
|
||||
"node_modules/some-package/index.js",
|
||||
"node_modules/some-package/package.json",
|
||||
"public",
|
||||
"public/index.js",
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPackData', function () {
|
||||
it('populate settings.plugins', function () {
|
||||
return copyReplyFile('test_plugin.zip')
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(() => {
|
||||
expect(settings.plugins[0].name).toBe('test-plugin');
|
||||
expect(settings.plugins[0].archivePath).toBe('kibana/test-plugin');
|
||||
expect(settings.plugins[0].version).toBe('1.0.0');
|
||||
expect(settings.plugins[0].kibanaVersion).toBe('1.0.0');
|
||||
});
|
||||
describe('getPackData', () => {
|
||||
it('populate settings.plugins', async () => {
|
||||
await copyReplyFile('test_plugin.zip');
|
||||
await getPackData(settings, logger);
|
||||
expect(settings.plugins).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "testPlugin",
|
||||
"kibanaVersion": "1.0.0",
|
||||
"stripPrefix": "kibana/test-plugin",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('populate settings.plugin.kibanaVersion', function () {
|
||||
//kibana.version is defined in this package.json and is different than plugin version
|
||||
return copyReplyFile('test_plugin_different_version.zip')
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(() => {
|
||||
expect(settings.plugins[0].kibanaVersion).toBe('5.0.1');
|
||||
});
|
||||
it('populate settings.plugin.kibanaVersion', async () => {
|
||||
await copyReplyFile('test_plugin_different_version.zip');
|
||||
await getPackData(settings, logger);
|
||||
expect(settings.plugins).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "testPlugin",
|
||||
"kibanaVersion": "5.0.1",
|
||||
"stripPrefix": "kibana/test-plugin",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('populate settings.plugin.kibanaVersion (default to plugin version)', function () {
|
||||
//kibana.version is not defined in this package.json, defaults to plugin version
|
||||
return copyReplyFile('test_plugin.zip')
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(() => {
|
||||
expect(settings.plugins[0].kibanaVersion).toBe('1.0.0');
|
||||
});
|
||||
it('populate settings.plugins with multiple plugins', async () => {
|
||||
await copyReplyFile('test_plugin_many.zip');
|
||||
await getPackData(settings, logger);
|
||||
expect(settings.plugins).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "fungerPlugin",
|
||||
"kibanaVersion": "1.0.0",
|
||||
"stripPrefix": "kibana/funger-plugin",
|
||||
},
|
||||
Object {
|
||||
"id": "pdf",
|
||||
"kibanaVersion": "1.0.0",
|
||||
"stripPrefix": "kibana/pdf",
|
||||
},
|
||||
Object {
|
||||
"id": "testPlugin",
|
||||
"kibanaVersion": "1.0.0",
|
||||
"stripPrefix": "kibana/test-plugin",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
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).toBe('funger-plugin');
|
||||
expect(settings.plugins[0].archivePath).toBe('kibana/funger-plugin');
|
||||
expect(settings.plugins[0].version).toBe('1.0.0');
|
||||
|
||||
expect(settings.plugins[1].name).toBe('pdf');
|
||||
expect(settings.plugins[1].archivePath).toBe('kibana/pdf-linux');
|
||||
expect(settings.plugins[1].version).toBe('1.0.0');
|
||||
|
||||
expect(settings.plugins[2].name).toBe('pdf');
|
||||
expect(settings.plugins[2].archivePath).toBe('kibana/pdf-win32');
|
||||
expect(settings.plugins[2].version).toBe('1.0.0');
|
||||
|
||||
expect(settings.plugins[3].name).toBe('pdf');
|
||||
expect(settings.plugins[3].archivePath).toBe('kibana/pdf-win64');
|
||||
expect(settings.plugins[3].version).toBe('1.0.0');
|
||||
|
||||
expect(settings.plugins[4].name).toBe('pdf');
|
||||
expect(settings.plugins[4].archivePath).toBe('kibana/pdf');
|
||||
expect(settings.plugins[4].version).toBe('1.0.0');
|
||||
|
||||
expect(settings.plugins[5].name).toBe('test-plugin');
|
||||
expect(settings.plugins[5].archivePath).toBe('kibana/test-plugin');
|
||||
expect(settings.plugins[5].version).toBe('1.0.0');
|
||||
});
|
||||
it('throw an error if there is no kibana plugin', async () => {
|
||||
await copyReplyFile('test_plugin_no_kibana.zip');
|
||||
await expect(getPackData(settings, logger)).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"No kibana plugins found in archive"`
|
||||
);
|
||||
});
|
||||
|
||||
it('throw an error if there is no kibana plugin', function () {
|
||||
return copyReplyFile('test_plugin_no_kibana.zip')
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(shouldReject, (err) => {
|
||||
expect(err.message).toMatch(/No kibana plugins found in archive/i);
|
||||
});
|
||||
it('throw an error with a corrupt zip', async () => {
|
||||
await copyReplyFile('corrupt.zip');
|
||||
await expect(getPackData(settings, logger)).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Error retrieving metadata from plugin archive"`
|
||||
);
|
||||
});
|
||||
|
||||
it('throw an error with a corrupt zip', function () {
|
||||
return copyReplyFile('corrupt.zip')
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(shouldReject, (err) => {
|
||||
expect(err.message).toMatch(/error retrieving/i);
|
||||
});
|
||||
});
|
||||
|
||||
it('throw an error if there an invalid plugin name', function () {
|
||||
return copyReplyFile('invalid_name.zip')
|
||||
.then(() => {
|
||||
return getPackData(settings, logger);
|
||||
})
|
||||
.then(shouldReject, (err) => {
|
||||
expect(err.message).toMatch(/invalid plugin name/i);
|
||||
});
|
||||
it('throw an error if there an invalid plugin name', async () => {
|
||||
await copyReplyFile('invalid_name.zip');
|
||||
await expect(getPackData(settings, logger)).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid plugin name [invalid name] in kibana.json, expected it to be valid camelCase"`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
/**
|
||||
* Generates file transfer progress messages
|
||||
*/
|
||||
export default class Progress {
|
||||
export class Progress {
|
||||
constructor(logger) {
|
||||
const self = this;
|
||||
|
||||
|
|
|
@ -18,8 +18,9 @@
|
|||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import Progress from './progress';
|
||||
import Logger from '../lib/logger';
|
||||
|
||||
import { Progress } from './progress';
|
||||
import { Logger } from '../lib/logger';
|
||||
|
||||
describe('kibana cli', function () {
|
||||
describe('plugin installer', function () {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
import { rename } from 'fs';
|
||||
|
||||
import { delay } from 'lodash';
|
||||
|
||||
export function renamePlugin(workingPath, finalPath) {
|
||||
|
@ -31,8 +32,12 @@ export function renamePlugin(workingPath, finalPath) {
|
|||
// Retry for up to retryTime seconds
|
||||
const windowsEPERM = process.platform === 'win32' && err.code === 'EPERM';
|
||||
const retryAvailable = Date.now() - start < retryTime;
|
||||
if (windowsEPERM && retryAvailable)
|
||||
return delay(rename, retryDelay, workingPath, finalPath, retry);
|
||||
|
||||
if (windowsEPERM && retryAvailable) {
|
||||
delay(rename, retryDelay, workingPath, finalPath, retry);
|
||||
return;
|
||||
}
|
||||
|
||||
reject(err);
|
||||
}
|
||||
resolve();
|
||||
|
|
|
@ -17,9 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import fs from 'fs';
|
||||
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { renamePlugin } from './rename';
|
||||
|
||||
describe('plugin folder rename', function () {
|
||||
|
|
|
@ -17,9 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import expiry from 'expiry-js';
|
||||
import { resolve } from 'path';
|
||||
|
||||
import expiry from 'expiry-js';
|
||||
|
||||
import { fromRoot } from '../../core/server/utils';
|
||||
|
||||
function generateUrls({ version, plugin }) {
|
||||
return [
|
||||
plugin,
|
||||
|
@ -46,20 +49,14 @@ export function parse(command, options, kbnPackage) {
|
|||
quiet: options.quiet || false,
|
||||
silent: options.silent || false,
|
||||
config: options.config || '',
|
||||
optimize: options.optimize,
|
||||
plugin: command,
|
||||
version: kbnPackage.version,
|
||||
pluginDir: options.pluginDir || '',
|
||||
pluginDir: fromRoot('plugins'),
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -17,199 +17,82 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { createAbsolutePathSerializer } from '@kbn/dev-utils';
|
||||
|
||||
import { fromRoot } from '../../core/server/utils';
|
||||
import { resolve } from 'path';
|
||||
import { parseMilliseconds, parse } from './settings';
|
||||
|
||||
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 () {
|
||||
const value = '';
|
||||
const result = parseMilliseconds(value);
|
||||
const SECOND = 1000;
|
||||
const MINUTE = SECOND * 60;
|
||||
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
expect.addSnapshotSerializer(createAbsolutePathSerializer());
|
||||
|
||||
it('should return 0 for a number with an invalid unit of measure', function () {
|
||||
const result = parseMilliseconds('1gigablasts');
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
|
||||
it('should assume a number with no unit of measure is specified as milliseconds', function () {
|
||||
const result = parseMilliseconds(1);
|
||||
expect(result).toBe(1);
|
||||
|
||||
const result2 = parseMilliseconds('1');
|
||||
expect(result2).toBe(1);
|
||||
});
|
||||
|
||||
it('should interpret a number with "s" as the unit of measure as seconds', function () {
|
||||
const result = parseMilliseconds('5s');
|
||||
expect(result).toBe(5 * 1000);
|
||||
});
|
||||
|
||||
it('should interpret a number with "second" as the unit of measure as seconds', function () {
|
||||
const result = parseMilliseconds('5second');
|
||||
expect(result).toBe(5 * 1000);
|
||||
});
|
||||
|
||||
it('should interpret a number with "seconds" as the unit of measure as seconds', function () {
|
||||
const result = parseMilliseconds('5seconds');
|
||||
expect(result).toBe(5 * 1000);
|
||||
});
|
||||
|
||||
it('should interpret a number with "m" as the unit of measure as minutes', function () {
|
||||
const result = parseMilliseconds('9m');
|
||||
expect(result).toBe(9 * 1000 * 60);
|
||||
});
|
||||
|
||||
it('should interpret a number with "minute" as the unit of measure as minutes', function () {
|
||||
const result = parseMilliseconds('9minute');
|
||||
expect(result).toBe(9 * 1000 * 60);
|
||||
});
|
||||
|
||||
it('should interpret a number with "minutes" as the unit of measure as minutes', function () {
|
||||
const result = parseMilliseconds('9minutes');
|
||||
expect(result).toBe(9 * 1000 * 60);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parse function', function () {
|
||||
const command = 'plugin name';
|
||||
let options = {};
|
||||
const kbnPackage = { version: 1234 };
|
||||
beforeEach(function () {
|
||||
options = { pluginDir: fromRoot('plugins') };
|
||||
});
|
||||
|
||||
describe('timeout option', function () {
|
||||
it('should default to 0 (milliseconds)', function () {
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.timeout).toBe(0);
|
||||
});
|
||||
|
||||
it('should set settings.timeout property', function () {
|
||||
options.timeout = 1234;
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.timeout).toBe(1234);
|
||||
});
|
||||
});
|
||||
|
||||
describe('quiet option', function () {
|
||||
it('should default to false', function () {
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.quiet).toBe(false);
|
||||
});
|
||||
|
||||
it('should set settings.quiet property to true', function () {
|
||||
options.quiet = true;
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.quiet).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('silent option', function () {
|
||||
it('should default to false', function () {
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.silent).toBe(false);
|
||||
});
|
||||
|
||||
it('should set settings.silent property to true', function () {
|
||||
options.silent = true;
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.silent).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('config option', function () {
|
||||
it('should default to ZLS', function () {
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.config).toBe('');
|
||||
});
|
||||
|
||||
it('should set settings.config property', function () {
|
||||
options.config = 'foo bar baz';
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.config).toBe('foo bar baz');
|
||||
});
|
||||
});
|
||||
|
||||
describe('pluginDir option', function () {
|
||||
it('should default to plugins', function () {
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.pluginDir).toBe(fromRoot('plugins'));
|
||||
});
|
||||
|
||||
it('should set settings.config property', function () {
|
||||
options.pluginDir = 'foo bar baz';
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.pluginDir).toBe('foo bar baz');
|
||||
});
|
||||
});
|
||||
|
||||
describe('command value', function () {
|
||||
it('should set settings.plugin property', function () {
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.plugin).toBe(command);
|
||||
});
|
||||
});
|
||||
|
||||
describe('urls collection', function () {
|
||||
it('should populate the settings.urls property', function () {
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
const expected = [
|
||||
command,
|
||||
`https://artifacts.elastic.co/downloads/kibana-plugins/${command}/${command}-1234.zip`,
|
||||
];
|
||||
|
||||
expect(settings.urls).toEqual(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).toBe(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).toBe(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).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('parseMilliseconds function', function () {
|
||||
it.each([
|
||||
['', 0],
|
||||
['1gigablasts', 0],
|
||||
[1, 1],
|
||||
['1', 1],
|
||||
['5s', 5 * SECOND],
|
||||
['5second', 5 * SECOND],
|
||||
['5seconds', 5 * SECOND],
|
||||
['9m', 9 * MINUTE],
|
||||
['9minute', 9 * MINUTE],
|
||||
['9minutes', 9 * MINUTE],
|
||||
])('should parse %j to %j', (input, result) => {
|
||||
expect(parseMilliseconds(input)).toBe(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parse function', function () {
|
||||
const command = 'plugin name';
|
||||
const defaultOptions = { pluginDir: fromRoot('plugins') };
|
||||
const kbnPackage = { version: 1234 };
|
||||
|
||||
it('produces expected defaults', function () {
|
||||
expect(parse(command, { ...defaultOptions }, kbnPackage)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"config": "",
|
||||
"plugin": "plugin name",
|
||||
"pluginDir": <absolute path>/plugins,
|
||||
"quiet": false,
|
||||
"silent": false,
|
||||
"tempArchiveFile": <absolute path>/plugins/.plugin.installing/archive.part,
|
||||
"timeout": 0,
|
||||
"urls": Array [
|
||||
"plugin name",
|
||||
"https://artifacts.elastic.co/downloads/kibana-plugins/plugin name/plugin name-1234.zip",
|
||||
],
|
||||
"version": 1234,
|
||||
"workingPath": <absolute path>/plugins/.plugin.installing,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('consumes overrides', function () {
|
||||
const options = {
|
||||
quiet: true,
|
||||
silent: true,
|
||||
config: 'foo bar baz',
|
||||
...defaultOptions,
|
||||
};
|
||||
|
||||
expect(parse(command, options, kbnPackage)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"config": "foo bar baz",
|
||||
"plugin": "plugin name",
|
||||
"pluginDir": <absolute path>/plugins,
|
||||
"quiet": true,
|
||||
"silent": true,
|
||||
"tempArchiveFile": <absolute path>/plugins/.plugin.installing/archive.part,
|
||||
"timeout": 0,
|
||||
"urls": Array [
|
||||
"plugin name",
|
||||
"https://artifacts.elastic.co/downloads/kibana-plugins/plugin name/plugin name-1234.zip",
|
||||
],
|
||||
"version": 1234,
|
||||
"workingPath": <absolute path>/plugins/.plugin.installing,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,21 +17,24 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import yauzl from 'yauzl';
|
||||
import path from 'path';
|
||||
import { createWriteStream, mkdir } from 'fs';
|
||||
import { get } from 'lodash';
|
||||
|
||||
import yauzl from 'yauzl';
|
||||
|
||||
const isDirectoryRegex = /(\/|\\)$/;
|
||||
function isDirectory(filename) {
|
||||
return isDirectoryRegex.test(filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of package objects. There will be one for each of
|
||||
* package.json files in the archive
|
||||
*
|
||||
* @param {string} archive - path to plugin archive zip file
|
||||
* package.json files in the archive
|
||||
*/
|
||||
|
||||
export function analyzeArchive(archive) {
|
||||
const plugins = [];
|
||||
const regExp = new RegExp('(kibana[\\\\/][^\\\\/]+)[\\\\/]package.json', 'i');
|
||||
const regExp = new RegExp('(kibana[\\\\/][^\\\\/]+)[\\\\/]kibana.json', 'i');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
yauzl.open(archive, { lazyEntries: true }, function (err, zipfile) {
|
||||
|
@ -47,31 +50,32 @@ export function analyzeArchive(archive) {
|
|||
return zipfile.readEntry();
|
||||
}
|
||||
|
||||
zipfile.openReadStream(entry, function (err, readable) {
|
||||
zipfile.openReadStream(entry, function (error, readable) {
|
||||
const chunks = [];
|
||||
|
||||
if (err) {
|
||||
return reject(err);
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
readable.on('data', (chunk) => chunks.push(chunk));
|
||||
|
||||
readable.on('end', function () {
|
||||
const contents = Buffer.concat(chunks).toString();
|
||||
const pkg = JSON.parse(contents);
|
||||
const manifestJson = Buffer.concat(chunks).toString();
|
||||
const manifest = JSON.parse(manifestJson);
|
||||
|
||||
plugins.push(
|
||||
Object.assign(pkg, {
|
||||
archivePath: match[1],
|
||||
archive: archive,
|
||||
plugins.push({
|
||||
id: manifest.id,
|
||||
stripPrefix: match[1],
|
||||
|
||||
// Plugins must specify their version, and by default that version should match
|
||||
// the version of kibana down to the patch level. If these two versions need
|
||||
// to diverge, they can specify a kibana.version to indicate the version of
|
||||
// kibana the plugin is intended to work with.
|
||||
kibanaVersion: get(pkg, 'kibana.version', pkg.version),
|
||||
})
|
||||
);
|
||||
// Plugins must specify their version, and by default that version in the plugin
|
||||
// manifest should match the version of kibana down to the patch level. If these
|
||||
// two versions need plugins can specify a kibanaVersion to indicate the version
|
||||
// of kibana the plugin is intended to work with.
|
||||
kibanaVersion:
|
||||
typeof manifest.kibanaVersion === 'string' && manifest.kibanaVersion
|
||||
? manifest.kibanaVersion
|
||||
: manifest.version,
|
||||
});
|
||||
|
||||
zipfile.readEntry();
|
||||
});
|
||||
|
@ -85,12 +89,7 @@ export function analyzeArchive(archive) {
|
|||
});
|
||||
}
|
||||
|
||||
const isDirectoryRegex = /(\/|\\)$/;
|
||||
export function _isDirectory(filename) {
|
||||
return isDirectoryRegex.test(filename);
|
||||
}
|
||||
|
||||
export function extractArchive(archive, targetDir, extractPath) {
|
||||
export function extractArchive(archive, targetDir, stripPrefix) {
|
||||
return new Promise((resolve, reject) => {
|
||||
yauzl.open(archive, { lazyEntries: true }, function (err, zipfile) {
|
||||
if (err) {
|
||||
|
@ -102,8 +101,8 @@ export function extractArchive(archive, targetDir, extractPath) {
|
|||
zipfile.on('entry', function (entry) {
|
||||
let fileName = entry.fileName;
|
||||
|
||||
if (extractPath && fileName.startsWith(extractPath)) {
|
||||
fileName = fileName.substring(extractPath.length);
|
||||
if (stripPrefix && fileName.startsWith(stripPrefix)) {
|
||||
fileName = fileName.substring(stripPrefix.length);
|
||||
} else {
|
||||
return zipfile.readEntry();
|
||||
}
|
||||
|
@ -112,30 +111,34 @@ export function extractArchive(archive, targetDir, extractPath) {
|
|||
fileName = path.join(targetDir, fileName);
|
||||
}
|
||||
|
||||
if (_isDirectory(fileName)) {
|
||||
mkdir(fileName, { recursive: true }, function (err) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
if (isDirectory(fileName)) {
|
||||
mkdir(fileName, { recursive: true }, function (error) {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
zipfile.readEntry();
|
||||
});
|
||||
} else {
|
||||
// file entry
|
||||
zipfile.openReadStream(entry, function (err, readStream) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
zipfile.openReadStream(entry, function (error, readStream) {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
// ensure parent directory exists
|
||||
mkdir(path.dirname(fileName), { recursive: true }, function (err) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
mkdir(path.dirname(fileName), { recursive: true }, function (error2) {
|
||||
if (error2) {
|
||||
return reject(error2);
|
||||
}
|
||||
|
||||
readStream.pipe(
|
||||
createWriteStream(fileName, { mode: entry.externalFileAttributes >>> 16 })
|
||||
createWriteStream(fileName, {
|
||||
// eslint-disable-next-line no-bitwise
|
||||
mode: entry.externalFileAttributes >>> 16,
|
||||
})
|
||||
);
|
||||
|
||||
readStream.on('end', function () {
|
||||
zipfile.readEntry();
|
||||
});
|
||||
|
|
|
@ -17,12 +17,16 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import del from 'del';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import glob from 'glob';
|
||||
import fs from 'fs';
|
||||
import { analyzeArchive, extractArchive, _isDirectory } from './zip';
|
||||
|
||||
import del from 'del';
|
||||
import glob from 'glob';
|
||||
|
||||
import { analyzeArchive, extractArchive } from './zip';
|
||||
|
||||
const getMode = (path) => (fs.statSync(path).mode & parseInt('777', 8)).toString(8);
|
||||
|
||||
describe('kibana cli', function () {
|
||||
describe('zip', function () {
|
||||
|
@ -43,32 +47,37 @@ describe('kibana cli', function () {
|
|||
describe('analyzeArchive', function () {
|
||||
it('returns array of plugins', async () => {
|
||||
const packages = await analyzeArchive(archivePath);
|
||||
const plugin = packages[0];
|
||||
|
||||
expect(packages).toBeInstanceOf(Array);
|
||||
expect(plugin.name).toBe('test-plugin');
|
||||
expect(plugin.archivePath).toBe('kibana/test-plugin');
|
||||
expect(plugin.archive).toBe(archivePath);
|
||||
expect(plugin.kibanaVersion).toBe('1.0.0');
|
||||
expect(packages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "testPlugin",
|
||||
"kibanaVersion": "1.0.0",
|
||||
"stripPrefix": "kibana/test-plugin",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractArchive', () => {
|
||||
it('extracts files using the extractPath filter', async () => {
|
||||
const archive = path.resolve(repliesPath, 'test_plugin_many.zip');
|
||||
|
||||
const archive = path.resolve(repliesPath, 'test_plugin.zip');
|
||||
await extractArchive(archive, tempPath, 'kibana/test-plugin');
|
||||
const files = await glob.sync('**/*', { cwd: tempPath });
|
||||
|
||||
const expected = [
|
||||
'extra file only in zip.txt',
|
||||
'index.js',
|
||||
'package.json',
|
||||
'public',
|
||||
'public/app.js',
|
||||
'README.md',
|
||||
];
|
||||
expect(files.sort()).toEqual(expected.sort());
|
||||
expect(glob.sync('**/*', { cwd: tempPath })).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"bin",
|
||||
"bin/executable",
|
||||
"bin/not-executable",
|
||||
"kibana.json",
|
||||
"node_modules",
|
||||
"node_modules/some-package",
|
||||
"node_modules/some-package/index.js",
|
||||
"node_modules/some-package/package.json",
|
||||
"public",
|
||||
"public/index.js",
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -76,49 +85,26 @@ describe('kibana cli', function () {
|
|||
it('verify consistency of modes of files', async () => {
|
||||
const archivePath = path.resolve(repliesPath, 'test_plugin.zip');
|
||||
|
||||
await extractArchive(archivePath, tempPath, 'kibana/libs');
|
||||
const files = await glob.sync('**/*', { cwd: tempPath });
|
||||
await extractArchive(archivePath, tempPath, 'kibana/test-plugin/bin');
|
||||
|
||||
const expected = ['executable', 'unexecutable'];
|
||||
expect(files.sort()).toEqual(expected.sort());
|
||||
expect(glob.sync('**/*', { cwd: tempPath })).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"executable",
|
||||
"not-executable",
|
||||
]
|
||||
`);
|
||||
|
||||
const executableMode =
|
||||
'0' +
|
||||
(fs.statSync(path.resolve(tempPath, 'executable')).mode & parseInt('777', 8)).toString(8);
|
||||
const unExecutableMode =
|
||||
'0' +
|
||||
(fs.statSync(path.resolve(tempPath, 'unexecutable')).mode & parseInt('777', 8)).toString(
|
||||
8
|
||||
);
|
||||
|
||||
expect(executableMode).toEqual('0755');
|
||||
expect(unExecutableMode).toEqual('0644');
|
||||
expect(getMode(path.resolve(tempPath, 'executable'))).toEqual('755');
|
||||
expect(getMode(path.resolve(tempPath, 'not-executable'))).toEqual('644');
|
||||
});
|
||||
});
|
||||
|
||||
it('handles a corrupt zip archive', async () => {
|
||||
try {
|
||||
await extractArchive(path.resolve(repliesPath, 'corrupt.zip'));
|
||||
throw new Error('This should have failed');
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('_isDirectory', () => {
|
||||
it('should check for a forward slash', () => {
|
||||
expect(_isDirectory('/foo/bar/')).toBe(true);
|
||||
});
|
||||
|
||||
it('should check for a backslash', () => {
|
||||
expect(_isDirectory('\\foo\\bar\\')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for files', () => {
|
||||
expect(_isDirectory('foo.txt')).toBe(false);
|
||||
expect(_isDirectory('\\path\\to\\foo.txt')).toBe(false);
|
||||
expect(_isDirectory('/path/to/foo.txt')).toBe(false);
|
||||
await expect(
|
||||
extractArchive(path.resolve(repliesPath, 'corrupt.zip'))
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"end of central directory record signature not found"`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { isOSS } from './is_oss';
|
||||
import { isOss } from './is_oss';
|
||||
|
||||
function isXPack(plugin) {
|
||||
return /x-pack/.test(plugin);
|
||||
|
@ -25,7 +25,7 @@ function isXPack(plugin) {
|
|||
|
||||
export function errorIfXPackInstall(settings) {
|
||||
if (isXPack(settings.plugin)) {
|
||||
if (isOSS()) {
|
||||
if (isOss()) {
|
||||
throw new Error(
|
||||
'You are using the OSS-only distribution of Kibana. ' +
|
||||
'As of version 6.3+ X-Pack is bundled in the standard distribution of this software by default; ' +
|
||||
|
@ -40,7 +40,7 @@ export function errorIfXPackInstall(settings) {
|
|||
}
|
||||
|
||||
export function errorIfXPackRemove(settings) {
|
||||
if (isXPack(settings.plugin) && !isOSS()) {
|
||||
if (isXPack(settings.plugin) && !isOss()) {
|
||||
throw new Error(
|
||||
'You are using the standard distribution of Kibana. Please install the OSS-only distribution to remove X-Pack features.'
|
||||
);
|
||||
|
|
|
@ -20,6 +20,6 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export function isOSS() {
|
||||
export function isOss() {
|
||||
return !fs.existsSync(path.resolve(__dirname, '../../../x-pack'));
|
||||
}
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { isOSS } from './is_oss';
|
||||
import { isOss } from './is_oss';
|
||||
|
||||
describe('is_oss', () => {
|
||||
describe('x-pack installed', () => {
|
||||
it('should return false', () => {
|
||||
expect(isOSS()).toEqual(false);
|
||||
expect(isOss()).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export default function (settings, logger) {
|
||||
export function logWarnings(logger) {
|
||||
process.on('warning', (warning) => {
|
||||
// deprecation warnings do no reflect a current problem for
|
||||
// the user and therefor should be filtered out.
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
/**
|
||||
* Logs messages and errors
|
||||
*/
|
||||
export default class Logger {
|
||||
export class Logger {
|
||||
constructor(settings = {}) {
|
||||
this.previousLineEnded = true;
|
||||
this.silent = !!settings.silent;
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import Logger from './logger';
|
||||
|
||||
import { Logger } from './logger';
|
||||
|
||||
describe('kibana cli', function () {
|
||||
describe('plugin installer', function () {
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export function warnIfUsingPluginDirOption(options, defaultValue, logger) {
|
||||
if (options && options.pluginDir !== defaultValue) {
|
||||
logger.log(
|
||||
'Warning: Using the -d, --plugin-dir option is deprecated, and is ' +
|
||||
'known to not work for all plugins, including X-Pack.'
|
||||
);
|
||||
}
|
||||
}
|
|
@ -18,37 +18,16 @@
|
|||
*/
|
||||
|
||||
import { fromRoot } from '../../core/server/utils';
|
||||
import list from './list';
|
||||
import Logger from '../lib/logger';
|
||||
import { parse } from './settings';
|
||||
import logWarnings from '../lib/log_warnings';
|
||||
import { warnIfUsingPluginDirOption } from '../lib/warn_if_plugin_dir_option';
|
||||
import { list } from './list';
|
||||
import { Logger } from '../lib/logger';
|
||||
import { logWarnings } from '../lib/log_warnings';
|
||||
|
||||
function processCommand(command, options) {
|
||||
let settings;
|
||||
try {
|
||||
settings = parse(command, options);
|
||||
} catch (ex) {
|
||||
//The logger has not yet been initialized.
|
||||
console.error(ex.message);
|
||||
process.exit(64); // eslint-disable-line no-process-exit
|
||||
}
|
||||
|
||||
const logger = new Logger(settings);
|
||||
|
||||
warnIfUsingPluginDirOption(settings, fromRoot('plugins'), logger);
|
||||
logWarnings(settings, logger);
|
||||
list(settings, logger);
|
||||
function processCommand() {
|
||||
const logger = new Logger();
|
||||
logWarnings(logger);
|
||||
list(fromRoot('plugins'), logger);
|
||||
}
|
||||
|
||||
export default function pluginList(program) {
|
||||
program
|
||||
.command('list')
|
||||
.option(
|
||||
'-d, --plugin-dir <path>',
|
||||
'path to the directory where plugins are stored (DEPRECATED, known to not work for all plugins)',
|
||||
fromRoot('plugins')
|
||||
)
|
||||
.description('list installed plugins')
|
||||
.action(processCommand);
|
||||
export function listCommand(program) {
|
||||
program.command('list').description('list installed plugins').action(processCommand);
|
||||
}
|
||||
|
|
|
@ -20,19 +20,20 @@
|
|||
import { statSync, readdirSync, readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
export default function list(settings, logger) {
|
||||
readdirSync(settings.pluginDir).forEach((filename) => {
|
||||
const stat = statSync(join(settings.pluginDir, filename));
|
||||
export function list(pluginDir, logger) {
|
||||
readdirSync(pluginDir).forEach((name) => {
|
||||
const stat = statSync(join(pluginDir, name));
|
||||
|
||||
if (stat.isDirectory() && filename[0] !== '.') {
|
||||
if (stat.isDirectory() && name[0] !== '.') {
|
||||
try {
|
||||
const packagePath = join(settings.pluginDir, filename, 'package.json');
|
||||
const { version } = JSON.parse(readFileSync(packagePath, 'utf8'));
|
||||
logger.log(filename + '@' + version);
|
||||
const packagePath = join(pluginDir, name, 'kibana.json');
|
||||
const pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
|
||||
logger.log(pkg.id + '@' + pkg.version);
|
||||
} catch (e) {
|
||||
throw new Error('Unable to read package.json file for plugin ' + filename);
|
||||
throw new Error('Unable to read kibana.json file for plugin ' + name);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
logger.log(''); //intentional blank line for aesthetics
|
||||
}
|
||||
|
|
|
@ -17,78 +17,67 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import sinon from 'sinon';
|
||||
import del from 'del';
|
||||
import Logger from '../lib/logger';
|
||||
import list from './list';
|
||||
import { join } from 'path';
|
||||
import { writeFileSync, appendFileSync, mkdirSync } from 'fs';
|
||||
import { writeFileSync, mkdirSync } from 'fs';
|
||||
|
||||
import del from 'del';
|
||||
|
||||
import { list } from './list';
|
||||
|
||||
function createPlugin(name, version, pluginBaseDir) {
|
||||
const pluginDir = join(pluginBaseDir, name);
|
||||
mkdirSync(pluginDir, { recursive: true });
|
||||
appendFileSync(join(pluginDir, 'package.json'), '{"version": "' + version + '"}');
|
||||
writeFileSync(
|
||||
join(pluginDir, 'kibana.json'),
|
||||
JSON.stringify({
|
||||
id: name,
|
||||
version,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const logger = {
|
||||
messages: [],
|
||||
log(msg) {
|
||||
this.messages.push(`log: ${msg}`);
|
||||
},
|
||||
error(msg) {
|
||||
this.messages.push(`error: ${msg}`);
|
||||
},
|
||||
};
|
||||
|
||||
describe('kibana cli', function () {
|
||||
describe('plugin lister', function () {
|
||||
const pluginDir = join(__dirname, '.test.data.list');
|
||||
let logger;
|
||||
|
||||
const settings = {
|
||||
pluginDir: pluginDir,
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
logger = new Logger(settings);
|
||||
sinon.stub(logger, 'log');
|
||||
sinon.stub(logger, 'error');
|
||||
logger.messages.length = 0;
|
||||
del.sync(pluginDir);
|
||||
mkdirSync(pluginDir, { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
logger.log.restore();
|
||||
logger.error.restore();
|
||||
del.sync(pluginDir);
|
||||
});
|
||||
|
||||
it('list all of the folders in the plugin folder', function () {
|
||||
createPlugin('plugin1', '5.0.0-alpha2', pluginDir);
|
||||
createPlugin('plugin2', '3.2.1', pluginDir);
|
||||
createPlugin('plugin3', '1.2.3', pluginDir);
|
||||
|
||||
list(settings, logger);
|
||||
|
||||
expect(logger.log.calledWith('plugin1@5.0.0-alpha2')).toBe(true);
|
||||
expect(logger.log.calledWith('plugin2@3.2.1')).toBe(true);
|
||||
expect(logger.log.calledWith('plugin3@1.2.3')).toBe(true);
|
||||
});
|
||||
|
||||
it('ignore folders that start with a period', function () {
|
||||
it('list all of the folders in the plugin folder, ignoring dot prefixed plugins and regular files', function () {
|
||||
createPlugin('.foo', '1.0.0', pluginDir);
|
||||
createPlugin('plugin1', '5.0.0-alpha2', pluginDir);
|
||||
createPlugin('plugin2', '3.2.1', pluginDir);
|
||||
createPlugin('plugin3', '1.2.3', pluginDir);
|
||||
createPlugin('.bar', '1.0.0', pluginDir);
|
||||
|
||||
list(settings, logger);
|
||||
|
||||
expect(logger.log.calledWith('.foo@1.0.0')).toBe(false);
|
||||
expect(logger.log.calledWith('.bar@1.0.0')).toBe(false);
|
||||
});
|
||||
|
||||
it('list should only list folders', function () {
|
||||
createPlugin('plugin1', '1.0.0', pluginDir);
|
||||
createPlugin('plugin2', '1.0.0', pluginDir);
|
||||
createPlugin('plugin3', '1.0.0', pluginDir);
|
||||
writeFileSync(join(pluginDir, 'plugin4'), 'This is a file, and not a folder.');
|
||||
|
||||
list(settings, logger);
|
||||
list(pluginDir, logger);
|
||||
|
||||
expect(logger.log.calledWith('plugin1@1.0.0')).toBe(true);
|
||||
expect(logger.log.calledWith('plugin2@1.0.0')).toBe(true);
|
||||
expect(logger.log.calledWith('plugin3@1.0.0')).toBe(true);
|
||||
expect(logger.messages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"log: plugin1@5.0.0-alpha2",
|
||||
"log: plugin2@3.2.1",
|
||||
"log: plugin3@1.2.3",
|
||||
"log: ",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('list should throw an exception if a plugin does not have a package.json', function () {
|
||||
|
@ -96,19 +85,23 @@ describe('kibana cli', function () {
|
|||
mkdirSync(join(pluginDir, 'empty-plugin'), { recursive: true });
|
||||
|
||||
expect(function () {
|
||||
list(settings, logger);
|
||||
}).toThrowError('Unable to read package.json file for plugin empty-plugin');
|
||||
list(pluginDir, logger);
|
||||
}).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Unable to read kibana.json file for plugin empty-plugin"`
|
||||
);
|
||||
});
|
||||
|
||||
it('list should throw an exception if a plugin have an empty package.json', function () {
|
||||
createPlugin('plugin1', '1.0.0', pluginDir);
|
||||
const invalidPluginDir = join(pluginDir, 'invalid-plugin');
|
||||
mkdirSync(invalidPluginDir, { recursive: true });
|
||||
appendFileSync(join(invalidPluginDir, 'package.json'), '');
|
||||
writeFileSync(join(invalidPluginDir, 'package.json'), '');
|
||||
|
||||
expect(function () {
|
||||
list(settings, logger);
|
||||
}).toThrowError('Unable to read package.json file for plugin invalid-plugin');
|
||||
list(pluginDir, logger);
|
||||
}).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Unable to read kibana.json file for plugin invalid-plugin"`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export function parse(command) {
|
||||
const settings = {
|
||||
pluginDir: command.pluginDir || '',
|
||||
};
|
||||
|
||||
return settings;
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { fromRoot } from '../../core/server/utils';
|
||||
import { parse } from './settings';
|
||||
|
||||
describe('kibana cli', function () {
|
||||
describe('plugin installer', function () {
|
||||
describe('command line option parsing', function () {
|
||||
describe('parse function', function () {
|
||||
let command;
|
||||
const options = {};
|
||||
beforeEach(function () {
|
||||
command = { pluginDir: fromRoot('plugins') };
|
||||
});
|
||||
|
||||
describe('pluginDir option', function () {
|
||||
it('should default to plugins', function () {
|
||||
const settings = parse(command, options);
|
||||
|
||||
expect(settings.pluginDir).toBe(fromRoot('plugins'));
|
||||
});
|
||||
|
||||
it('should set settings.config property', function () {
|
||||
command.pluginDir = 'foo bar baz';
|
||||
const settings = parse(command, options);
|
||||
|
||||
expect(settings.pluginDir).toBe('foo bar baz');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -17,46 +17,34 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { fromRoot } from '../../core/server/utils';
|
||||
import remove from './remove';
|
||||
import Logger from '../lib/logger';
|
||||
import { remove } from './remove';
|
||||
import { Logger } from '../lib/logger';
|
||||
import { parse } from './settings';
|
||||
import { getConfigPath } from '../../core/server/path';
|
||||
import logWarnings from '../lib/log_warnings';
|
||||
import { warnIfUsingPluginDirOption } from '../lib/warn_if_plugin_dir_option';
|
||||
import { logWarnings } from '../lib/log_warnings';
|
||||
|
||||
function processCommand(command, options) {
|
||||
let settings;
|
||||
try {
|
||||
settings = parse(command, options);
|
||||
} catch (ex) {
|
||||
//The logger has not yet been initialized.
|
||||
// The logger has not yet been initialized.
|
||||
console.error(ex.message);
|
||||
process.exit(64); // eslint-disable-line no-process-exit
|
||||
}
|
||||
|
||||
const logger = new Logger(settings);
|
||||
|
||||
warnIfUsingPluginDirOption(settings, fromRoot('plugins'), logger);
|
||||
logWarnings(settings, logger);
|
||||
remove(settings, logger);
|
||||
}
|
||||
|
||||
export default function pluginRemove(program) {
|
||||
export function removeCommand(program) {
|
||||
program
|
||||
.command('remove <plugin>')
|
||||
.option('-q, --quiet', 'disable all process messaging except errors')
|
||||
.option('-s, --silent', 'disable all process messaging')
|
||||
.option('-c, --config <path>', 'path to the config file', getConfigPath())
|
||||
.option(
|
||||
'-d, --plugin-dir <path>',
|
||||
'path to the directory where plugins are stored (DEPRECATED, known to not work for all plugins)',
|
||||
fromRoot('plugins')
|
||||
)
|
||||
.description(
|
||||
'remove a plugin',
|
||||
`common examples:
|
||||
remove x-pack`
|
||||
)
|
||||
.description('remove a plugin')
|
||||
.action(processCommand);
|
||||
}
|
||||
|
|
|
@ -18,11 +18,12 @@
|
|||
*/
|
||||
|
||||
import { statSync } from 'fs';
|
||||
import { errorIfXPackRemove } from '../lib/error_if_x_pack';
|
||||
|
||||
import del from 'del';
|
||||
|
||||
export default function remove(settings, logger) {
|
||||
import { errorIfXPackRemove } from '../lib/error_if_x_pack';
|
||||
|
||||
export function remove(settings, logger) {
|
||||
try {
|
||||
let stat;
|
||||
try {
|
||||
|
|
|
@ -17,13 +17,15 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { join } from 'path';
|
||||
import { writeFileSync, existsSync, mkdirSync } from 'fs';
|
||||
|
||||
import sinon from 'sinon';
|
||||
import glob from 'glob-all';
|
||||
import del from 'del';
|
||||
import Logger from '../lib/logger';
|
||||
import remove from './remove';
|
||||
import { join } from 'path';
|
||||
import { writeFileSync, existsSync, mkdirSync } from 'fs';
|
||||
|
||||
import { Logger } from '../lib/logger';
|
||||
import { remove } from './remove';
|
||||
|
||||
describe('kibana cli', function () {
|
||||
describe('plugin remover', function () {
|
||||
|
|
|
@ -19,12 +19,14 @@
|
|||
|
||||
import { resolve } from 'path';
|
||||
|
||||
import { fromRoot } from '../../core/server/utils';
|
||||
|
||||
export function parse(command, options) {
|
||||
const settings = {
|
||||
quiet: options.quiet || false,
|
||||
silent: options.silent || false,
|
||||
config: options.config || '',
|
||||
pluginDir: options.pluginDir || '',
|
||||
pluginDir: fromRoot('plugins'),
|
||||
plugin: command,
|
||||
};
|
||||
|
||||
|
|
|
@ -17,88 +17,42 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { fromRoot } from '../../core/server/utils';
|
||||
import { createAbsolutePathSerializer } from '@kbn/dev-utils';
|
||||
|
||||
import { parse } from './settings';
|
||||
|
||||
describe('kibana cli', function () {
|
||||
describe('plugin installer', function () {
|
||||
describe('command line option parsing', function () {
|
||||
describe('parse function', function () {
|
||||
const command = 'plugin name';
|
||||
let options = {};
|
||||
const kbnPackage = { version: 1234 };
|
||||
beforeEach(function () {
|
||||
options = { pluginDir: fromRoot('plugins') };
|
||||
});
|
||||
const command = 'plugin name';
|
||||
|
||||
describe('quiet option', function () {
|
||||
it('should default to false', function () {
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
expect.addSnapshotSerializer(createAbsolutePathSerializer());
|
||||
|
||||
expect(settings.quiet).toBe(false);
|
||||
});
|
||||
|
||||
it('should set settings.quiet property to true', function () {
|
||||
options.quiet = true;
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.quiet).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('silent option', function () {
|
||||
it('should default to false', function () {
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.silent).toBe(false);
|
||||
});
|
||||
|
||||
it('should set settings.silent property to true', function () {
|
||||
options.silent = true;
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.silent).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('config option', function () {
|
||||
it('should default to ZLS', function () {
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.config).toBe('');
|
||||
});
|
||||
|
||||
it('should set settings.config property', function () {
|
||||
options.config = 'foo bar baz';
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.config).toBe('foo bar baz');
|
||||
});
|
||||
});
|
||||
|
||||
describe('pluginDir option', function () {
|
||||
it('should default to plugins', function () {
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.pluginDir).toBe(fromRoot('plugins'));
|
||||
});
|
||||
|
||||
it('should set settings.config property', function () {
|
||||
options.pluginDir = 'foo bar baz';
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.pluginDir).toBe('foo bar baz');
|
||||
});
|
||||
});
|
||||
|
||||
describe('command value', function () {
|
||||
it('should set settings.plugin property', function () {
|
||||
const settings = parse(command, options, kbnPackage);
|
||||
|
||||
expect(settings.plugin).toBe(command);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
it('produces the defaults', () => {
|
||||
expect(parse(command, {})).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"config": "",
|
||||
"plugin": "plugin name",
|
||||
"pluginDir": <absolute path>/plugins,
|
||||
"pluginPath": <absolute path>/plugins/plugin name,
|
||||
"quiet": false,
|
||||
"silent": false,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('overrides the defaults with the parsed cli options', () => {
|
||||
expect(
|
||||
parse(command, {
|
||||
quiet: true,
|
||||
silent: true,
|
||||
config: 'foo/bar',
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"config": "foo/bar",
|
||||
"plugin": "plugin name",
|
||||
"pluginDir": <absolute path>/plugins,
|
||||
"pluginPath": <absolute path>/plugins/plugin name,
|
||||
"quiet": true,
|
||||
"silent": true,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -7878,11 +7878,6 @@ builtin-status-codes@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
|
||||
integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
|
||||
|
||||
builtins@0.0.7:
|
||||
version "0.0.7"
|
||||
resolved "https://registry.yarnpkg.com/builtins/-/builtins-0.0.7.tgz#355219cd6cf18dbe7c01cc7fd2dce765cfdc549a"
|
||||
integrity sha1-NVIZzWzxjb58Acx/0tznZc/cVJo=
|
||||
|
||||
bytes@1:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8"
|
||||
|
@ -29959,13 +29954,6 @@ validate-npm-package-license@^3.0.1:
|
|||
spdx-correct "~1.0.0"
|
||||
spdx-expression-parse "~1.0.0"
|
||||
|
||||
validate-npm-package-name@2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-2.2.2.tgz#f65695b22f7324442019a3c7fa39a6e7fd299085"
|
||||
integrity sha1-9laVsi9zJEQgGaPH+jmm5/0pkIU=
|
||||
dependencies:
|
||||
builtins "0.0.7"
|
||||
|
||||
validator@^10.11.0:
|
||||
version "10.11.0"
|
||||
resolved "https://registry.yarnpkg.com/validator/-/validator-10.11.0.tgz#003108ea6e9a9874d31ccc9e5006856ccd76b228"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue