From a5989d41f5cf9d7f47d45d8cacd52b6f7164fd85 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Fri, 13 Jul 2018 15:50:15 +0200 Subject: [PATCH] Use new platform CLI as the main entry point. --- bin/kibana | 2 +- bin/kibana.bat | 2 +- package.json | 6 + scripts/kibana.js | 2 +- src/cli/cli.js | 64 ----- .../__fixtures__/invalid_config.yml | 13 - .../reload_logging_config/kibana.test.yml | 8 - .../__snapshots__/invalid_config.test.js.snap | 18 -- .../reload_logging_config.test.js.snap | 66 ----- .../integration_tests/invalid_config.test.js | 50 ---- .../reload_logging_config.test.js | 145 ---------- src/cli/serve/serve.js | 226 ---------------- src/cli_keystore/cli_keystore.js | 2 +- src/cli_plugin/cli.js | 2 +- .../cli/__tests__/read_keystore.test.ts} | 19 +- src/core/cli/apply_config_overrides.ts | 150 +++++++++++ src/core/cli/args.ts | 71 +++++ src/core/cli/cli.ts | 63 +++++ src/core/cli/commands/index.ts | 20 ++ src/core/cli/commands/serve/command.ts | 254 ++++++++++++++++++ .../commands/serve/handler.ts} | 65 ++--- src/{cli/dev_ssl.js => core/cli/dev_ssl.ts} | 0 src/{ => core}/cli/index.js | 2 +- src/core/cli/kibana_features.ts | 72 +++++ .../cli/read_keystore.ts} | 5 +- src/core/server/index.ts | 2 - .../__tests__/legacy_service.test.ts | 4 +- .../server/legacy_compat/legacy_service.ts | 4 +- .../build/tasks/create_package_json_task.js | 1 + src/dev/jest/config.js | 2 +- .../cli/cluster/__mocks__/cluster.js | 0 .../cli/cluster/cluster_manager.js | 6 +- .../cli/cluster/cluster_manager.test.js | 0 src/{ => legacy}/cli/cluster/worker.js | 15 +- src/{ => legacy}/cli/cluster/worker.test.js | 0 src/{ => legacy}/cli/color.js | 0 src/{ => legacy}/cli/command.js | 0 src/{ => legacy}/cli/help.js | 0 src/{ => legacy}/cli/log.js | 0 .../cli/repl/__snapshots__/repl.test.js.snap | 0 src/{ => legacy}/cli/repl/index.js | 0 src/{ => legacy}/cli/repl/repl.test.js | 0 .../path/{index.test.js => index.test.ts} | 6 +- src/server/path/{index.js => index.ts} | 26 +- src/utils/{from_root.js => from_root.ts} | 4 +- x-pack/package.json | 2 +- x-pack/yarn.lock | 12 +- yarn.lock | 10 + 48 files changed, 734 insertions(+), 687 deletions(-) delete mode 100644 src/cli/cli.js delete mode 100644 src/cli/serve/integration_tests/__fixtures__/invalid_config.yml delete mode 100644 src/cli/serve/integration_tests/__fixtures__/reload_logging_config/kibana.test.yml delete mode 100644 src/cli/serve/integration_tests/__snapshots__/invalid_config.test.js.snap delete mode 100644 src/cli/serve/integration_tests/__snapshots__/reload_logging_config.test.js.snap delete mode 100644 src/cli/serve/integration_tests/invalid_config.test.js rename src/{cli/serve/read_keystore.test.js => core/cli/__tests__/read_keystore.test.ts} (78%) create mode 100644 src/core/cli/apply_config_overrides.ts create mode 100644 src/core/cli/args.ts create mode 100644 src/core/cli/cli.ts create mode 100644 src/core/cli/commands/index.ts create mode 100644 src/core/cli/commands/serve/command.ts rename src/core/{server/bootstrap.ts => cli/commands/serve/handler.ts} (59%) rename src/{cli/dev_ssl.js => core/cli/dev_ssl.ts} (100%) rename src/{ => core}/cli/index.js (96%) create mode 100644 src/core/cli/kibana_features.ts rename src/{cli/serve/read_keystore.js => core/cli/read_keystore.ts} (91%) rename src/{ => legacy}/cli/cluster/__mocks__/cluster.js (100%) rename src/{ => legacy}/cli/cluster/cluster_manager.js (97%) rename src/{ => legacy}/cli/cluster/cluster_manager.test.js (100%) rename src/{ => legacy}/cli/cluster/worker.js (91%) rename src/{ => legacy}/cli/cluster/worker.test.js (100%) rename src/{ => legacy}/cli/color.js (100%) rename src/{ => legacy}/cli/command.js (100%) rename src/{ => legacy}/cli/help.js (100%) rename src/{ => legacy}/cli/log.js (100%) rename src/{ => legacy}/cli/repl/__snapshots__/repl.test.js.snap (100%) rename src/{ => legacy}/cli/repl/index.js (100%) rename src/{ => legacy}/cli/repl/repl.test.js (100%) rename src/server/path/{index.test.js => index.test.ts} (89%) rename src/server/path/{index.js => index.ts} (71%) rename src/utils/{from_root.js => from_root.ts} (95%) diff --git a/bin/kibana b/bin/kibana index a2c7e1417e4b20..4d40fd862c34e1 100755 --- a/bin/kibana +++ b/bin/kibana @@ -21,4 +21,4 @@ if [ ! -x "$NODE" ]; then exit 1 fi -NODE_ENV=production exec "${NODE}" $NODE_OPTIONS --no-warnings "${DIR}/src/cli" ${@} +NODE_ENV=production exec "${NODE}" $NODE_OPTIONS --no-warnings "${DIR}/src/core/cli" ${@} diff --git a/bin/kibana.bat b/bin/kibana.bat index c7ae07f7cecca2..cc6ab7a464cbaa 100755 --- a/bin/kibana.bat +++ b/bin/kibana.bat @@ -14,7 +14,7 @@ If Not Exist "%NODE%" ( Exit /B 1 ) -"%NODE%" %NODE_OPTIONS% --no-warnings "%DIR%\src\cli" %* +"%NODE%" %NODE_OPTIONS% --no-warnings "%DIR%\src\core\cli" %* :finally diff --git a/package.json b/package.json index 0e372f5a655136..751a071f5d78cb 100644 --- a/package.json +++ b/package.json @@ -198,6 +198,7 @@ "whatwg-fetch": "^2.0.3", "wreck": "12.4.0", "x-pack": "link:x-pack", + "yargs": "^12.0.1", "yauzl": "2.7.0" }, "devDependencies": { @@ -222,6 +223,7 @@ "@types/fetch-mock": "^5.12.2", "@types/getopts": "^2.0.0", "@types/glob": "^5.0.35", + "@types/hapi": "^13.0.38", "@types/hapi-latest": "npm:@types/hapi@17.0.12", "@types/has-ansi": "^3.0.0", "@types/jest": "^22.2.3", @@ -241,6 +243,7 @@ "@types/strip-ansi": "^3.0.0", "@types/supertest": "^2.0.5", "@types/type-detect": "^4.0.1", + "@types/yargs": "^11.1.0", "angular-mocks": "1.4.7", "babel-eslint": "8.1.2", "babel-jest": "^22.4.3", @@ -336,5 +339,8 @@ "engines": { "node": "8.11.4", "yarn": "^1.6.0" + }, + "yargs": { + "dot-notation": false } } diff --git a/scripts/kibana.js b/scripts/kibana.js index b1b470a37535fc..555876e34764a3 100644 --- a/scripts/kibana.js +++ b/scripts/kibana.js @@ -18,4 +18,4 @@ */ require('../src/setup_node_env'); -require('../src/cli/cli'); +require('../src/core/cli'); diff --git a/src/cli/cli.js b/src/cli/cli.js deleted file mode 100644 index 68cf8017ce3690..00000000000000 --- a/src/cli/cli.js +++ /dev/null @@ -1,64 +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 _ from 'lodash'; -import { pkg } from '../utils'; -import Command from './command'; -import serveCommand from './serve/serve'; - -const argv = process.env.kbnWorkerArgv ? JSON.parse(process.env.kbnWorkerArgv) : process.argv.slice(); -const program = new Command('bin/kibana'); - -program - .version(pkg.version) - .description( - 'Kibana is an open source (Apache Licensed), browser ' + - 'based analytics and search dashboard for Elasticsearch.' - ); - -// attach commands -serveCommand(program); - -program - .command('help ') - .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}`); - cmd.help(); - }); - -program - .command('*', null, { noHelp: true }) - .action(function (cmd) { - program.error(`unknown command ${cmd}`); - }); - -// check for no command name -const subCommand = argv[2] && !String(argv[2][0]).match(/^-|^\.|\//); - -if (!subCommand) { - if (_.intersection(argv.slice(2), ['-h', '--help']).length) { - program.defaultHelp(); - } else { - argv.splice(2, 0, ['serve']); - } -} - -program.parse(argv); diff --git a/src/cli/serve/integration_tests/__fixtures__/invalid_config.yml b/src/cli/serve/integration_tests/__fixtures__/invalid_config.yml deleted file mode 100644 index df9ea641cd3fe5..00000000000000 --- a/src/cli/serve/integration_tests/__fixtures__/invalid_config.yml +++ /dev/null @@ -1,13 +0,0 @@ -unknown: - key: 1 - -other: - unknown.key: 2 - third: 3 - -some.flat.key: 4 - -some.array: - - 1 - - 2 - - 3 diff --git a/src/cli/serve/integration_tests/__fixtures__/reload_logging_config/kibana.test.yml b/src/cli/serve/integration_tests/__fixtures__/reload_logging_config/kibana.test.yml deleted file mode 100644 index 23f33940283c0d..00000000000000 --- a/src/cli/serve/integration_tests/__fixtures__/reload_logging_config/kibana.test.yml +++ /dev/null @@ -1,8 +0,0 @@ -server: - port: 8274 -logging: - json: true -optimize: - enabled: false -plugins: - initialize: false diff --git a/src/cli/serve/integration_tests/__snapshots__/invalid_config.test.js.snap b/src/cli/serve/integration_tests/__snapshots__/invalid_config.test.js.snap deleted file mode 100644 index 47b98f740af588..00000000000000 --- a/src/cli/serve/integration_tests/__snapshots__/invalid_config.test.js.snap +++ /dev/null @@ -1,18 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`cli invalid config support exits with statusCode 64 and logs a single line when config is invalid 1`] = ` -Array [ - Object { - "@timestamp": "## @timestamp ##", - "error": "## Error with stack trace ##", - "level": "fatal", - "message": "\\"unknown.key\\", \\"other.unknown.key\\", \\"other.third\\", \\"some.flat.key\\", and \\"some.array\\" settings were not applied. Check for spelling errors and ensure that expected plugins are installed.", - "pid": "## PID ##", - "tags": Array [ - "fatal", - "root", - ], - "type": "error", - }, -] -`; diff --git a/src/cli/serve/integration_tests/__snapshots__/reload_logging_config.test.js.snap b/src/cli/serve/integration_tests/__snapshots__/reload_logging_config.test.js.snap deleted file mode 100644 index 495ddab6aae6b0..00000000000000 --- a/src/cli/serve/integration_tests/__snapshots__/reload_logging_config.test.js.snap +++ /dev/null @@ -1,66 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Server logging configuration should be reloadable via SIGHUP process signaling 1`] = ` -Object { - "code": 0, - "lines": Array [ - Object { - "@timestamp": "## @timestamp ##", - "message": "Plugin initialization disabled.", - "pid": "## PID ##", - "tags": Array [ - "info", - ], - "type": "log", - }, - Object { - "@timestamp": "## @timestamp ##", - "message": "Server running at http://localhost:8274", - "pid": "## PID ##", - "tags": Array [ - "info", - "http", - "server", - "listening", - ], - "type": "log", - }, - Object { - "@timestamp": "## @timestamp ##", - "message": "Reloading logging configuration due to SIGHUP.", - "pid": "## PID ##", - "tags": Array [ - "info", - "config", - ], - "type": "log", - }, - Object { - "@timestamp": "## @timestamp ##", - "message": "New logging configuration: -{ - \\"ops\\": { - \\"interval\\": 5000 - }, - \\"logging\\": { - \\"json\\": false, - \\"silent\\": false, - \\"quiet\\": false, - \\"verbose\\": false, - \\"events\\": {}, - \\"dest\\": \\"stdout\\", - \\"filter\\": {}, - \\"useUTC\\": true - } -}", - "pid": "## PID ##", - "tags": Array [ - "info", - "config", - ], - "type": "log", - }, - " log [## timestamp ##] [info][config] Reloaded logging configuration due to SIGHUP.", - ], -} -`; diff --git a/src/cli/serve/integration_tests/invalid_config.test.js b/src/cli/serve/integration_tests/invalid_config.test.js deleted file mode 100644 index 495bfbeaa939e3..00000000000000 --- a/src/cli/serve/integration_tests/invalid_config.test.js +++ /dev/null @@ -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 { spawnSync } from 'child_process'; -import { resolve } from 'path'; - -const ROOT_DIR = resolve(__dirname, '../../../../'); -const INVALID_CONFIG_PATH = resolve(__dirname, '__fixtures__/invalid_config.yml'); - -describe('cli invalid config support', function () { - it('exits with statusCode 64 and logs a single line when config is invalid', function () { - const { error, status, stdout } = spawnSync(process.execPath, [ - 'src/cli', - '--config', INVALID_CONFIG_PATH - ], { - cwd: ROOT_DIR - }); - - const logLines = stdout.toString('utf8') - .split('\n') - .filter(Boolean) - .map(JSON.parse) - .map(obj => ({ - ...obj, - pid: '## PID ##', - '@timestamp': '## @timestamp ##', - error: '## Error with stack trace ##', - })); - - expect(error).toBe(undefined); - expect(status).toBe(64); - expect(logLines).toMatchSnapshot(); - }, 20 * 1000); -}); diff --git a/src/cli/serve/integration_tests/reload_logging_config.test.js b/src/cli/serve/integration_tests/reload_logging_config.test.js index 61e590f2b51d51..e69de29bb2d1d6 100644 --- a/src/cli/serve/integration_tests/reload_logging_config.test.js +++ b/src/cli/serve/integration_tests/reload_logging_config.test.js @@ -1,145 +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 { spawn } from 'child_process'; -import { writeFileSync } from 'fs'; -import { relative, resolve } from 'path'; -import { safeDump } from 'js-yaml'; -import es from 'event-stream'; -import stripAnsi from 'strip-ansi'; -import { getConfigFromFiles } from '../../../core/server/config'; - -const testConfigFile = follow('__fixtures__/reload_logging_config/kibana.test.yml'); -const kibanaPath = follow('../../../../scripts/kibana.js'); - -function follow(file) { - return relative(process.cwd(), resolve(__dirname, file)); -} - -function setLoggingJson(enabled) { - const conf = getConfigFromFiles([testConfigFile]); - conf.logging = conf.logging || {}; - conf.logging.json = enabled; - - const yaml = safeDump(conf); - - writeFileSync(testConfigFile, yaml); -} - -const prepareJson = obj => ({ - ...obj, - pid: '## PID ##', - '@timestamp': '## @timestamp ##' -}); - -const prepareLogLine = str => - stripAnsi(str.replace( - /\[\d{2}:\d{2}:\d{2}.\d{3}\]/, - '[## timestamp ##]' - )); - -describe.skip('Server logging configuration', function () { - let child; - let isJson; - - beforeEach(() => { - isJson = true; - setLoggingJson(true); - }); - - afterEach(() => { - isJson = true; - setLoggingJson(true); - - if (child !== undefined) { - child.kill(); - child = undefined; - } - }); - - const isWindows = /^win/.test(process.platform); - if (isWindows) { - it('SIGHUP is not a feature of Windows.', () => { - // nothing to do for Windows - }); - } else { - it('should be reloadable via SIGHUP process signaling', function (done) { - expect.assertions(1); - - child = spawn('node', [kibanaPath, '--config', testConfigFile]); - - child.on('error', err => { - done(new Error(`error in child process while attempting to reload config. ${err.stack || err.message || err}`)); - }); - - const lines = []; - - child.on('exit', _code => { - const code = _code === null ? 0 : _code; - - expect({ code, lines }).toMatchSnapshot(); - done(); - }); - - child.stdout - .pipe(es.split()) - .pipe(es.mapSync(line => { - if (!line) { - // skip empty lines - return; - } - - if (isJson) { - const data = JSON.parse(line); - lines.push(prepareJson(data)); - - if (data.tags.includes('listening')) { - switchToPlainTextLog(); - } - } else if (line.startsWith('{')) { - // We have told Kibana to stop logging json, but it hasn't completed - // the switch yet, so we verify the messages that are logged while - // switching over. - - const data = JSON.parse(line); - lines.push(prepareJson(data)); - } else { - // Kibana has successfully stopped logging json, so we verify the - // log line and kill the server. - - lines.push(prepareLogLine(line)); - - child.kill(); - child = undefined; - } - })); - - function switchToPlainTextLog() { - isJson = false; - setLoggingJson(false); - - // Reload logging config. We give it a little bit of time to just make - // sure the process sighup handler is registered. - setTimeout(() => { - child.kill('SIGHUP'); - }, 100); - } - }, 60000); - } -}); diff --git a/src/cli/serve/serve.js b/src/cli/serve/serve.js index 2820ac6a64ea41..e69de29bb2d1d6 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -1,226 +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 _ from 'lodash'; -import { statSync, lstatSync, realpathSync } from 'fs'; -import { resolve } from 'path'; - -import { fromRoot } from '../../utils'; -import { getConfig } from '../../server/path'; -import { bootstrap } from '../../core/server'; -import { readKeystore } from './read_keystore'; - -import { DEV_SSL_CERT_PATH, DEV_SSL_KEY_PATH } from '../dev_ssl'; - -function canRequire(path) { - try { - require.resolve(path); - return true; - } catch (error) { - if (error.code === 'MODULE_NOT_FOUND') { - return false; - } else { - throw error; - } - } -} - -function isSymlinkTo(link, dest) { - try { - const stat = lstatSync(link); - return stat.isSymbolicLink() && realpathSync(link) === dest; - } catch (error) { - if (error.code !== 'ENOENT') { - throw error; - } - } -} - -const CLUSTER_MANAGER_PATH = resolve(__dirname, '../cluster/cluster_manager'); -const CAN_CLUSTER = canRequire(CLUSTER_MANAGER_PATH); - -const REPL_PATH = resolve(__dirname, '../repl'); -const CAN_REPL = canRequire(REPL_PATH); - -// xpack is installed in both dev and the distributable, it's optional if -// install is a link to the source, not an actual install -const XPACK_INSTALLED_DIR = resolve(__dirname, '../../../node_modules/x-pack'); -const XPACK_SOURCE_DIR = resolve(__dirname, '../../../x-pack'); -const XPACK_INSTALLED = canRequire(XPACK_INSTALLED_DIR); -const XPACK_OPTIONAL = isSymlinkTo(XPACK_INSTALLED_DIR, XPACK_SOURCE_DIR); - -const pathCollector = function () { - const paths = []; - return function (path) { - paths.push(resolve(process.cwd(), path)); - return paths; - }; -}; - -const configPathCollector = pathCollector(); -const pluginDirCollector = pathCollector(); -const pluginPathCollector = pathCollector(); - -function applyConfigOverrides(rawConfig, opts, extraCliOptions) { - const set = _.partial(_.set, rawConfig); - const get = _.partial(_.get, rawConfig); - const has = _.partial(_.has, rawConfig); - const merge = _.partial(_.merge, rawConfig); - - if (opts.dev) { - set('env', 'development'); - set('optimize.watch', true); - - if (!has('elasticsearch.username')) { - set('elasticsearch.username', 'elastic'); - } - - if (!has('elasticsearch.password')) { - set('elasticsearch.password', 'changeme'); - } - - if (opts.ssl) { - set('server.ssl.enabled', true); - } - - if (opts.ssl && !has('server.ssl.certificate') && !has('server.ssl.key')) { - set('server.ssl.certificate', DEV_SSL_CERT_PATH); - set('server.ssl.key', DEV_SSL_KEY_PATH); - } - } - - if (opts.elasticsearch) set('elasticsearch.url', opts.elasticsearch); - if (opts.port) set('server.port', opts.port); - if (opts.host) set('server.host', opts.host); - if (opts.quiet) set('logging.quiet', true); - if (opts.silent) set('logging.silent', true); - if (opts.verbose) set('logging.verbose', true); - if (opts.logFile) set('logging.dest', opts.logFile); - - set('plugins.scanDirs', _.compact([].concat( - get('plugins.scanDirs'), - opts.pluginDir - ))); - - set('plugins.paths', _.compact([].concat( - get('plugins.paths'), - opts.pluginPath, - - XPACK_INSTALLED && (!XPACK_OPTIONAL || !opts.oss) - ? [XPACK_INSTALLED_DIR] - : [], - ))); - - merge(extraCliOptions); - merge(readKeystore(get('path.data'))); - - return rawConfig; -} - -export default function (program) { - const command = program.command('serve'); - - command - .description('Run the kibana server') - .collectUnknownOptions() - .option('-e, --elasticsearch ', 'Elasticsearch instance') - .option( - '-c, --config ', - 'Path to the config file, can be changed with the CONFIG_PATH environment variable as well. ' + - 'Use multiple --config args to include multiple config files.', - configPathCollector, - [ getConfig() ] - ) - .option('-p, --port ', 'The port to bind to', parseInt) - .option('-q, --quiet', 'Prevent all logging except errors') - .option('-Q, --silent', 'Prevent all logging') - .option('--verbose', 'Turns on verbose logging') - .option('-H, --host ', 'The host to bind to') - .option('-l, --log-file ', 'The file to log to') - .option( - '--plugin-dir ', - 'A path to scan for plugins, this can be specified multiple ' + - 'times to specify multiple directories', - pluginDirCollector, - [ - fromRoot('plugins'), - fromRoot('src/core_plugins') - ] - ) - .option( - '--plugin-path ', - 'A path to a plugin which should be included by the server, ' + - 'this can be specified multiple times to specify multiple paths', - pluginPathCollector, - [] - ) - .option('--plugins ', 'an alias for --plugin-dir', pluginDirCollector); - - if (CAN_REPL) { - command.option('--repl', 'Run the server with a REPL prompt and access to the server object'); - } - - if (XPACK_OPTIONAL) { - command - .option('--oss', 'Start Kibana without X-Pack'); - } - - if (CAN_CLUSTER) { - command - .option('--dev', 'Run the server with development mode defaults') - .option('--ssl', 'Run the dev server using HTTPS') - .option('--no-base-path', 'Don\'t put a proxy in front of the dev server, which adds a random basePath') - .option('--no-watch', 'Prevents automatic restarts of the server in --dev mode'); - } - - command - .action(async function (opts) { - if (opts.dev) { - try { - const kbnDevConfig = fromRoot('config/kibana.dev.yml'); - if (statSync(kbnDevConfig).isFile()) { - opts.config.push(kbnDevConfig); - } - } catch (err) { - // ignore, kibana.dev.yml does not exist - } - } - - const unknownOptions = this.getUnknownOptions(); - await bootstrap({ - configs: [].concat(opts.config || []), - cliArgs: { - dev: !!opts.dev, - envName: unknownOptions.env ? unknownOptions.env.name : undefined, - quiet: !!opts.quiet, - silent: !!opts.silent, - watch: !!opts.watch, - repl: !!opts.repl, - basePath: !!opts.basePath, - }, - features: { - isClusterModeSupported: CAN_CLUSTER, - isOssModeSupported: XPACK_OPTIONAL, - isXPackInstalled: XPACK_INSTALLED, - isReplModeSupported: CAN_REPL, - }, - applyConfigOverrides: rawConfig => applyConfigOverrides(rawConfig, opts, unknownOptions), - }); - }); -} diff --git a/src/cli_keystore/cli_keystore.js b/src/cli_keystore/cli_keystore.js index af67bf2638c670..e74a87e7daec9e 100644 --- a/src/cli_keystore/cli_keystore.js +++ b/src/cli_keystore/cli_keystore.js @@ -20,7 +20,7 @@ import { join } from 'path'; import { pkg } from '../utils'; -import Command from '../cli/command'; +import Command from '../legacy/cli/command'; import { getData } from '../server/path'; import { Keystore } from '../server/keystore'; diff --git a/src/cli_plugin/cli.js b/src/cli_plugin/cli.js index 223f8e6e4764c9..61f7b1e01d28c5 100644 --- a/src/cli_plugin/cli.js +++ b/src/cli_plugin/cli.js @@ -19,7 +19,7 @@ import _ from 'lodash'; import { pkg } from '../utils'; -import Command from '../cli/command'; +import Command from '../legacy/cli/command'; import listCommand from './list'; import installCommand from './install'; import removeCommand from './remove'; diff --git a/src/cli/serve/read_keystore.test.js b/src/core/cli/__tests__/read_keystore.test.ts similarity index 78% rename from src/cli/serve/read_keystore.test.js rename to src/core/cli/__tests__/read_keystore.test.ts index 0702df78132bf0..0d5ac91cd03bb6 100644 --- a/src/cli/serve/read_keystore.test.js +++ b/src/core/cli/__tests__/read_keystore.test.ts @@ -17,30 +17,31 @@ * under the License. */ -import path from 'path'; -import { readKeystore } from './read_keystore'; +jest.mock('../../../server/keystore'); -jest.mock('../../server/keystore'); -import { Keystore } from '../../server/keystore'; +import path from 'path'; +// @ts-ignore: implicit any for JS file +import { Keystore } from '../../../server/keystore'; +import { readKeystore } from '../read_keystore'; -describe('cli/serve/read_keystore', () => { +describe('core/cli/read_keystore', () => { beforeEach(() => { jest.resetAllMocks(); }); - it('returns structured keystore data', () => { + test('returns structured keystore data', () => { const keystoreData = { 'elasticsearch.password': 'changeme' }; Keystore.prototype.data = keystoreData; const data = readKeystore(); expect(data).toEqual({ elasticsearch: { - password: 'changeme' - } + password: 'changeme', + }, }); }); - it('uses data path provided', () => { + test('uses data path provided', () => { const keystoreDir = '/foo/'; const keystorePath = path.join(keystoreDir, 'kibana.keystore'); diff --git a/src/core/cli/apply_config_overrides.ts b/src/core/cli/apply_config_overrides.ts new file mode 100644 index 00000000000000..a559787ee80b33 --- /dev/null +++ b/src/core/cli/apply_config_overrides.ts @@ -0,0 +1,150 @@ +/* + * 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 { Config } from '../server/config'; +import * as args from './args'; +import { DEV_SSL_CERT_PATH, DEV_SSL_KEY_PATH } from './dev_ssl'; +import { KibanaFeatures, XPACK_INSTALLED_PATH } from './kibana_features'; +import { readKeystore } from './read_keystore'; + +/** + * Applies overrides to the config values supplied through command line and keystore. + * + * @param config `RawConfig` instance to update config values for. + * @param argv Argv object with key/value pairs. + * @param kibanaFeatures Features that are supported by current installation. + */ +export function applyConfigOverrides( + config: Config, + argv: { [key: string]: any }, + kibanaFeatures: KibanaFeatures +) { + const configOverrides = [ + ...getServerConfigOverrides(argv), + ...getDevConfigOverrides(argv), + ...getLoggingConfigOverrides(argv), + ...getPluginsConfigOverrides(config, argv, kibanaFeatures), + ...getUnknownArgsOverrides(argv, kibanaFeatures), + ...getKeystoreOverrides(config), + ]; + + for (const [key, value] of configOverrides) { + config.set(key, value); + } + + return config; +} + +function* getServerConfigOverrides(argv: { [key: string]: any }) { + if (argv.port != null) { + yield [['server', 'port'], argv.port]; + } + + if (argv.host != null) { + yield [['server', 'host'], argv.host]; + } + + if (argv.elasticsearch != null) { + yield [['elasticsearch', 'url'], argv.elasticsearch]; + } +} + +function* getDevConfigOverrides(argv: { [key: string]: any }) { + if (!argv.dev) { + return; + } + + yield ['env', 'development']; + yield [['optimize', 'watch'], true]; + + if (argv['elasticsearch.username'] == null) { + yield [['elasticsearch', 'username'], 'elastic']; + } + + if (argv['elasticsearch.password'] == null) { + yield [['elasticsearch', 'password'], 'changeme']; + } + + if (argv.ssl) { + yield [['server', 'ssl', 'enabled'], true]; + + if (argv['server.ssl.certificate'] == null && argv['server.ssl.key'] == null) { + yield [['server', 'certificate', 'enabled'], DEV_SSL_CERT_PATH]; + yield [['server', 'ssl', 'key'], DEV_SSL_KEY_PATH]; + } + } +} + +function* getLoggingConfigOverrides(argv: { [key: string]: any }) { + if (argv.quiet != null) { + yield [['logging', 'quiet'], argv.quiet]; + } + + if (argv.silent != null) { + yield [['logging', 'silent'], argv.silent]; + } + + if (argv.verbose != null) { + yield [['logging', 'verbose'], argv.verbose]; + } + + if (argv.logFile != null) { + yield [['logging', 'dest'], argv.logFile]; + } +} + +function* getPluginsConfigOverrides( + config: Config, + argv: { [key: string]: any }, + kibanaFeatures: KibanaFeatures +) { + yield [ + ['plugins', 'scanDirs'], + [...new Set([].concat(config.get(['plugins', 'scanDirs']), argv.pluginDir).filter(Boolean))], + ]; + + const xPackPluginPaths: string[] = + kibanaFeatures.isXPackInstalled && (!kibanaFeatures.isOssModeSupported || !argv.oss) + ? [XPACK_INSTALLED_PATH] + : []; + + yield [ + ['plugins', 'paths'], + [ + ...new Set( + xPackPluginPaths.concat(config.get(['plugins', 'paths']), argv.pluginPath).filter(Boolean) + ), + ], + ]; +} + +function* getUnknownArgsOverrides(argv: { [key: string]: any }, kibanaFeatures: KibanaFeatures) { + // Merge unknown CLI args into config. + for (const unknownArgKey of args.getUnknownOptions(argv, kibanaFeatures)) { + try { + yield [unknownArgKey, JSON.parse(argv[unknownArgKey])]; + } catch (e) { + yield [unknownArgKey, argv[unknownArgKey]]; + } + } +} + +function* getKeystoreOverrides(config: Config) { + yield* Object.entries(readKeystore(config.get(['path', 'data']))); +} diff --git a/src/core/cli/args.ts b/src/core/cli/args.ts new file mode 100644 index 00000000000000..08b19a70021e4f --- /dev/null +++ b/src/core/cli/args.ts @@ -0,0 +1,71 @@ +/* + * 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 chalk from 'chalk'; +import { getServeCommandOptions } from './commands'; +import { KibanaFeatures } from './kibana_features'; + +export const usage = 'Usage: bin/kibana [options]'; +export const description = + 'Kibana is an open source (Apache Licensed), browser-based analytics and search dashboard for Elasticsearch.'; +export const docs = 'Documentation: https://elastic.co/kibana'; + +function snakeToCamel(s: string) { + return s.replace(/(\-\w)/g, m => m[1].toUpperCase()); +} + +export const check = (options: { [key: string]: any }) => (argv: { [key: string]: any }) => { + // make sure only allowed options are specified + const yargsSpecialOptions = ['$0', '_', 'help', 'h', 'version', 'v']; + const allowedOptions = Object.keys(options).reduce( + (allowed, option) => + allowed + .add(option) + .add(snakeToCamel(option)) + .add(options[option].alias || option), + new Set(yargsSpecialOptions) + ); + const unrecognizedOptions = Object.keys(argv).filter(arg => !allowedOptions.has(arg)); + + if (unrecognizedOptions.length) { + throw new Error( + `The following options were not recognized:\n` + + ` ${chalk.bold(JSON.stringify(unrecognizedOptions))}` + ); + } + + return true; +}; + +export function getUnknownOptions(argv: { [key: string]: any }, kibanaFeatures: KibanaFeatures) { + const yargsSpecialOptions = ['$0', '_', 'help', 'h', 'version', 'v']; + const allowedOptions = new Set(yargsSpecialOptions); + + for (const [name, value] of getServeCommandOptions(kibanaFeatures)) { + allowedOptions.add(name).add(snakeToCamel(name)); + + if (Array.isArray(value.alias)) { + value.alias.forEach(alias => allowedOptions.add(alias)); + } else if (typeof value.alias === 'string') { + allowedOptions.add(value.alias); + } + } + + return Object.keys(argv).filter(arg => !allowedOptions.has(arg)); +} diff --git a/src/core/cli/cli.ts b/src/core/cli/cli.ts new file mode 100644 index 00000000000000..c2d5f2a2db44bb --- /dev/null +++ b/src/core/cli/cli.ts @@ -0,0 +1,63 @@ +/* + * 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 chalk from 'chalk'; +import yargs, { Arguments } from 'yargs'; + +import * as args from './args'; +import { createServeCommand } from './commands'; + +const onCliError = (reason?: Error | string) => { + if (reason !== undefined) { + // tslint:disable no-console + console.error(` +${chalk.white.bgRed(' CLI ERROR ')} ${reason} + +Specify --help for available options +`); + } + + process.exit(reason === undefined ? 0 : 64); +}; + +export const parseArgv = (argv: string[]): Arguments => { + return yargs(argv) + .usage(`${args.usage}\n\n${args.description}`) + .scriptName('') + .version() + .alias('h', 'help') + .alias('v', 'version') + .command(createServeCommand()) + .showHelpOnFail(false) + .fail((msg: string) => onCliError(msg)) + .help() + .epilogue(args.docs) + .wrap(yargs.terminalWidth()).argv; +}; + +export const run = () => { + const argv = process.env.kbnWorkerArgv ? JSON.parse(process.env.kbnWorkerArgv) : process.argv; + + const cliArgs = parseArgv(argv.slice(2)); + if (cliArgs._.length > 0) { + onCliError(`unknown command ${cliArgs._[0]}`); + } +}; + +run(); diff --git a/src/core/cli/commands/index.ts b/src/core/cli/commands/index.ts new file mode 100644 index 00000000000000..e158f378a3694c --- /dev/null +++ b/src/core/cli/commands/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { createServeCommand, getServeCommandOptions } from './serve/command'; diff --git a/src/core/cli/commands/serve/command.ts b/src/core/cli/commands/serve/command.ts new file mode 100644 index 00000000000000..603bccebd7eb41 --- /dev/null +++ b/src/core/cli/commands/serve/command.ts @@ -0,0 +1,254 @@ +/* + * 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 { accessSync, constants as fsConstants } from 'fs'; +import { resolve } from 'path'; +import typeDetect from 'type-detect'; +import { CommandModule, Options } from 'yargs'; +import { getConfig } from '../../../../server/path'; +import { fromRoot } from '../../../../utils/from_root'; +import { handler } from './handler'; + +import { detectKibanaFeatures, KibanaFeatures } from '../../kibana_features'; + +const { R_OK } = fsConstants; + +function resolvePath(path: string) { + return resolve(process.cwd(), path); +} + +const fileExists = (configPath: string): boolean => { + try { + accessSync(configPath, R_OK); + return true; + } catch (e) { + return false; + } +}; + +function ensureConfigExists(path: string) { + if (!fileExists(path)) { + throw new Error(`Config file [${path}] does not exist`); + } +} + +export function getServeCommandOptions(kibanaFeatures: KibanaFeatures) { + const options: Array<[string, Options]> = [ + [ + 'e', + { + alias: 'elasticsearch', + description: 'Elasticsearch instance', + requiresArg: true, + type: 'string', + }, + ], + [ + 'c', + { + alias: 'config', + coerce: arg => (typeof arg === 'string' ? resolvePath(arg) : arg), + default: [getConfig()], + description: + 'Path to the config file, can be changed with the CONFIG_PATH environment variable as well. ' + + 'Use multiple --config args to include multiple config files.', + requiresArg: true, + type: 'array', + }, + ], + [ + 'p', + { + alias: 'port', + description: 'The port to bind Kibana to', + requiresArg: true, + type: 'number', + }, + ], + [ + 'q', + { + alias: 'quiet', + description: 'Prevent all logging except errors', + type: 'boolean', + conflicts: ['Q', 'verbose'], + }, + ], + [ + 'Q', + { + alias: 'silent', + description: 'Prevent all logging except errors', + type: 'boolean', + conflicts: 'verbose', + }, + ], + [ + 'verbose', + { + description: 'Turns on verbose logging', + type: 'boolean', + }, + ], + [ + 'H', + { + alias: 'host', + description: 'The host to bind to', + requiresArg: true, + type: 'string', + }, + ], + [ + 'l', + { + alias: 'log-file', + description: 'The file to log to', + requiresArg: true, + type: 'string', + }, + ], + [ + 'plugin-dir', + { + alias: ['plugins'], + coerce: arg => arg.map((path: string) => resolvePath(path)), + default: [fromRoot('plugins'), fromRoot('src/core_plugins')], + description: + 'A path to scan for plugins, this can be specified multiple ' + + 'times to specify multiple directories', + requiresArg: true, + type: 'array', + }, + ], + [ + 'plugin-path', + { + coerce: arg => arg.map((path: string) => resolvePath(path)), + description: + 'A path to a plugin which should be included by the server, ' + + 'this can be specified multiple times to specify multiple paths', + type: 'array', + }, + ], + ]; + + if (kibanaFeatures.isReplModeSupported) { + options.push([ + 'repl', + { + description: 'Run the server with a REPL prompt and access to the server object', + type: 'boolean', + }, + ]); + } + + if (kibanaFeatures.isOssModeSupported) { + options.push([ + 'oss', + { + description: 'Start Kibana without X-Pack', + type: 'boolean', + }, + ]); + } + + if (kibanaFeatures.isClusterModeSupported) { + options.push( + [ + 'dev', + { + description: 'Run the server with development mode defaults', + type: 'boolean', + }, + ], + [ + 'ssl', + { + description: 'Run the dev server using HTTPS', + type: 'boolean', + }, + ], + [ + 'base-path', + { + description: + 'Put a proxy in front of the server in --dev mode, which adds a random basePath. Use --no-base-path to disable that behavior', + type: 'boolean', + default: true, + }, + ], + [ + 'watch', + { + default: true, + description: + 'Automatically restarts server in --dev mode. Use --no-watch to disable that behavior', + type: 'boolean', + }, + ] + ); + } + + return options; +} + +export function createServeCommand(): CommandModule { + const kibanaFeatures = detectKibanaFeatures(); + + return { + builder: yargs => { + for (const [name, value] of getServeCommandOptions(kibanaFeatures)) { + yargs.option(name, value); + } + + return yargs.check((argv: { [key: string]: any }) => { + // Ensure config files exists, + const configs: string[] = argv.config; + for (const config of configs) { + ensureConfigExists(config); + } + + if (argv.dev) { + try { + const kbnDevConfig = fromRoot('config/kibana.dev.yml'); + if (fileExists(kbnDevConfig)) { + argv.config.push(kbnDevConfig); + } + } catch (err) { + // ignore, kibana.dev.yml does not exist + } + } + + if (argv.repl && !kibanaFeatures.isReplModeSupported) { + throw new Error('[repl] Kibana REPL mode can only be run in development mode.'); + } + + if (argv.port !== undefined && isNaN(argv.port)) { + throw new Error(`[port] must be a number, but got ${typeDetect(argv.port)}`); + } + + return true; + }); + }, + command: ['serve', '$0'], + describe: 'Run the kibana server', + handler: cliArgs => handler(cliArgs, kibanaFeatures), + }; +} diff --git a/src/core/server/bootstrap.ts b/src/core/cli/commands/serve/handler.ts similarity index 59% rename from src/core/server/bootstrap.ts rename to src/core/cli/commands/serve/handler.ts index 69b1d751010c91..d9c8fd22736c28 100644 --- a/src/core/server/bootstrap.ts +++ b/src/core/cli/commands/serve/handler.ts @@ -19,53 +19,30 @@ import chalk from 'chalk'; import { isMaster } from 'cluster'; -import { CliArgs, Env, RawConfigService } from './config'; -import { LegacyObjectToConfigAdapter } from './legacy_compat'; -import { Root } from './root'; - -interface KibanaFeatures { - // Indicates whether we can run Kibana in a so called cluster mode in which - // Kibana is run as a "worker" process together with optimizer "worker" process - // that are orchestrated by the "master" process (dev mode only feature). - isClusterModeSupported: boolean; - - // Indicates whether we can run Kibana without X-Pack plugin pack even if it's - // installed (dev mode only feature). - isOssModeSupported: boolean; - - // Indicates whether we can run Kibana in REPL mode (dev mode only feature). - isReplModeSupported: boolean; - - // Indicates whether X-Pack plugin pack is installed and available. - isXPackInstalled: boolean; -} - -interface BootstrapArgs { - configs: string[]; - cliArgs: CliArgs; - applyConfigOverrides: (config: Record) => Record; - features: KibanaFeatures; -} - -export async function bootstrap({ - configs, - cliArgs, - applyConfigOverrides, - features, -}: BootstrapArgs) { - if (cliArgs.repl && !features.isReplModeSupported) { - onRootShutdown('Kibana REPL mode can only be run in development mode.'); - } - +import { Arguments } from 'yargs'; +import { Env, RawConfigService } from '../../../server/config'; +import { LegacyObjectToConfigAdapter } from '../../../server/legacy_compat'; +import { Root } from '../../../server/root'; +import { applyConfigOverrides } from '../../apply_config_overrides'; +import { KibanaFeatures } from '../../kibana_features'; + +export async function handler(cliArgs: Arguments, kibanaFeatures: KibanaFeatures) { const env = Env.createDefault({ - configs, - cliArgs, - isDevClusterMaster: isMaster && cliArgs.dev && features.isClusterModeSupported, + configs: [].concat(cliArgs.config || []), + cliArgs: { + dev: !!cliArgs.dev, + envName: cliArgs.env ? cliArgs.env.name : undefined, + quiet: !!cliArgs.quiet, + silent: !!cliArgs.silent, + watch: !!cliArgs.watch, + repl: !!cliArgs.repl, + basePath: !!cliArgs.basePath, + }, + isDevClusterMaster: isMaster && cliArgs.dev && kibanaFeatures.isClusterModeSupported, }); - const rawConfigService = new RawConfigService( - env.configs, - rawConfig => new LegacyObjectToConfigAdapter(applyConfigOverrides(rawConfig)) + const rawConfigService = new RawConfigService(env.configs, rawConfig => + applyConfigOverrides(new LegacyObjectToConfigAdapter(rawConfig), cliArgs, kibanaFeatures) ); rawConfigService.loadConfig(); diff --git a/src/cli/dev_ssl.js b/src/core/cli/dev_ssl.ts similarity index 100% rename from src/cli/dev_ssl.js rename to src/core/cli/dev_ssl.ts diff --git a/src/cli/index.js b/src/core/cli/index.js similarity index 96% rename from src/cli/index.js rename to src/core/cli/index.js index 4af5e3c68423cd..34250b66620b09 100644 --- a/src/cli/index.js +++ b/src/core/cli/index.js @@ -17,5 +17,5 @@ * under the License. */ -require('../setup_node_env'); +require('../../setup_node_env'); require('./cli'); diff --git a/src/core/cli/kibana_features.ts b/src/core/cli/kibana_features.ts new file mode 100644 index 00000000000000..e16a7e5ffecf3b --- /dev/null +++ b/src/core/cli/kibana_features.ts @@ -0,0 +1,72 @@ +/* + * 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 { lstatSync, realpathSync } from 'fs'; +import { resolve } from 'path'; + +const XPACK_SOURCE_PATH = resolve(__dirname, '../../../x-pack'); + +export const XPACK_INSTALLED_PATH = resolve(__dirname, '../../../node_modules/x-pack'); +export const CLUSTER_MANAGER_PATH = resolve(__dirname, '../../legacy/cli/cluster/cluster_manager'); +export const REPL_PATH = resolve(__dirname, '../../legacy/cli/repl'); + +function isSymlinkTo(link: string, dest: string) { + try { + const stat = lstatSync(link); + return stat.isSymbolicLink() && realpathSync(link) === dest; + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } + } +} + +function canRequire(path: string) { + try { + require.resolve(path); + return true; + } catch (error) { + if (error.code === 'MODULE_NOT_FOUND') { + return false; + } else { + throw error; + } + } +} + +export function detectKibanaFeatures() { + return { + // If we can access `cluster_manager.js` that means we can run Kibana in a so called cluster + // mode when Kibana is run as a "worker" process together with optimizer "worker" process. + isClusterModeSupported: canRequire(CLUSTER_MANAGER_PATH), + + // X-Pack is installed in both dev and the distributable, it's optional if + // install is a link to the source, not an actual install. + isOssModeSupported: isSymlinkTo(XPACK_INSTALLED_PATH, XPACK_SOURCE_PATH), + + // If we can access `repl/` that means we can run Kibana in REPL mode. + isReplModeSupported: canRequire(REPL_PATH), + + // X-Pack is considered as installed if it's available in `node_modules` folder and it + // looks the same for both dev and the distributable. + isXPackInstalled: canRequire(XPACK_INSTALLED_PATH), + }; +} + +export type KibanaFeatures = ReturnType; diff --git a/src/cli/serve/read_keystore.js b/src/core/cli/read_keystore.ts similarity index 91% rename from src/cli/serve/read_keystore.js rename to src/core/cli/read_keystore.ts index feefce22ab2c2f..52cfe71fb8427d 100644 --- a/src/cli/serve/read_keystore.js +++ b/src/core/cli/read_keystore.ts @@ -17,13 +17,14 @@ * under the License. */ -import path from 'path'; import { set } from 'lodash'; +import path from 'path'; +// @ts-ignore: implicit any for JS file import { Keystore } from '../../server/keystore'; import { getData } from '../../server/path'; -export function readKeystore(dataPath = getData()) { +export function readKeystore(dataPath = getData()): Record { const keystore = new Keystore(path.join(dataPath, 'kibana.keystore')); keystore.load(); diff --git a/src/core/server/index.ts b/src/core/server/index.ts index ac645b22800417..846caebc8b3841 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -17,8 +17,6 @@ * under the License. */ -export { bootstrap } from './bootstrap'; - import { first } from 'rxjs/operators'; import { ConfigService, Env } from './config'; import { HttpConfig, HttpModule, HttpServerInfo } from './http'; diff --git a/src/core/server/legacy_compat/__tests__/legacy_service.test.ts b/src/core/server/legacy_compat/__tests__/legacy_service.test.ts index dc16709861084a..124a6f6abc2d0a 100644 --- a/src/core/server/legacy_compat/__tests__/legacy_service.test.ts +++ b/src/core/server/legacy_compat/__tests__/legacy_service.test.ts @@ -21,11 +21,11 @@ import { BehaviorSubject, Subject, throwError } from 'rxjs'; jest.mock('../legacy_platform_proxy'); jest.mock('../../../../server/kbn_server'); -jest.mock('../../../../cli/cluster/cluster_manager'); +jest.mock('../../../../legacy/cli/cluster/cluster_manager'); import { first } from 'rxjs/operators'; // @ts-ignore: implicit any for JS file -import MockClusterManager from '../../../../cli/cluster/cluster_manager'; +import MockClusterManager from '../../../../legacy/cli/cluster/cluster_manager'; // @ts-ignore: implicit any for JS file import MockKbnServer from '../../../../server/kbn_server'; import { Config, ConfigService, Env, ObjectToConfigAdapter } from '../../config'; diff --git a/src/core/server/legacy_compat/legacy_service.ts b/src/core/server/legacy_compat/legacy_service.ts index 092057874fa737..d9b131ee088f48 100644 --- a/src/core/server/legacy_compat/legacy_service.ts +++ b/src/core/server/legacy_compat/legacy_service.ts @@ -105,7 +105,7 @@ export class LegacyService implements CoreService { ) : EMPTY; - require('../../../cli/cluster/cluster_manager').create( + require('../../../legacy/cli/cluster/cluster_manager').create( this.env.cliArgs, config.toRaw(), await basePathProxy$.toPromise() @@ -133,7 +133,7 @@ export class LegacyService implements CoreService { // from being started multiple times in different processes. // We only want one REPL. if (this.env.cliArgs.repl && process.env.kbnWorkerType === 'server') { - require('../../../cli/repl').startRepl(kbnServer); + require('../../../legacy/cli/repl').startRepl(kbnServer); } const httpConfig = await this.configService diff --git a/src/dev/build/tasks/create_package_json_task.js b/src/dev/build/tasks/create_package_json_task.js index 59d2e7f6595820..c9b4a4e70a09e8 100644 --- a/src/dev/build/tasks/create_package_json_task.js +++ b/src/dev/build/tasks/create_package_json_task.js @@ -43,6 +43,7 @@ export const CreatePackageJsonTask = { node: pkg.engines.node, }, dependencies: transformDependencies(pkg.dependencies), + yargs: pkg.yargs, }; if (build.isOss()) { diff --git a/src/dev/jest/config.js b/src/dev/jest/config.js index 99b22b14c2d711..d612344a4fa240 100644 --- a/src/dev/jest/config.js +++ b/src/dev/jest/config.js @@ -24,7 +24,7 @@ export default { '/src/core', '/src/core_plugins', '/src/server', - '/src/cli', + '/src/legacy/cli', '/src/cli_keystore', '/src/cli_plugin', '/src/dev', diff --git a/src/cli/cluster/__mocks__/cluster.js b/src/legacy/cli/cluster/__mocks__/cluster.js similarity index 100% rename from src/cli/cluster/__mocks__/cluster.js rename to src/legacy/cli/cluster/__mocks__/cluster.js diff --git a/src/cli/cluster/cluster_manager.js b/src/legacy/cli/cluster/cluster_manager.js similarity index 97% rename from src/cli/cluster/cluster_manager.js rename to src/legacy/cli/cluster/cluster_manager.js index 1ea8a91eb21ef6..8f2c67a5e338ee 100644 --- a/src/cli/cluster/cluster_manager.js +++ b/src/legacy/cli/cluster/cluster_manager.js @@ -24,8 +24,8 @@ import { first } from 'rxjs/operators'; import Log from '../log'; import Worker from './worker'; -import { Config } from '../../server/config/config'; -import { transformDeprecations } from '../../server/config/transform_deprecations'; +import { Config } from '../../../server/config/config'; +import { transformDeprecations } from '../../../server/config/transform_deprecations'; process.env.kbnWorkerType = 'managr'; @@ -130,7 +130,7 @@ export default class ClusterManager { setupWatching(extraPaths, extraIgnores) { const chokidar = require('chokidar'); - const { fromRoot } = require('../../utils'); + const { fromRoot } = require('../../../utils'); const watchPaths = [ fromRoot('src/core_plugins'), diff --git a/src/cli/cluster/cluster_manager.test.js b/src/legacy/cli/cluster/cluster_manager.test.js similarity index 100% rename from src/cli/cluster/cluster_manager.test.js rename to src/legacy/cli/cluster/cluster_manager.test.js diff --git a/src/cli/cluster/worker.js b/src/legacy/cli/cluster/worker.js similarity index 91% rename from src/cli/cluster/worker.js rename to src/legacy/cli/cluster/worker.js index a7176bc0c734cb..4a88b0fe39fd07 100644 --- a/src/cli/cluster/worker.js +++ b/src/legacy/cli/cluster/worker.js @@ -21,10 +21,17 @@ import _ from 'lodash'; import cluster from 'cluster'; import { EventEmitter } from 'events'; -import { BinderFor, fromRoot } from '../../utils'; - -const cliPath = fromRoot('src/cli'); -const baseArgs = _.difference(process.argv.slice(2), ['--no-watch']); +import { BinderFor, fromRoot } from '../../../utils'; + +const cliPath = fromRoot('src/core/cli'); +const baseArgs = process.argv.slice(2).filter((arg) => { + // The following arguments should be consumed by main process only, + // workers will receive these only from cluster manager if needed. + return arg !== 'no-watch' + && !arg.startsWith('--server.port') + && !arg.startsWith('--server.basePath') + && !arg.startsWith('--server.rewriteBasePath'); +}); const baseArgv = [process.execPath, cliPath].concat(baseArgs); cluster.setupMaster({ diff --git a/src/cli/cluster/worker.test.js b/src/legacy/cli/cluster/worker.test.js similarity index 100% rename from src/cli/cluster/worker.test.js rename to src/legacy/cli/cluster/worker.test.js diff --git a/src/cli/color.js b/src/legacy/cli/color.js similarity index 100% rename from src/cli/color.js rename to src/legacy/cli/color.js diff --git a/src/cli/command.js b/src/legacy/cli/command.js similarity index 100% rename from src/cli/command.js rename to src/legacy/cli/command.js diff --git a/src/cli/help.js b/src/legacy/cli/help.js similarity index 100% rename from src/cli/help.js rename to src/legacy/cli/help.js diff --git a/src/cli/log.js b/src/legacy/cli/log.js similarity index 100% rename from src/cli/log.js rename to src/legacy/cli/log.js diff --git a/src/cli/repl/__snapshots__/repl.test.js.snap b/src/legacy/cli/repl/__snapshots__/repl.test.js.snap similarity index 100% rename from src/cli/repl/__snapshots__/repl.test.js.snap rename to src/legacy/cli/repl/__snapshots__/repl.test.js.snap diff --git a/src/cli/repl/index.js b/src/legacy/cli/repl/index.js similarity index 100% rename from src/cli/repl/index.js rename to src/legacy/cli/repl/index.js diff --git a/src/cli/repl/repl.test.js b/src/legacy/cli/repl/repl.test.js similarity index 100% rename from src/cli/repl/repl.test.js rename to src/legacy/cli/repl/repl.test.js diff --git a/src/server/path/index.test.js b/src/server/path/index.test.ts similarity index 89% rename from src/server/path/index.test.js rename to src/server/path/index.test.ts index 08d3568cfbbadb..4d06db15bdb078 100644 --- a/src/server/path/index.test.js +++ b/src/server/path/index.test.ts @@ -17,10 +17,12 @@ * under the License. */ +import { accessSync, constants as fsConstants } from 'fs'; import { getConfig, getData } from './'; -import { accessSync, R_OK } from 'fs'; -describe('Default path finder', function () { +const { R_OK } = fsConstants; + +describe('Default path finder', () => { it('should find a kibana.yml', () => { const configPath = getConfig(); expect(() => accessSync(configPath, R_OK)).not.toThrow(); diff --git a/src/server/path/index.js b/src/server/path/index.ts similarity index 71% rename from src/server/path/index.js rename to src/server/path/index.ts index d74bca3a200f1c..4bd1db0ba809b5 100644 --- a/src/server/path/index.js +++ b/src/server/path/index.ts @@ -17,29 +17,29 @@ * under the License. */ -import { accessSync, R_OK } from 'fs'; -import { find } from 'lodash'; -import { fromRoot } from '../../utils'; +import { accessSync, constants as fsConstants } from 'fs'; +import { fromRoot } from '../../utils/from_root'; + +const { R_OK } = fsConstants; const CONFIG_PATHS = [ - process.env.CONFIG_PATH, + process.env.CONFIG_PATH || '', fromRoot('config/kibana.yml'), - '/etc/kibana/kibana.yml' + '/etc/kibana/kibana.yml', ].filter(Boolean); -const DATA_PATHS = [ - process.env.DATA_PATH, - fromRoot('data'), - '/var/lib/kibana' -].filter(Boolean); +const DATA_PATHS = [process.env.DATA_PATH || '', fromRoot('data'), '/var/lib/kibana'].filter( + Boolean +); -function findFile(paths) { - const availablePath = find(paths, configPath => { +function findFile(paths: string[]) { + const availablePath = paths.find(configPath => { try { accessSync(configPath, R_OK); return true; } catch (e) { - //Check the next path + // Check the next path + return false; } }); return availablePath || paths[0]; diff --git a/src/utils/from_root.js b/src/utils/from_root.ts similarity index 95% rename from src/utils/from_root.js rename to src/utils/from_root.ts index 2d85b8f1dab38d..b13941d3fd4d72 100644 --- a/src/utils/from_root.js +++ b/src/utils/from_root.ts @@ -17,9 +17,9 @@ * under the License. */ -import { pkg } from './package_json'; import { resolve } from 'path'; +import { pkg } from './package_json'; -export function fromRoot(...args) { +export function fromRoot(...args: string[]) { return resolve(pkg.__dirname, ...args); } diff --git a/x-pack/package.json b/x-pack/package.json index c2bd527701fed4..9ca2706a33b794 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -37,7 +37,7 @@ "chalk": "^2.4.1", "chance": "1.0.10", "checksum": "0.1.1", - "commander": "2.12.2", + "commander": "^2.15.1", "del": "^3.0.0", "dotenv": "2.0.0", "enzyme": "3.2.0", diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index 7c73f4699a7ec7..6021c7054f6688 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -1519,10 +1519,6 @@ commander@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06" -commander@2.12.2, commander@^2.9.0: - version "2.12.2" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555" - commander@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873" @@ -1533,6 +1529,14 @@ commander@2.9.0: dependencies: graceful-readlink ">= 1.0.0" +commander@^2.15.1: + version "2.17.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" + +commander@^2.9.0: + version "2.12.2" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555" + component-emitter@^1.2.0, component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" diff --git a/yarn.lock b/yarn.lock index 8c22fee2635e78..cc9dfc94742d64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -378,6 +378,12 @@ "@types/podium" "*" "@types/shot" "*" +"@types/hapi@^13.0.38": + version "13.0.38" + resolved "https://registry.yarnpkg.com/@types/hapi/-/hapi-13.0.38.tgz#3671ab29fd465d61e394718ce546b7d5ef21a331" + dependencies: + "@types/node" "*" + "@types/has-ansi@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/has-ansi/-/has-ansi-3.0.0.tgz#636403dc4e0b2649421c4158e5c404416f3f0330" @@ -558,6 +564,10 @@ "@types/events" "*" "@types/node" "*" +"@types/yargs@^11.1.0": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-11.1.1.tgz#2e724257167fd6b615dbe4e54301e65fe597433f" + abab@^1.0.0, abab@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e"