mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Upgrade Hapi in legacy platform to v17 (#21707)
* Disable even-better monitoring * Upgrade to Hapi v15 * Upgrade to Hapi v16 * Handle optional req params correctly * Update http and kbnServer * Get mocha tests passing * Convert `reply` usages [wip] * Fix Joi and Plugin incompatibilities * Get server up and running * Get basic logging working * Fix optimizer * Fix recent route handlers * Various fixes * Fix recent routes * Upgrade wreck for async/await * Fix mocha tests * Fix joi issues * Fix xpack jest tests * Fix recent routes * Fix tests * Fix index setup * Decouple monitoring stats collection from good plugin * Update reload logging test to work * Reimplement logging with updated good plugin * Fix unit tests * Fix getConnections back * Make LegacyLoggingServer compatible with Hapi v17 * Update joi types * Fix x-pack unit tests * Remove stray debugger * Remove hapi-compat * Fix API integrations * Upgrade boom * Fix security plugin * Misc fixes * bump * Fix licensePreRoutingFactory * Fix failing integration tests * Remove unnecessary test change * Remove hapi-latest package * fx * Various cleanup * Fix race condition in oppsy events * Use elastic/good fork * Fix boom.wrap and hapi-latest changes * Simplify LegacyLoggingServer updates * package.json cleanup + test fix * yarn.lock cleanup * Change good tag * Fixes * Change return err -> throw err in routes * Fix await returns * Fix new load_data test * Make cookie security flags consistent * tmp doc * Fix types * Fix tests * Upgrade canvas plugin * Move good package to published @elastic/good one * Fix SO test * Fix logging reloading * Update APM apis * Fix error logging * Fix logging test * Convert spaces plugin * Add validation error shim * Remove 7.0 release notes * Await renderApp * Fix ccr routes * Prevent header popovers from scrolling with page content (#23850) * Fix spaces test * new yarn.lock-s * Fix spaces tests * Remove h2o2-latest * Fix @types/hapi * Upgrade InfraOps plugin * Fix package.json * Add back isSameSite: false * Upgrade beats_management plugin * Update snapshot * Fix InfraOps * Upgrade kql_telemetry * Merge upstream/master * Upgrade apm and ml * Put snapshot test back * Fx beats * Upgrade rollups * Update boom usages in new plugins
This commit is contained in:
parent
ab776d4577
commit
27e5406d7a
361 changed files with 3023 additions and 3486 deletions
|
@ -55,8 +55,8 @@ Append `request.getBasePath()` to any absolute URL path.
|
|||
const basePath = server.config().get('server.basePath');
|
||||
server.route({
|
||||
path: '/redirect',
|
||||
handler(request, reply) {
|
||||
reply.redirect(`${request.getBasePath()}/otherLocation`);
|
||||
handler(request, h) {
|
||||
return h.redirect(`${request.getBasePath()}/otherLocation`);
|
||||
}
|
||||
});
|
||||
-----------
|
||||
|
@ -84,4 +84,4 @@ or `yarn start`.
|
|||
["source","shell"]
|
||||
-----------
|
||||
yarn start --no-base-path
|
||||
-----------
|
||||
-----------
|
||||
|
|
25
package.json
25
package.json
|
@ -68,6 +68,7 @@
|
|||
"dependencies": {
|
||||
"@elastic/eui": "4.5.1",
|
||||
"@elastic/filesaver": "1.1.2",
|
||||
"@elastic/good": "8.1.1-kibana1",
|
||||
"@elastic/numeral": "2.3.2",
|
||||
"@elastic/ui-ace": "0.2.3",
|
||||
"@kbn/babel-preset": "link:packages/kbn-babel-preset",
|
||||
|
@ -92,7 +93,7 @@
|
|||
"babel-polyfill": "6.20.0",
|
||||
"babel-register": "6.18.0",
|
||||
"bluebird": "2.9.34",
|
||||
"boom": "5.2.0",
|
||||
"boom": "^7.2.0",
|
||||
"brace": "0.11.1",
|
||||
"cache-loader": "1.0.3",
|
||||
"chalk": "^2.4.1",
|
||||
|
@ -108,7 +109,6 @@
|
|||
"elasticsearch": "^15.1.1",
|
||||
"elasticsearch-browser": "^15.1.1",
|
||||
"encode-uri-query": "1.0.0",
|
||||
"even-better": "7.0.2",
|
||||
"execa": "^0.10.0",
|
||||
"expiry-js": "0.1.7",
|
||||
"extract-text-webpack-plugin": "3.0.1",
|
||||
|
@ -118,16 +118,15 @@
|
|||
"glob": "^7.1.2",
|
||||
"glob-all": "^3.1.0",
|
||||
"good-squeeze": "2.1.0",
|
||||
"h2o2": "5.1.1",
|
||||
"h2o2-latest": "npm:h2o2@8.1.2",
|
||||
"h2o2": "^8.1.2",
|
||||
"handlebars": "4.0.5",
|
||||
"hapi": "14.2.0",
|
||||
"hapi-latest": "npm:hapi@17.5.0",
|
||||
"hapi": "^17.5.3",
|
||||
"hjson": "3.1.0",
|
||||
"hoek": "^5.0.4",
|
||||
"http-proxy-agent": "^2.1.0",
|
||||
"https-proxy-agent": "^2.2.1",
|
||||
"inert": "4.0.2",
|
||||
"joi": "10.4.1",
|
||||
"inert": "^5.1.0",
|
||||
"joi": "^13.5.2",
|
||||
"jquery": "^3.3.1",
|
||||
"js-yaml": "3.4.1",
|
||||
"json-stringify-pretty-compact": "1.0.4",
|
||||
|
@ -151,6 +150,7 @@
|
|||
"ngreact": "0.5.1",
|
||||
"no-ui-slider": "1.2.0",
|
||||
"node-fetch": "1.3.2",
|
||||
"oppsy": "^2.0.0",
|
||||
"pegjs": "0.9.0",
|
||||
"postcss-loader": "2.0.6",
|
||||
"prop-types": "15.5.8",
|
||||
|
@ -203,11 +203,11 @@
|
|||
"vega-lite": "^2.4.0",
|
||||
"vega-schema-url-parser": "1.0.0",
|
||||
"vega-tooltip": "^0.9.14",
|
||||
"vision": "4.1.0",
|
||||
"vision": "^5.3.3",
|
||||
"webpack": "3.6.0",
|
||||
"webpack-merge": "4.1.0",
|
||||
"whatwg-fetch": "^2.0.3",
|
||||
"wreck": "12.4.0",
|
||||
"wreck": "^14.0.2",
|
||||
"x-pack": "link:x-pack",
|
||||
"yauzl": "2.7.0"
|
||||
},
|
||||
|
@ -238,10 +238,11 @@
|
|||
"@types/fetch-mock": "^5.12.2",
|
||||
"@types/getopts": "^2.0.0",
|
||||
"@types/glob": "^5.0.35",
|
||||
"@types/hapi-latest": "npm:@types/hapi@17.0.12",
|
||||
"@types/hapi": "^17.0.18",
|
||||
"@types/has-ansi": "^3.0.0",
|
||||
"@types/hoek": "^4.1.3",
|
||||
"@types/jest": "^23.3.1",
|
||||
"@types/joi": "^10.4.4",
|
||||
"@types/joi": "^13.4.2",
|
||||
"@types/jquery": "^3.3.6",
|
||||
"@types/js-yaml": "^3.11.1",
|
||||
"@types/listr": "^0.13.0",
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import * as Joi from 'joi';
|
||||
import Joi from 'joi';
|
||||
import {
|
||||
AnySchema,
|
||||
JoiRoot,
|
||||
|
|
|
@ -101,12 +101,7 @@ export abstract class Type<V> {
|
|||
return error;
|
||||
}
|
||||
|
||||
const { context = {}, type, path: rawPath, message } = error;
|
||||
|
||||
// Before v11.0.0 Joi reported paths as `.`-delimited strings, but more
|
||||
// recent version use arrays instead. Once we upgrade Joi, we should just
|
||||
// remove this split logic and use `path` provided by Joi directly.
|
||||
const path = rawPath ? rawPath.split('.') : [];
|
||||
const { context = {}, type, path, message } = error;
|
||||
|
||||
const errorHandleResult = this.handleError(type, context, path);
|
||||
if (errorHandleResult instanceof SchemaTypeError) {
|
||||
|
|
|
@ -3,8 +3,8 @@ export default function (server) {
|
|||
server.route({
|
||||
path: '/api/<%= name %>/example',
|
||||
method: 'GET',
|
||||
handler(req, reply) {
|
||||
reply({ time: (new Date()).toISOString() });
|
||||
handler() {
|
||||
return { time: (new Date()).toISOString() };
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,76 +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": "The elasticsearch plugin is disabled. Skipping migrations.",
|
||||
"pid": "## PID ##",
|
||||
"tags": Array [
|
||||
"warning",
|
||||
"migration",
|
||||
],
|
||||
"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.",
|
||||
],
|
||||
}
|
||||
`;
|
|
@ -22,8 +22,7 @@ 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';
|
||||
import { getConfigFromFiles } from '../../../core/server/config/read_config';
|
||||
|
||||
const testConfigFile = follow('__fixtures__/reload_logging_config/kibana.test.yml');
|
||||
const kibanaPath = follow('../../../../scripts/kibana.js');
|
||||
|
@ -42,19 +41,7 @@ function setLoggingJson(enabled) {
|
|||
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 () {
|
||||
describe('Server logging configuration', function () {
|
||||
let child;
|
||||
let isJson;
|
||||
|
||||
|
@ -80,7 +67,7 @@ describe.skip('Server logging configuration', function () {
|
|||
});
|
||||
} else {
|
||||
it('should be reloadable via SIGHUP process signaling', function (done) {
|
||||
expect.assertions(1);
|
||||
expect.assertions(3);
|
||||
|
||||
child = spawn('node', [kibanaPath, '--config', testConfigFile]);
|
||||
|
||||
|
@ -88,12 +75,15 @@ describe.skip('Server logging configuration', function () {
|
|||
done(new Error(`error in child process while attempting to reload config. ${err.stack || err.message || err}`));
|
||||
});
|
||||
|
||||
const lines = [];
|
||||
let sawJson = false;
|
||||
let sawNonjson = false;
|
||||
|
||||
child.on('exit', _code => {
|
||||
const code = _code === null ? 0 : _code;
|
||||
|
||||
expect({ code, lines }).toMatchSnapshot();
|
||||
expect(code).toEqual(0);
|
||||
expect(sawJson).toEqual(true);
|
||||
expect(sawNonjson).toEqual(true);
|
||||
done();
|
||||
});
|
||||
|
||||
|
@ -107,23 +97,18 @@ describe.skip('Server logging configuration', function () {
|
|||
|
||||
if (isJson) {
|
||||
const data = JSON.parse(line);
|
||||
lines.push(prepareJson(data));
|
||||
sawJson = true;
|
||||
|
||||
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));
|
||||
// the switch yet, so we ignore before switching over.
|
||||
} else {
|
||||
// Kibana has successfully stopped logging json, so we verify the
|
||||
// log line and kill the server.
|
||||
// Kibana has successfully stopped logging json, so kill the server.
|
||||
|
||||
lines.push(prepareLogLine(line));
|
||||
sawNonjson = true;
|
||||
|
||||
child.kill();
|
||||
child = undefined;
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
import Wreck from 'wreck';
|
||||
import Progress from '../progress';
|
||||
import { fromNode as fn } from 'bluebird';
|
||||
import { createWriteStream } from 'fs';
|
||||
import HttpProxyAgent from 'http-proxy-agent';
|
||||
import HttpsProxyAgent from 'https-proxy-agent';
|
||||
|
@ -41,32 +40,31 @@ function getProxyAgent(sourceUrl, logger) {
|
|||
}
|
||||
}
|
||||
|
||||
function sendRequest({ sourceUrl, timeout }, logger) {
|
||||
async function sendRequest({ sourceUrl, timeout }, logger) {
|
||||
const maxRedirects = 11; //Because this one goes to 11.
|
||||
return fn(cb => {
|
||||
const reqOptions = { timeout, redirects: maxRedirects };
|
||||
const proxyAgent = getProxyAgent(sourceUrl, logger);
|
||||
const reqOptions = { timeout, redirects: maxRedirects };
|
||||
const proxyAgent = getProxyAgent(sourceUrl, logger);
|
||||
|
||||
if (proxyAgent) {
|
||||
reqOptions.agent = proxyAgent;
|
||||
if (proxyAgent) {
|
||||
reqOptions.agent = proxyAgent;
|
||||
}
|
||||
|
||||
try {
|
||||
const promise = Wreck.request('GET', sourceUrl, reqOptions);
|
||||
const req = promise.req;
|
||||
const resp = await promise;
|
||||
if (resp.statusCode >= 400) {
|
||||
throw new Error('ENOTFOUND');
|
||||
}
|
||||
|
||||
const req = Wreck.request('GET', sourceUrl, reqOptions, (err, resp) => {
|
||||
if (err) {
|
||||
if (err.code === 'ECONNREFUSED') {
|
||||
err = new Error('ENOTFOUND');
|
||||
}
|
||||
return { req, resp };
|
||||
} catch (err) {
|
||||
if (err.code === 'ECONNREFUSED') {
|
||||
err = new Error('ENOTFOUND');
|
||||
}
|
||||
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
if (resp.statusCode >= 400) {
|
||||
return cb(new Error('ENOTFOUND'));
|
||||
}
|
||||
|
||||
cb(null, { req, resp });
|
||||
});
|
||||
});
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function downloadResponse({ resp, targetPath, progress }) {
|
||||
|
|
|
@ -10,12 +10,15 @@ Object {
|
|||
"maxBytes": 1024,
|
||||
},
|
||||
"validate": Object {
|
||||
"failAction": [Function],
|
||||
"options": Object {
|
||||
"abortEarly": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
"state": Object {
|
||||
"isHttpOnly": true,
|
||||
"isSameSite": false,
|
||||
"strictHeader": false,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { ByteSizeValue } from '@kbn/config-schema';
|
||||
import { Server } from 'hapi-latest';
|
||||
import { Server } from 'hapi';
|
||||
import { Agent as HttpsAgent, ServerOptions as TlsOptions } from 'https';
|
||||
import { sample } from 'lodash';
|
||||
import { DevConfig } from '../dev';
|
||||
|
@ -66,7 +66,7 @@ export class BasePathProxyServer {
|
|||
|
||||
// Register hapi plugin that adds proxying functionality. It can be configured
|
||||
// through the route configuration object (see { handler: { proxy: ... } }).
|
||||
await this.server.register({ plugin: require('h2o2-latest') });
|
||||
await this.server.register({ plugin: require('h2o2') });
|
||||
|
||||
if (this.httpConfig.ssl.enabled) {
|
||||
const tlsOptions = serverOptions.tls as TlsOptions;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Server, ServerOptions } from 'hapi-latest';
|
||||
import { Server, ServerOptions } from 'hapi';
|
||||
|
||||
import { modifyUrl } from '../../utils';
|
||||
import { Logger } from '../logging';
|
||||
|
|
59
src/core/server/http/http_tools.test.ts
Normal file
59
src/core/server/http/http_tools.test.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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 { Request, ResponseToolkit } from 'hapi';
|
||||
import Joi from 'joi';
|
||||
import { defaultValidationErrorHandler, HapiValidationError } from './http_tools';
|
||||
|
||||
const emptyOutput = {
|
||||
statusCode: 400,
|
||||
headers: {},
|
||||
payload: {
|
||||
statusCode: 400,
|
||||
error: '',
|
||||
validation: {
|
||||
source: '',
|
||||
keys: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('defaultValidationErrorHandler', () => {
|
||||
it('formats value validation errors correctly', () => {
|
||||
expect.assertions(1);
|
||||
const schema = Joi.array().items(
|
||||
Joi.object({
|
||||
type: Joi.string().required(),
|
||||
}).required()
|
||||
);
|
||||
|
||||
const error = schema.validate([{}], { abortEarly: false }).error as HapiValidationError;
|
||||
|
||||
// Emulate what Hapi v17 does by default
|
||||
error.output = { ...emptyOutput };
|
||||
error.output.payload.validation.keys = ['0.type', ''];
|
||||
|
||||
try {
|
||||
defaultValidationErrorHandler({} as Request, {} as ResponseToolkit, error);
|
||||
} catch (err) {
|
||||
// Verify the empty string gets corrected to 'value'
|
||||
expect(err.output.payload.validation.keys).toEqual(['0.type', 'value']);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -18,8 +18,10 @@
|
|||
*/
|
||||
|
||||
import { readFileSync } from 'fs';
|
||||
import { Server, ServerOptions } from 'hapi-latest';
|
||||
import { Lifecycle, Request, ResponseToolkit, Server, ServerOptions, Util } from 'hapi';
|
||||
import Hoek from 'hoek';
|
||||
import { ServerOptions as TLSOptions } from 'https';
|
||||
import { ValidationError } from 'joi';
|
||||
import { HttpConfig } from './http_config';
|
||||
|
||||
/**
|
||||
|
@ -39,6 +41,7 @@ export function getServerOptions(config: HttpConfig, { configureTLS = true } = {
|
|||
maxBytes: config.maxPayload.getValueInBytes(),
|
||||
},
|
||||
validate: {
|
||||
failAction: defaultValidationErrorHandler,
|
||||
options: {
|
||||
abortEarly: false,
|
||||
},
|
||||
|
@ -46,6 +49,8 @@ export function getServerOptions(config: HttpConfig, { configureTLS = true } = {
|
|||
},
|
||||
state: {
|
||||
strictHeader: false,
|
||||
isHttpOnly: true,
|
||||
isSameSite: false, // necessary to allow using Kibana inside an iframe
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -88,3 +93,55 @@ export function createServer(options: ServerOptions) {
|
|||
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hapi extends the ValidationError interface to add this output key with more data.
|
||||
*/
|
||||
export interface HapiValidationError extends ValidationError {
|
||||
output: {
|
||||
statusCode: number;
|
||||
headers: Util.Dictionary<string | string[]>;
|
||||
payload: {
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message?: string;
|
||||
validation: {
|
||||
source: string;
|
||||
keys: string[];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to replicate Hapi v16 and below's validation responses. Should be used in the routes.validate.failAction key.
|
||||
*/
|
||||
export function defaultValidationErrorHandler(
|
||||
request: Request,
|
||||
h: ResponseToolkit,
|
||||
err?: Error
|
||||
): Lifecycle.ReturnValue {
|
||||
// Newer versions of Joi don't format the key for missing params the same way. This shim
|
||||
// provides backwards compatibility. Unfortunately, Joi doesn't export it's own Error class
|
||||
// in JS so we have to rely on the `name` key before we can cast it.
|
||||
//
|
||||
// The Hapi code we're 'overwriting' can be found here:
|
||||
// https://github.com/hapijs/hapi/blob/master/lib/validation.js#L102
|
||||
if (err && err.name === 'ValidationError' && err.hasOwnProperty('output')) {
|
||||
const validationError: HapiValidationError = err as HapiValidationError;
|
||||
const validationKeys: string[] = [];
|
||||
|
||||
validationError.details.forEach(detail => {
|
||||
if (detail.path.length > 0) {
|
||||
validationKeys.push(Hoek.escapeHtml(detail.path.join('.')));
|
||||
} else {
|
||||
// If no path, use the value sigil to signal the entire value had an issue.
|
||||
validationKeys.push('value');
|
||||
}
|
||||
});
|
||||
|
||||
validationError.output.payload.validation.keys = validationKeys;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Request, ResponseToolkit, Server } from 'hapi-latest';
|
||||
import { Request, ResponseToolkit, Server } from 'hapi';
|
||||
import { format as formatUrl } from 'url';
|
||||
|
||||
import { Logger } from '../logging';
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { ObjectType, TypeOf } from '@kbn/config-schema';
|
||||
import { Request } from 'hapi-latest';
|
||||
import { Request } from 'hapi';
|
||||
|
||||
import { filterHeaders, Headers } from './headers';
|
||||
import { RouteSchemas } from './route';
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import { ObjectType, schema, TypeOf } from '@kbn/config-schema';
|
||||
import { Request, ResponseObject, ResponseToolkit } from 'hapi-latest';
|
||||
import { Request, ResponseObject, ResponseToolkit } from 'hapi';
|
||||
|
||||
import { KibanaRequest } from './request';
|
||||
import { KibanaResponse, ResponseFactory, responseFactory } from './response';
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { Server as HapiServer } from 'hapi-latest';
|
||||
import { Server as HapiServer } from 'hapi';
|
||||
import { combineLatest, ConnectableObservable, EMPTY, Subscription } from 'rxjs';
|
||||
import { first, map, mergeMap, publishReplay, tap } from 'rxjs/operators';
|
||||
import { CoreService } from '../../types/core_service';
|
||||
|
|
|
@ -26,7 +26,7 @@ import { LegacyLoggingServer } from './legacy_logging_server';
|
|||
test('correctly forwards log records.', () => {
|
||||
const loggingServer = new LegacyLoggingServer({ events: {} });
|
||||
const onLogMock = jest.fn();
|
||||
loggingServer.on('log', onLogMock);
|
||||
loggingServer.events.on('log', onLogMock);
|
||||
|
||||
const timestamp = 1554433221100;
|
||||
const firstLogRecord = {
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { ServerExtType } from 'hapi';
|
||||
import Podium from 'podium';
|
||||
// @ts-ignore: implicit any for JS file
|
||||
import { Config, transformDeprecations } from '../../../../server/config';
|
||||
// @ts-ignore: implicit any for JS file
|
||||
|
@ -25,12 +26,11 @@ import { setupLogging } from '../../../../server/logging';
|
|||
import { LogRecord } from '../../logging/log_record';
|
||||
|
||||
interface PluginRegisterParams {
|
||||
register: {
|
||||
plugin: {
|
||||
register: (
|
||||
server: LegacyLoggingServer,
|
||||
options: PluginRegisterParams['options'],
|
||||
cb: () => void
|
||||
) => void;
|
||||
options: PluginRegisterParams['options']
|
||||
) => Promise<void>;
|
||||
};
|
||||
options: Record<string, any>;
|
||||
}
|
||||
|
@ -41,12 +41,14 @@ interface PluginRegisterParams {
|
|||
* same as the rest of the records generated by the "legacy" Kibana. But to reduce
|
||||
* overhead of having full blown Hapi server instance we create our own "light" version.
|
||||
*/
|
||||
export class LegacyLoggingServer extends EventEmitter {
|
||||
export class LegacyLoggingServer {
|
||||
public connections = [];
|
||||
// Emulates Hapi's usage of the podium event bus.
|
||||
public events: Podium = new Podium(['log', 'request', 'response']);
|
||||
|
||||
private onPostStopCallback?: () => void;
|
||||
|
||||
constructor(legacyLoggingConfig: Readonly<Record<string, any>>) {
|
||||
super();
|
||||
|
||||
// We set `ops.interval` to max allowed number and `ops` filter to value
|
||||
// that doesn't exist to avoid logging of ops at all, if turned on it will be
|
||||
// logged by the "legacy" Kibana.
|
||||
|
@ -64,12 +66,12 @@ export class LegacyLoggingServer extends EventEmitter {
|
|||
setupLogging(this, Config.withDefaultSchema(transformDeprecations(config)));
|
||||
}
|
||||
|
||||
public register({ register: plugin, options }: PluginRegisterParams, cb: () => void) {
|
||||
plugin.register(this, options, cb);
|
||||
public register({ plugin: { register }, options }: PluginRegisterParams): Promise<void> {
|
||||
return register(this, options);
|
||||
}
|
||||
|
||||
public log({ level, context, message, error, timestamp, meta = {} }: LogRecord) {
|
||||
this.emit('log', {
|
||||
this.events.emit('log', {
|
||||
data: error || message,
|
||||
tags: [level.id.toLowerCase(), ...context.split('.'), ...(meta.tags || [])],
|
||||
timestamp: timestamp.getTime(),
|
||||
|
@ -77,7 +79,18 @@ export class LegacyLoggingServer extends EventEmitter {
|
|||
}
|
||||
|
||||
public stop() {
|
||||
this.emit('stop');
|
||||
// Tell the plugin we're stopping.
|
||||
if (this.onPostStopCallback !== undefined) {
|
||||
this.onPostStopCallback();
|
||||
}
|
||||
}
|
||||
|
||||
public ext(eventName: ServerExtType, callback: () => void) {
|
||||
// method is called by plugin that's being registered.
|
||||
if (eventName === 'onPostStop') {
|
||||
this.onPostStopCallback = callback;
|
||||
}
|
||||
// We don't care about any others the plugin resgisters
|
||||
}
|
||||
|
||||
public expose() {
|
||||
|
|
|
@ -21,7 +21,7 @@ import _ from 'lodash';
|
|||
|
||||
const KNOWN_APIS = ['es_6_0'];
|
||||
|
||||
export function resolveApi(senseVersion, apis, reply) {
|
||||
export function resolveApi(senseVersion, apis, h) {
|
||||
const result = {};
|
||||
_.each(apis, function (name) {
|
||||
{
|
||||
|
@ -33,5 +33,5 @@ export function resolveApi(senseVersion, apis, reply) {
|
|||
}
|
||||
});
|
||||
|
||||
return reply(result).type('application/json');
|
||||
return h.response(result).type('application/json');
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import { resolveApi } from './server';
|
|||
describe('resolveApi', () => {
|
||||
it('allows known APIs to be resolved', () => {
|
||||
const mockReply = jest.fn((result) => ({ type: () => result }));
|
||||
const result = resolveApi('Sense Version', ['es_6_0'], mockReply);
|
||||
const result = resolveApi('Sense Version', ['es_6_0'], { response: mockReply });
|
||||
expect(result).toMatchObject({
|
||||
es_6_0: {
|
||||
endpoints: expect.any(Object),
|
||||
|
@ -33,13 +33,13 @@ describe('resolveApi', () => {
|
|||
|
||||
it('does not resolve APIs that are not known', () => {
|
||||
const mockReply = jest.fn((result) => ({ type: () => result }));
|
||||
const result = resolveApi('Sense Version', ['unknown'], mockReply);
|
||||
const result = resolveApi('Sense Version', ['unknown'], { response: mockReply });
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('handles request for apis that are known and unknown', () => {
|
||||
const mockReply = jest.fn((result) => ({ type: () => result }));
|
||||
const result = resolveApi('Sense Version', ['es_6_0'], mockReply);
|
||||
const result = resolveApi('Sense Version', ['es_6_0'], { response: mockReply });
|
||||
expect(result).toMatchObject({
|
||||
es_6_0: {
|
||||
endpoints: expect.any(Object),
|
||||
|
@ -48,4 +48,4 @@ describe('resolveApi', () => {
|
|||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -113,14 +113,13 @@ export default function (kibana) {
|
|||
server.route({
|
||||
path: '/api/console/api_server',
|
||||
method: ['GET', 'POST'],
|
||||
handler: function (req, reply) {
|
||||
handler: function (req, h) {
|
||||
const { sense_version: version, apis } = req.query;
|
||||
if (!apis) {
|
||||
reply(Boom.badRequest('"apis" is a required param.'));
|
||||
return;
|
||||
throw Boom.badRequest('"apis" is a required param.');
|
||||
}
|
||||
|
||||
return resolveApi(version, apis.split(','), reply);
|
||||
return resolveApi(version, apis.split(','), h);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
@ -36,8 +36,6 @@ describe('Console Proxy Route', () => {
|
|||
sandbox.stub(Wreck, 'request').callsFake(createWreckResponseStub(response));
|
||||
|
||||
const server = new Server();
|
||||
|
||||
server.connection({ port: 0 });
|
||||
server.route(createProxyRoute({
|
||||
baseUrl: 'http://localhost:9200'
|
||||
}));
|
||||
|
|
|
@ -38,8 +38,6 @@ describe('Console Proxy Route', () => {
|
|||
|
||||
setup = () => {
|
||||
const server = new Server();
|
||||
|
||||
server.connection({ port: 0 });
|
||||
server.route(createProxyRoute({
|
||||
baseUrl: 'http://localhost:9200'
|
||||
}));
|
||||
|
|
|
@ -38,7 +38,6 @@ describe('Console Proxy Route', () => {
|
|||
|
||||
setup = () => {
|
||||
const server = new Server();
|
||||
server.connection({ port: 0 });
|
||||
teardowns.push(() => server.stop());
|
||||
return { server };
|
||||
};
|
||||
|
|
|
@ -36,8 +36,6 @@ describe('Console Proxy Route', () => {
|
|||
|
||||
request = async (method, path) => {
|
||||
const server = new Server();
|
||||
|
||||
server.connection({ port: 0 });
|
||||
server.route(createProxyRoute({
|
||||
baseUrl: 'http://localhost:9200'
|
||||
}));
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
import { Readable } from 'stream';
|
||||
|
||||
export function createWreckResponseStub(response) {
|
||||
return (...args) => {
|
||||
return async () => {
|
||||
const resp = new Readable({
|
||||
read() {
|
||||
if (response) {
|
||||
|
@ -37,6 +37,6 @@ export function createWreckResponseStub(response) {
|
|||
'content-length': String(response ? response.length : 0)
|
||||
};
|
||||
|
||||
args.pop()(null, resp);
|
||||
return resp;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ function getProxyHeaders(req) {
|
|||
// see https://git.io/vytQ7
|
||||
extendCommaList(headers, 'x-forwarded-for', req.info.remoteAddress);
|
||||
extendCommaList(headers, 'x-forwarded-port', req.info.remotePort);
|
||||
extendCommaList(headers, 'x-forwarded-proto', req.connection.info.protocol);
|
||||
extendCommaList(headers, 'x-forwarded-proto', req.server.info.protocol);
|
||||
extendCommaList(headers, 'x-forwarded-host', req.info.host);
|
||||
}
|
||||
|
||||
|
@ -82,21 +82,21 @@ export const createProxyRoute = ({
|
|||
},
|
||||
|
||||
pre: [
|
||||
function filterPath(req, reply) {
|
||||
function filterPath(req) {
|
||||
const { path } = req.query;
|
||||
|
||||
if (!pathFilters.some(re => re.test(path))) {
|
||||
const err = Boom.forbidden();
|
||||
err.output.payload = `Error connecting to '${path}':\n\nUnable to send requests to that path.`;
|
||||
err.output.headers['content-type'] = 'text/plain';
|
||||
reply(err);
|
||||
} else {
|
||||
reply();
|
||||
if (pathFilters.some(re => re.test(path))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const err = Boom.forbidden();
|
||||
err.output.payload = `Error connecting to '${path}':\n\nUnable to send requests to that path.`;
|
||||
err.output.headers['content-type'] = 'text/plain';
|
||||
throw err;
|
||||
},
|
||||
],
|
||||
|
||||
handler(req, reply) {
|
||||
handler: async (req, h) => {
|
||||
const { payload, query } = req;
|
||||
const { path, method } = query;
|
||||
const uri = resolveUri(baseUrl, path);
|
||||
|
@ -119,23 +119,18 @@ export const createProxyRoute = ({
|
|||
},
|
||||
};
|
||||
|
||||
Wreck.request(method, uri, wreckOptions, (err, esResponse) => {
|
||||
if (err) {
|
||||
return reply(err);
|
||||
}
|
||||
const esResponse = await Wreck.request(method, uri, wreckOptions);
|
||||
|
||||
if (method.toUpperCase() !== 'HEAD') {
|
||||
reply(esResponse)
|
||||
.code(esResponse.statusCode)
|
||||
.header('warning', esResponse.headers.warning);
|
||||
return;
|
||||
}
|
||||
|
||||
reply(`${esResponse.statusCode} - ${esResponse.statusMessage}`)
|
||||
if (method.toUpperCase() !== 'HEAD') {
|
||||
return h.response(esResponse)
|
||||
.code(esResponse.statusCode)
|
||||
.type('text/plain')
|
||||
.header('warning', esResponse.headers.warning);
|
||||
});
|
||||
}
|
||||
|
||||
return h.response(`${esResponse.statusCode} - ${esResponse.statusMessage}`)
|
||||
.code(esResponse.statusCode)
|
||||
.type('text/plain')
|
||||
.header('warning', esResponse.headers.warning);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -35,34 +35,32 @@ export default function (kibana) {
|
|||
return new kibana.Plugin({
|
||||
require: ['kibana'],
|
||||
config(Joi) {
|
||||
const { array, boolean, number, object, string, ref } = Joi;
|
||||
|
||||
const sslSchema = object({
|
||||
verificationMode: string().valid('none', 'certificate', 'full').default('full'),
|
||||
certificateAuthorities: array().single().items(string()),
|
||||
certificate: string(),
|
||||
key: string(),
|
||||
keyPassphrase: string(),
|
||||
alwaysPresentCertificate: boolean().default(false),
|
||||
const sslSchema = Joi.object({
|
||||
verificationMode: Joi.string().valid('none', 'certificate', 'full').default('full'),
|
||||
certificateAuthorities: Joi.array().single().items(Joi.string()),
|
||||
certificate: Joi.string(),
|
||||
key: Joi.string(),
|
||||
keyPassphrase: Joi.string(),
|
||||
alwaysPresentCertificate: Joi.boolean().default(false),
|
||||
}).default();
|
||||
|
||||
return object({
|
||||
enabled: boolean().default(true),
|
||||
url: string().uri({ scheme: ['http', 'https'] }).default('http://localhost:9200'),
|
||||
preserveHost: boolean().default(true),
|
||||
username: string(),
|
||||
password: string(),
|
||||
shardTimeout: number().default(30000),
|
||||
requestTimeout: number().default(30000),
|
||||
requestHeadersWhitelist: array().items().single().default(DEFAULT_REQUEST_HEADERS),
|
||||
customHeaders: object().default({}),
|
||||
pingTimeout: number().default(ref('requestTimeout')),
|
||||
startupTimeout: number().default(5000),
|
||||
logQueries: boolean().default(false),
|
||||
return Joi.object({
|
||||
enabled: Joi.boolean().default(true),
|
||||
url: Joi.string().uri({ scheme: ['http', 'https'] }).default('http://localhost:9200'),
|
||||
preserveHost: Joi.boolean().default(true),
|
||||
username: Joi.string(),
|
||||
password: Joi.string(),
|
||||
shardTimeout: Joi.number().default(30000),
|
||||
requestTimeout: Joi.number().default(30000),
|
||||
requestHeadersWhitelist: Joi.array().items().single().default(DEFAULT_REQUEST_HEADERS),
|
||||
customHeaders: Joi.object().default({}),
|
||||
pingTimeout: Joi.number().default(Joi.ref('requestTimeout')),
|
||||
startupTimeout: Joi.number().default(5000),
|
||||
logQueries: Joi.boolean().default(false),
|
||||
ssl: sslSchema,
|
||||
apiVersion: Joi.string().default('master'),
|
||||
healthCheck: object({
|
||||
delay: number().default(2500)
|
||||
healthCheck: Joi.object({
|
||||
delay: Joi.number().default(2500)
|
||||
}).default(),
|
||||
}).default();
|
||||
},
|
||||
|
|
|
@ -36,7 +36,9 @@ describe('plugins/elasticsearch', function () {
|
|||
elasticsearch: {}
|
||||
},
|
||||
expose: sinon.mock(),
|
||||
on: sinon.stub(),
|
||||
events: {
|
||||
on: sinon.stub(),
|
||||
}
|
||||
};
|
||||
|
||||
clusters = createClusters(server);
|
||||
|
@ -86,7 +88,6 @@ describe('plugins/elasticsearch', function () {
|
|||
|
||||
it('closes all clusters', async () => {
|
||||
const server = new Hapi.Server();
|
||||
server.connection({ port: 0 });
|
||||
const clusters = createClusters(server);
|
||||
const cluster = clusters.create('name', { config: true });
|
||||
expect(cluster).to.have.property('stub', true);
|
||||
|
|
|
@ -114,13 +114,10 @@ describe('plugins/elasticsearch', () => {
|
|||
sinon.assert.calledOnce(server.ext);
|
||||
sinon.assert.calledWithExactly(server.ext, sinon.match.string, sinon.match.func);
|
||||
|
||||
// call the server extension
|
||||
const reply = sinon.stub();
|
||||
const [, handler] = server.ext.firstCall.args;
|
||||
handler({}, reply);
|
||||
handler(); // this should be health.stop
|
||||
|
||||
// ensure that the handler called reply and unregistered the time
|
||||
sinon.assert.calledOnce(reply);
|
||||
// ensure that the handler unregistered the timer
|
||||
expect(getTimerCount()).to.be(0);
|
||||
});
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import { Cluster } from './cluster';
|
|||
export function createClusters(server) {
|
||||
const clusters = new Map();
|
||||
|
||||
server.on('stop', () => {
|
||||
server.events.on('stop', () => {
|
||||
for (const [name, cluster] of clusters) {
|
||||
cluster.close();
|
||||
clusters.delete(name);
|
||||
|
|
|
@ -33,10 +33,9 @@ export function createProxy(server, method, path, config) {
|
|||
['/elasticsearch', server.plugins.elasticsearch.getCluster('data')],
|
||||
]);
|
||||
|
||||
const responseHandler = function (err, upstreamResponse, request, reply) {
|
||||
const responseHandler = function (err, upstreamResponse) {
|
||||
if (err) {
|
||||
reply(err);
|
||||
return;
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (upstreamResponse.headers.location) {
|
||||
|
@ -44,7 +43,7 @@ export function createProxy(server, method, path, config) {
|
|||
upstreamResponse.headers.location = encodeURI(upstreamResponse.headers.location);
|
||||
}
|
||||
|
||||
reply(null, upstreamResponse);
|
||||
return upstreamResponse;
|
||||
};
|
||||
|
||||
for (const [proxyPrefix, cluster] of proxies) {
|
||||
|
|
|
@ -95,10 +95,7 @@ export default function (plugin, server) {
|
|||
return true;
|
||||
}
|
||||
|
||||
server.ext('onPreStop', (request, reply) => {
|
||||
stopChecking();
|
||||
reply();
|
||||
});
|
||||
server.ext('onPreStop', stopChecking);
|
||||
|
||||
return {
|
||||
waitUntilReady: waitUntilReady,
|
||||
|
@ -107,5 +104,4 @@ export default function (plugin, server) {
|
|||
stop: stopChecking,
|
||||
isRunning: function () { return !!timeoutId; },
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ export default function mapUri(cluster, proxyPrefix) {
|
|||
return trimRight(pathA, '/') + '/' + trimLeft(pathB, '/');
|
||||
}
|
||||
|
||||
return function (request, done) {
|
||||
return function (request) {
|
||||
const {
|
||||
protocol: esUrlProtocol,
|
||||
slashes: esUrlHasSlashes,
|
||||
|
@ -60,6 +60,6 @@ export default function mapUri(cluster, proxyPrefix) {
|
|||
const filteredHeaders = filterHeaders(request.headers, cluster.getRequestHeadersWhitelist());
|
||||
const mappedHeaders = setHeaders(filteredHeaders, cluster.getCustomHeaders());
|
||||
const mappedUrl = formatUrl(mappedUrlComponents);
|
||||
done(null, mappedUrl, mappedHeaders);
|
||||
return { uri: mappedUrl, headers: mappedHeaders };
|
||||
};
|
||||
}
|
||||
|
|
|
@ -36,18 +36,18 @@ export function exportApi(server) {
|
|||
tags: ['api'],
|
||||
},
|
||||
method: ['GET'],
|
||||
handler: (req, reply) => {
|
||||
handler: async (req, h) => {
|
||||
const currentDate = moment.utc();
|
||||
return exportDashboards(req)
|
||||
.then(resp => {
|
||||
const json = JSON.stringify(resp, null, ' ');
|
||||
const filename = `kibana-dashboards.${currentDate.format('YYYY-MM-DD-HH-mm-ss')}.json`;
|
||||
reply(json)
|
||||
return h.response(json)
|
||||
.header('Content-Disposition', `attachment; filename="${filename}"`)
|
||||
.header('Content-Type', 'application/json')
|
||||
.header('Content-Length', Buffer.byteLength(json, 'utf8'));
|
||||
})
|
||||
.catch(err => reply(Boom.boomify(err, { statusCode: 400 })));
|
||||
.catch(err => Boom.boomify(err, { statusCode: 400 }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -22,8 +22,8 @@ export function registerTutorials(server) {
|
|||
server.route({
|
||||
path: '/api/kibana/home/tutorials',
|
||||
method: ['GET'],
|
||||
handler: async function (req, reply) {
|
||||
reply(server.getTutorials(req));
|
||||
handler: function (req) {
|
||||
return server.getTutorials(req);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -39,10 +39,12 @@ export function importApi(server) {
|
|||
tags: ['api'],
|
||||
},
|
||||
|
||||
handler: (req, reply) => {
|
||||
return importDashboards(req)
|
||||
.then((resp) => reply(resp))
|
||||
.catch(err => reply(Boom.boomify(err, { statusCode: 400 })));
|
||||
handler: async (req) => {
|
||||
try {
|
||||
return await importDashboards(req);
|
||||
} catch (err) {
|
||||
throw Boom.boomify(err, { statusCode: 400 });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ export function registerKqlTelemetryApi(server) {
|
|||
},
|
||||
tags: ['api'],
|
||||
},
|
||||
handler: async function (request, reply) {
|
||||
handler: async function (request) {
|
||||
const { savedObjects: { getSavedObjectsRepository } } = server;
|
||||
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin');
|
||||
const internalRepository = getSavedObjectsRepository(callWithInternalUser);
|
||||
|
@ -51,10 +51,10 @@ export function registerKqlTelemetryApi(server) {
|
|||
);
|
||||
}
|
||||
catch (error) {
|
||||
reply(new Boom('Something went wrong', { statusCode: error.status, data: { success: false } }));
|
||||
return new Boom('Something went wrong', { statusCode: error.status, data: { success: false } });
|
||||
}
|
||||
|
||||
reply({ success: true });
|
||||
return { success: true };
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -38,20 +38,19 @@ export function registerRelationships(server) {
|
|||
},
|
||||
},
|
||||
|
||||
handler: async (req, reply) => {
|
||||
handler: async (req) => {
|
||||
const type = req.params.type;
|
||||
const id = req.params.id;
|
||||
const size = req.query.size || 10;
|
||||
|
||||
try {
|
||||
const response = await findRelationships(type, id, size, req.getSavedObjectsClient());
|
||||
reply(response);
|
||||
return await findRelationships(type, id, size, req.getSavedObjectsClient());
|
||||
} catch (err) {
|
||||
if (isNotFoundError(err)) {
|
||||
reply(Boom.boomify(new Error('Resource not found'), { statusCode: 404 }));
|
||||
return;
|
||||
throw Boom.boomify(new Error('Resource not found'), { statusCode: 404 });
|
||||
}
|
||||
reply(Boom.boomify(err, { statusCode: 500 }));
|
||||
|
||||
throw Boom.boomify(err, { statusCode: 500 });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -45,13 +45,14 @@ export function registerScrollForExportRoute(server) {
|
|||
},
|
||||
},
|
||||
|
||||
handler: async (req, reply) => {
|
||||
handler: async (req) => {
|
||||
const savedObjectsClient = req.getSavedObjectsClient();
|
||||
const objects = await findAll(savedObjectsClient, {
|
||||
perPage: 1000,
|
||||
type: req.payload.typesToInclude
|
||||
});
|
||||
const response = objects.map(hit => {
|
||||
|
||||
return objects.map(hit => {
|
||||
const type = hit.type;
|
||||
return {
|
||||
_id: hit.id,
|
||||
|
@ -63,8 +64,6 @@ export function registerScrollForExportRoute(server) {
|
|||
_migrationVersion: hit.migrationVersion,
|
||||
};
|
||||
});
|
||||
|
||||
reply(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -82,7 +81,7 @@ export function registerScrollForCountRoute(server) {
|
|||
},
|
||||
},
|
||||
|
||||
handler: async (req, reply) => {
|
||||
handler: async (req) => {
|
||||
const savedObjectsClient = req.getSavedObjectsClient();
|
||||
const findOptions = {
|
||||
type: req.payload.typesToInclude,
|
||||
|
@ -108,7 +107,7 @@ export function registerScrollForCountRoute(server) {
|
|||
}
|
||||
}
|
||||
|
||||
reply(counts);
|
||||
return counts;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -20,8 +20,15 @@ import Hapi from 'hapi';
|
|||
import { registerScrollForExportRoute } from './scroll';
|
||||
|
||||
const createMockServer = () => {
|
||||
const mockServer = new Hapi.Server({ debug: false });
|
||||
mockServer.connection({ port: 8080 });
|
||||
const mockServer = new Hapi.Server({
|
||||
debug: false,
|
||||
port: 8080,
|
||||
routes: {
|
||||
validate: {
|
||||
failAction: (r, h, err) => { throw err; }
|
||||
}
|
||||
}
|
||||
});
|
||||
return mockServer;
|
||||
};
|
||||
|
||||
|
|
|
@ -21,8 +21,8 @@ export function registerLanguages(server) {
|
|||
server.route({
|
||||
path: '/api/kibana/scripts/languages',
|
||||
method: 'GET',
|
||||
handler: function (request, reply) {
|
||||
reply(['painless', 'expression']);
|
||||
handler: function () {
|
||||
return ['painless', 'expression'];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ export function scrollSearchApi(server) {
|
|||
server.route({
|
||||
path: '/api/kibana/legacy_scroll_start',
|
||||
method: ['POST'],
|
||||
handler: (req, reply) => {
|
||||
handler: async (req) => {
|
||||
const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin');
|
||||
const { index, size, body } = req.payload;
|
||||
const params = {
|
||||
|
@ -33,21 +33,26 @@ export function scrollSearchApi(server) {
|
|||
scroll: '1m',
|
||||
sort: '_doc',
|
||||
};
|
||||
return callWithRequest(req, 'search', params)
|
||||
.then(reply)
|
||||
.catch(error => reply(handleESError(error)));
|
||||
|
||||
try {
|
||||
return await callWithRequest(req, 'search', params);
|
||||
} catch (err) {
|
||||
throw handleESError(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
path: '/api/kibana/legacy_scroll_continue',
|
||||
method: ['POST'],
|
||||
handler: (req, reply) => {
|
||||
handler: async (req) => {
|
||||
const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin');
|
||||
const { scrollId } = req.payload;
|
||||
return callWithRequest(req, 'scroll', { scrollId, scroll: '1m' })
|
||||
.then(reply)
|
||||
.catch(error => reply(handleESError(error)));
|
||||
try {
|
||||
return await callWithRequest(req, 'scroll', { scrollId, scroll: '1m' });
|
||||
} catch (err) {
|
||||
throw handleESError(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -24,22 +24,20 @@ export default function registerCount(server) {
|
|||
server.route({
|
||||
path: '/api/kibana/{id}/_count',
|
||||
method: ['POST', 'GET'],
|
||||
handler: function (req, reply) {
|
||||
handler: async function (req) {
|
||||
const { callWithRequest } = server.plugins.elasticsearch.getCluster('data');
|
||||
const boundCallWithRequest = _.partial(callWithRequest, req);
|
||||
|
||||
boundCallWithRequest('count', {
|
||||
allowNoIndices: false,
|
||||
index: req.params.id
|
||||
})
|
||||
.then(
|
||||
function (res) {
|
||||
reply({ count: res.count });
|
||||
},
|
||||
function (error) {
|
||||
reply(handleESError(error));
|
||||
}
|
||||
);
|
||||
try {
|
||||
const res = await boundCallWithRequest('count', {
|
||||
allowNoIndices: false,
|
||||
index: req.params.id
|
||||
});
|
||||
|
||||
return { count: res.count };
|
||||
} catch (err) {
|
||||
throw handleESError(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ export function registerValueSuggestions(server) {
|
|||
server.route({
|
||||
path: '/api/kibana/suggestions/values/{index}',
|
||||
method: ['POST'],
|
||||
handler: async function (req, reply) {
|
||||
handler: async function (req) {
|
||||
const { index } = req.params;
|
||||
const { field, query, boolFilter } = req.payload;
|
||||
const { callWithRequest } = server.plugins.elasticsearch.getCluster('data');
|
||||
|
@ -33,9 +33,9 @@ export function registerValueSuggestions(server) {
|
|||
const response = await callWithRequest(req, 'search', { index, body });
|
||||
const buckets = get(response, 'aggregations.suggestions.buckets') || [];
|
||||
const suggestions = map(buckets, 'key');
|
||||
reply(suggestions);
|
||||
return suggestions;
|
||||
} catch (error) {
|
||||
reply(handleESError(error));
|
||||
throw handleESError(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -20,11 +20,11 @@
|
|||
import { IndexPatternsService } from '../../../../server/index_patterns/service';
|
||||
export const getIndexPatternService = {
|
||||
assign: 'indexPatternsService',
|
||||
method(req, reply) {
|
||||
method(req) {
|
||||
const dataCluster = req.server.plugins.elasticsearch.getCluster('data');
|
||||
const callDataCluster = (...args) => {
|
||||
return dataCluster.callWithRequest(req, ...args);
|
||||
};
|
||||
reply(new IndexPatternsService(callDataCluster));
|
||||
return new IndexPatternsService(callDataCluster);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -27,15 +27,17 @@ export default (server) => {
|
|||
},
|
||||
path: '/api/metrics/fields',
|
||||
method: 'GET',
|
||||
handler: (req, reply) => {
|
||||
getFields(req)
|
||||
.then(reply)
|
||||
.catch((err) => {
|
||||
if (err.isBoom && err.status === 401) return reply(err);
|
||||
reply([]);
|
||||
});
|
||||
handler: async (req) => {
|
||||
try {
|
||||
return await getFields(req);
|
||||
} catch (err) {
|
||||
if (err.isBoom && err.status === 401) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -24,13 +24,16 @@ export default (server) => {
|
|||
server.route({
|
||||
path: '/api/metrics/vis/data',
|
||||
method: 'POST',
|
||||
handler: (req, reply) => {
|
||||
getVisData(req)
|
||||
.then(reply)
|
||||
.catch(err => {
|
||||
if (err.isBoom && err.status === 401) return reply(err);
|
||||
reply(Boom.boomify(err, { statusCode: 500 }));
|
||||
});
|
||||
handler: async (req) => {
|
||||
try {
|
||||
return await getVisData(req);
|
||||
} catch(err) {
|
||||
if (err.isBoom && err.status === 401) {
|
||||
return err;
|
||||
}
|
||||
|
||||
throw Boom.boomify(err, { statusCode: 500 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -23,12 +23,13 @@ export default function (server) {
|
|||
server.route({
|
||||
method: 'GET',
|
||||
path: '/api/timelion/functions',
|
||||
handler: function (request, reply) {
|
||||
handler: () => {
|
||||
const functionArray = _.map(server.plugins.timelion.functions, function (val, key) {
|
||||
// TODO: This won't work on frozen objects, it should be removed when everything is converted to datasources and chainables
|
||||
return _.extend({}, val, { name: key });
|
||||
});
|
||||
reply(_.sortBy(functionArray, 'name'));
|
||||
|
||||
return _.sortBy(functionArray, 'name');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -22,8 +22,8 @@ import _ from 'lodash';
|
|||
import chainRunnerFn from '../handlers/chain_runner.js';
|
||||
const timelionDefaults = require('../lib/get_namespaced_settings')();
|
||||
|
||||
function replyWithError(e, reply) {
|
||||
reply({
|
||||
function formatErrorResponse(e, h) {
|
||||
return h.response({
|
||||
title: e.toString(),
|
||||
message: e.toString()
|
||||
}).code(500);
|
||||
|
@ -34,7 +34,7 @@ export default function (server) {
|
|||
server.route({
|
||||
method: ['POST', 'GET'],
|
||||
path: '/api/timelion/run',
|
||||
handler: async (request, reply) => {
|
||||
handler: async (request, h) => {
|
||||
try {
|
||||
const uiSettings = await request.getUiSettingsService().getAll();
|
||||
|
||||
|
@ -55,18 +55,18 @@ export default function (server) {
|
|||
}
|
||||
}));
|
||||
|
||||
reply({
|
||||
return {
|
||||
sheet,
|
||||
stats: chainRunner.getStats()
|
||||
});
|
||||
};
|
||||
|
||||
} catch (err) {
|
||||
server.log(['timelion', 'error'], `${err.toString()}: ${err.stack}`);
|
||||
// TODO Maybe we should just replace everywhere we throw with Boom? Probably.
|
||||
if (err.isBoom) {
|
||||
reply(err);
|
||||
return err;
|
||||
} else {
|
||||
replyWithError(err, reply);
|
||||
return formatErrorResponse(err, h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ export default function (server) {
|
|||
server.route({
|
||||
method: 'GET',
|
||||
path: '/api/timelion/validate/es',
|
||||
handler: async function (request, reply) {
|
||||
handler: async function (request) {
|
||||
const uiSettings = await request.getUiSettingsService().getAll();
|
||||
|
||||
const { callWithRequest } = server.plugins.elasticsearch.getCluster('data');
|
||||
|
@ -57,19 +57,18 @@ export default function (server) {
|
|||
}
|
||||
|
||||
if (_.has(resp, 'aggregations.maxAgg.value') && _.has(resp, 'aggregations.minAgg.value')) {
|
||||
reply({
|
||||
return {
|
||||
ok: true,
|
||||
field: timefield,
|
||||
min: _.get(resp, 'aggregations.minAgg.value'),
|
||||
max: _.get(resp, 'aggregations.maxAgg.value')
|
||||
});
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
reply({
|
||||
return {
|
||||
ok: false,
|
||||
resp: resp
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -52,11 +52,7 @@ export async function download(options) {
|
|||
try {
|
||||
log.debug(`Attempting download of ${url}`, chalk.dim(sha256));
|
||||
|
||||
const request = wreck.request('GET', url);
|
||||
const response = await Promise.race([
|
||||
new Promise(resolve => request.once('response', resolve)),
|
||||
new Promise((resolve, reject) => request.once('error', reject)),
|
||||
]);
|
||||
const response = await wreck.request('GET', url);
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
throw new Error(
|
||||
|
|
|
@ -427,8 +427,7 @@ export default class BaseOptimizer {
|
|||
Stats.presetToOptions('minimal')
|
||||
));
|
||||
|
||||
return Boom.create(
|
||||
500,
|
||||
throw Boom.internal(
|
||||
`Optimizations failure.\n${details.split('\n').join('\n ')}\n`,
|
||||
stats.toJson(Stats.presetToOptions('detailed'))
|
||||
);
|
||||
|
|
|
@ -47,7 +47,6 @@ describe('optimizer/bundle route', () => {
|
|||
} = options;
|
||||
|
||||
const server = new Hapi.Server();
|
||||
server.connection({ port: 0 });
|
||||
server.register([Inert]);
|
||||
|
||||
server.route(createBundlesRoute({
|
||||
|
@ -217,18 +216,18 @@ describe('optimizer/bundle route', () => {
|
|||
});
|
||||
|
||||
describe('js file outside bundlesPath', () => {
|
||||
it('responds with a 403', async () => {
|
||||
it('responds with a 404', async () => {
|
||||
const server = createServer();
|
||||
|
||||
const response = await server.inject({
|
||||
url: '/bundles/../outside_output.js'
|
||||
});
|
||||
|
||||
expect(response.statusCode).to.be(403);
|
||||
expect(response.statusCode).to.be(404);
|
||||
expect(response.result).to.eql({
|
||||
error: 'Forbidden',
|
||||
message: 'Forbidden',
|
||||
statusCode: 403
|
||||
error: 'Not Found',
|
||||
message: 'Not Found',
|
||||
statusCode: 404
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -58,18 +58,19 @@ export function createBundlesRoute({ bundlesPath, basePublicPath }) {
|
|||
auth: false,
|
||||
ext: {
|
||||
onPreHandler: {
|
||||
method(request, reply) {
|
||||
method(request, h) {
|
||||
const ext = extname(request.params.path);
|
||||
if (ext !== '.js' && ext !== '.css') {
|
||||
return reply.continue();
|
||||
return h.continue;
|
||||
}
|
||||
|
||||
reply(createDynamicAssetResponse({
|
||||
return createDynamicAssetResponse({
|
||||
request,
|
||||
h,
|
||||
bundlesPath,
|
||||
fileHashCache,
|
||||
publicPath: `${basePublicPath}/bundles/`
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -54,6 +54,7 @@ import { replacePlaceholder } from '../public_path_placeholder';
|
|||
export async function createDynamicAssetResponse(options) {
|
||||
const {
|
||||
request,
|
||||
h,
|
||||
bundlesPath,
|
||||
publicPath,
|
||||
fileHashCache,
|
||||
|
@ -65,7 +66,7 @@ export async function createDynamicAssetResponse(options) {
|
|||
|
||||
// prevent path traversal, only process paths that resolve within bundlesPath
|
||||
if (!path.startsWith(bundlesPath)) {
|
||||
return Boom.forbidden(null, 'EACCES');
|
||||
throw Boom.forbidden(null, 'EACCES');
|
||||
}
|
||||
|
||||
// we use and manage a file descriptor mostly because
|
||||
|
@ -83,13 +84,12 @@ export async function createDynamicAssetResponse(options) {
|
|||
});
|
||||
fd = null; // read stream is now responsible for fd
|
||||
|
||||
const response = request.generateResponse(replacePlaceholder(read, publicPath));
|
||||
response.code(200);
|
||||
response.etag(`${hash}-${publicPath}`);
|
||||
response.header('cache-control', 'must-revalidate');
|
||||
response.type(request.server.mime.path(path).type);
|
||||
return response;
|
||||
|
||||
return h.response(replacePlaceholder(read, publicPath))
|
||||
.takeover()
|
||||
.code(200)
|
||||
.etag(`${hash}-${publicPath}`)
|
||||
.header('cache-control', 'must-revalidate')
|
||||
.type(request.server.mime.path(path).type);
|
||||
} catch (error) {
|
||||
if (fd) {
|
||||
try {
|
||||
|
@ -101,9 +101,9 @@ export async function createDynamicAssetResponse(options) {
|
|||
}
|
||||
|
||||
if (error.code === 'ENOENT') {
|
||||
return Boom.notFound();
|
||||
throw Boom.notFound();
|
||||
}
|
||||
|
||||
return Boom.boomify(error);
|
||||
throw Boom.boomify(error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,10 +63,9 @@ export default class WatchOptimizer extends BaseOptimizer {
|
|||
// pause all requests received while the compiler is running
|
||||
// and continue once an outcome is reached (aborting the request
|
||||
// with an error if it was a failure).
|
||||
server.ext('onRequest', (request, reply) => {
|
||||
this.onceBuildOutcome()
|
||||
.then(() => reply.continue())
|
||||
.catch(reply);
|
||||
server.ext('onRequest', async (request, h) => {
|
||||
await this.onceBuildOutcome();
|
||||
return h.continue;
|
||||
});
|
||||
|
||||
server.route(createBundlesRoute({
|
||||
|
|
|
@ -19,26 +19,23 @@
|
|||
|
||||
|
||||
import { Server } from 'hapi';
|
||||
import { fromNode } from 'bluebird';
|
||||
import { registerHapiPlugins } from '../../server/http/register_hapi_plugins';
|
||||
|
||||
export default class WatchServer {
|
||||
constructor(host, port, basePath, optimizer) {
|
||||
this.basePath = basePath;
|
||||
this.optimizer = optimizer;
|
||||
this.server = new Server();
|
||||
|
||||
registerHapiPlugins(this.server);
|
||||
|
||||
this.server.connection({
|
||||
this.server = new Server({
|
||||
host: host,
|
||||
port: port
|
||||
});
|
||||
|
||||
registerHapiPlugins(this.server);
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.optimizer.init();
|
||||
this.optimizer.bindToServer(this.server, this.basePath);
|
||||
await fromNode(cb => this.server.start(cb));
|
||||
await this.server.start();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ describe('config/deprecation warnings mixin', function () {
|
|||
let stdio = '';
|
||||
let proc = null;
|
||||
|
||||
before(() => new Promise((resolve, reject) => {
|
||||
before(async () => {
|
||||
proc = spawn(process.execPath, [
|
||||
'-r', SETUP_NODE_ENV,
|
||||
RUN_KBN_SERVER_STARTUP
|
||||
|
@ -50,23 +50,35 @@ describe('config/deprecation warnings mixin', function () {
|
|||
}
|
||||
});
|
||||
|
||||
proc.stdout.on('data', (chunk) => {
|
||||
stdio += chunk.toString('utf8');
|
||||
});
|
||||
// Either time out in 10 seconds, or resolve once the line is in our buffer
|
||||
return Promise.race([
|
||||
new Promise((resolve) => setTimeout(resolve, 10000)),
|
||||
new Promise((resolve, reject) => {
|
||||
proc.stdout.on('data', (chunk) => {
|
||||
stdio += chunk.toString('utf8');
|
||||
if (chunk.toString('utf8').includes('deprecation')) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
proc.stderr.on('data', (chunk) => {
|
||||
stdio += chunk.toString('utf8');
|
||||
});
|
||||
proc.stderr.on('data', (chunk) => {
|
||||
stdio += chunk.toString('utf8');
|
||||
if (chunk.toString('utf8').includes('deprecation')) {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
proc.on('exit', (code) => {
|
||||
proc = null;
|
||||
if (code > 0) {
|
||||
reject(new Error(`Kibana server exited with ${code} -- stdout:\n\n${stdio}\n`));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}));
|
||||
proc.on('exit', (code) => {
|
||||
proc = null;
|
||||
if (code > 0) {
|
||||
reject(new Error(`Kibana server exited with ${code} -- stdout:\n\n${stdio}\n`));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
})
|
||||
]);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
if (proc) {
|
||||
|
|
|
@ -51,20 +51,20 @@ describe('Config schema', function () {
|
|||
it('rejects strings with trailing slashes', function () {
|
||||
const { error } = validate({ server: { basePath: '/path/' } });
|
||||
expect(error).toHaveProperty('details');
|
||||
expect(error.details[0]).toHaveProperty('path', 'server.basePath');
|
||||
expect(error.details[0]).toHaveProperty('path', ['server', 'basePath']);
|
||||
});
|
||||
|
||||
it('rejects strings without leading slashes', function () {
|
||||
const { error } = validate({ server: { basePath: 'path' } });
|
||||
expect(error).toHaveProperty('details');
|
||||
expect(error.details[0]).toHaveProperty('path', 'server.basePath');
|
||||
expect(error.details[0]).toHaveProperty('path', ['server', 'basePath']);
|
||||
});
|
||||
|
||||
it('rejects things that are not strings', function () {
|
||||
for (const value of [1, true, {}, [], /foo/]) {
|
||||
const { error } = validate({ server: { basePath: value } });
|
||||
expect(error).toHaveProperty('details');
|
||||
expect(error.details[0]).toHaveProperty('path', 'server.basePath');
|
||||
expect(error.details[0]).toHaveProperty('path', ['server', 'basePath']);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -91,13 +91,13 @@ describe('Config schema', function () {
|
|||
it('rejects true if basePath not set', function () {
|
||||
const { error } = validate({ server: { rewriteBasePath: true } });
|
||||
expect(error).toHaveProperty('details');
|
||||
expect(error.details[0]).toHaveProperty('path', 'server.rewriteBasePath');
|
||||
expect(error.details[0]).toHaveProperty('path', ['server', 'rewriteBasePath']);
|
||||
});
|
||||
|
||||
it('rejects strings', function () {
|
||||
const { error } = validate({ server: { rewriteBasePath: 'foo' } });
|
||||
expect(error).toHaveProperty('details');
|
||||
expect(error.details[0]).toHaveProperty('path', 'server.rewriteBasePath');
|
||||
expect(error.details[0]).toHaveProperty('path', ['server', 'rewriteBasePath']);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -110,7 +110,7 @@ describe('Config schema', function () {
|
|||
const { error } = validate(config);
|
||||
expect(error).toBeInstanceOf(Object);
|
||||
expect(error).toHaveProperty('details');
|
||||
expect(error.details[0]).toHaveProperty('path', 'server.ssl.enabled');
|
||||
expect(error.details[0]).toHaveProperty('path', ['server', 'ssl', 'enabled']);
|
||||
});
|
||||
|
||||
it('can be true', function () {
|
||||
|
@ -146,7 +146,7 @@ describe('Config schema', function () {
|
|||
const { error } = validate(config);
|
||||
expect(error).toBeInstanceOf(Object);
|
||||
expect(error).toHaveProperty('details');
|
||||
expect(error.details[0]).toHaveProperty('path', 'server.ssl.certificate');
|
||||
expect(error.details[0]).toHaveProperty('path', ['server', 'ssl', 'certificate']);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -165,7 +165,7 @@ describe('Config schema', function () {
|
|||
const { error } = validate(config);
|
||||
expect(error).toBeInstanceOf(Object);
|
||||
expect(error).toHaveProperty('details');
|
||||
expect(error.details[0]).toHaveProperty('path', 'server.ssl.key');
|
||||
expect(error.details[0]).toHaveProperty('path', ['server', 'ssl', 'key']);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -202,7 +202,7 @@ describe('Config schema', function () {
|
|||
const { error } = validate(config);
|
||||
expect(error).toBeInstanceOf(Object);
|
||||
expect(error).toHaveProperty('details');
|
||||
expect(error.details[0]).toHaveProperty('path', 'server.ssl.supportedProtocols.0');
|
||||
expect(error.details[0]).toHaveProperty('path', ['server', 'ssl', 'supportedProtocols', 0]);
|
||||
});
|
||||
|
||||
it('rejects SSLv3', function () {
|
||||
|
@ -211,7 +211,7 @@ describe('Config schema', function () {
|
|||
const { error } = validate(config);
|
||||
expect(error).toBeInstanceOf(Object);
|
||||
expect(error).toHaveProperty('details');
|
||||
expect(error.details[0]).toHaveProperty('path', 'server.ssl.supportedProtocols.0');
|
||||
expect(error.details[0]).toHaveProperty('path', ['server', 'ssl', 'supportedProtocols', 0]);
|
||||
});
|
||||
|
||||
it('accepts TLSv1, TLSv1.1, TLSv1.2', function () {
|
||||
|
@ -243,7 +243,7 @@ describe('Config schema', function () {
|
|||
const { error } = validate(config);
|
||||
expect(error).toBeInstanceOf(Object);
|
||||
expect(error).toHaveProperty('details');
|
||||
expect(error.details[0]).toHaveProperty('path', 'server.xsrf.whitelist.0');
|
||||
expect(error.details[0]).toHaveProperty('path', ['server', 'xsrf', 'whitelist', 0]);
|
||||
});
|
||||
|
||||
it('whitelist accepts paths that start with a slash.', () => {
|
||||
|
|
|
@ -28,14 +28,12 @@ import { setupBasePathProvider } from './setup_base_path_provider';
|
|||
import { setupXsrf } from './xsrf';
|
||||
|
||||
export default async function (kbnServer, server, config) {
|
||||
kbnServer.server = new Hapi.Server();
|
||||
kbnServer.server = new Hapi.Server(kbnServer.core.serverOptions);
|
||||
server = kbnServer.server;
|
||||
|
||||
server.connection(kbnServer.core.serverOptions);
|
||||
|
||||
setupBasePathProvider(server, config);
|
||||
|
||||
registerHapiPlugins(server);
|
||||
await registerHapiPlugins(server);
|
||||
|
||||
// provide a simple way to expose static directories
|
||||
server.decorate('server', 'exposeStaticDir', function (routePath, dirPath) {
|
||||
|
@ -63,7 +61,7 @@ export default async function (kbnServer, server, config) {
|
|||
});
|
||||
|
||||
// attach the app name to the server, so we can be sure we are actually talking to kibana
|
||||
server.ext('onPreResponse', function (req, reply) {
|
||||
server.ext('onPreResponse', function onPreResponse(req, h) {
|
||||
const response = req.response;
|
||||
|
||||
const customHeaders = {
|
||||
|
@ -82,32 +80,34 @@ export default async function (kbnServer, server, config) {
|
|||
});
|
||||
}
|
||||
|
||||
return reply.continue();
|
||||
return h.continue;
|
||||
});
|
||||
|
||||
server.route({
|
||||
path: '/',
|
||||
method: 'GET',
|
||||
handler(req, reply) {
|
||||
handler(req, h) {
|
||||
const basePath = req.getBasePath();
|
||||
const defaultRoute = config.get('server.defaultRoute');
|
||||
reply.redirect(`${basePath}${defaultRoute}`);
|
||||
return h.redirect(`${basePath}${defaultRoute}`);
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: '/{p*}',
|
||||
handler: function (req, reply) {
|
||||
handler: function (req, h) {
|
||||
const path = req.path;
|
||||
if (path === '/' || path.charAt(path.length - 1) !== '/') {
|
||||
return reply(Boom.notFound());
|
||||
throw Boom.notFound();
|
||||
}
|
||||
|
||||
const pathPrefix = req.getBasePath() ? `${req.getBasePath()}/` : '';
|
||||
return reply.redirect(format({
|
||||
search: req.url.search,
|
||||
pathname: pathPrefix + path.slice(0, -1),
|
||||
}))
|
||||
return h
|
||||
.redirect(format({
|
||||
search: req.url.search,
|
||||
pathname: pathPrefix + path.slice(0, -1),
|
||||
}))
|
||||
.permanent(true);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -29,7 +29,7 @@ beforeAll(async () => {
|
|||
path: '/payload_size_check/test/route',
|
||||
method: 'POST',
|
||||
config: { payload: { maxBytes: 200 } },
|
||||
handler: (req, reply) => reply(null, req.payload.data.slice(0, 5)),
|
||||
handler: (req) => req.payload.data.slice(0, 5),
|
||||
});
|
||||
}, 30000);
|
||||
|
||||
|
@ -41,12 +41,12 @@ test('accepts payload with a size larger than default but smaller than route con
|
|||
.expect(200, '+++++');
|
||||
});
|
||||
|
||||
test('fails with 400 if payload size is larger than default and route config allows', async () => {
|
||||
test('fails with 413 if payload size is larger than default and route config allows', async () => {
|
||||
await kbnTestServer.request.post(root, '/payload_size_check/test/route')
|
||||
.send({ data: Array(250).fill('+').join('') })
|
||||
.expect(400, {
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
.expect(413, {
|
||||
statusCode: 413,
|
||||
error: 'Request Entity Too Large',
|
||||
message: 'Payload content length greater than maximum allowed: 200'
|
||||
});
|
||||
});
|
||||
|
|
|
@ -35,8 +35,8 @@ describe('version_check request filter', function () {
|
|||
kbnTestServer.getKbnServer(root).server.route({
|
||||
path: '/version_check/test/route',
|
||||
method: 'GET',
|
||||
handler: function (req, reply) {
|
||||
reply(null, 'ok');
|
||||
handler: function () {
|
||||
return 'ok';
|
||||
}
|
||||
});
|
||||
}, 30000);
|
||||
|
|
|
@ -44,8 +44,8 @@ describe('xsrf request filter', () => {
|
|||
kbnServer.server.route({
|
||||
path: testPath,
|
||||
method: 'GET',
|
||||
handler: function (req, reply) {
|
||||
reply(null, 'ok');
|
||||
handler: async function () {
|
||||
return 'ok';
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -58,8 +58,8 @@ describe('xsrf request filter', () => {
|
|||
parse: false
|
||||
}
|
||||
},
|
||||
handler: function (req, reply) {
|
||||
reply(null, 'ok');
|
||||
handler: async function () {
|
||||
return 'ok';
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -72,8 +72,8 @@ describe('xsrf request filter', () => {
|
|||
parse: false
|
||||
}
|
||||
},
|
||||
handler: function (req, reply) {
|
||||
reply(null, 'ok');
|
||||
handler: async function () {
|
||||
return 'ok';
|
||||
}
|
||||
});
|
||||
}, 30000);
|
||||
|
|
|
@ -20,14 +20,11 @@
|
|||
import HapiTemplates from 'vision';
|
||||
import HapiStaticFiles from 'inert';
|
||||
import HapiProxy from 'h2o2';
|
||||
import { fromNode } from 'bluebird';
|
||||
|
||||
const plugins = [HapiTemplates, HapiStaticFiles, HapiProxy];
|
||||
|
||||
async function registerPlugins(server) {
|
||||
await fromNode(cb => {
|
||||
server.register(plugins, cb);
|
||||
});
|
||||
return await server.register(plugins);
|
||||
}
|
||||
|
||||
export function registerHapiPlugins(server) {
|
||||
|
|
|
@ -23,16 +23,16 @@ export function setupVersionCheck(server, config) {
|
|||
const versionHeader = 'kbn-version';
|
||||
const actualVersion = config.get('pkg.version');
|
||||
|
||||
server.ext('onPostAuth', function (req, reply) {
|
||||
server.ext('onPostAuth', function onPostAuthVersionCheck(req, h) {
|
||||
const versionRequested = req.headers[versionHeader];
|
||||
|
||||
if (versionRequested && versionRequested !== actualVersion) {
|
||||
return reply(badRequest('Browser client is out of date, please refresh the page', {
|
||||
throw badRequest('Browser client is out of date, please refresh the page', {
|
||||
expected: actualVersion,
|
||||
got: versionRequested
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
return reply.continue();
|
||||
return h.continue;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -25,13 +25,13 @@ export function setupXsrf(server, config) {
|
|||
const versionHeader = 'kbn-version';
|
||||
const xsrfHeader = 'kbn-xsrf';
|
||||
|
||||
server.ext('onPostAuth', function (req, reply) {
|
||||
server.ext('onPostAuth', function onPostAuthXsrf(req, h) {
|
||||
if (disabled) {
|
||||
return reply.continue();
|
||||
return h.continue;
|
||||
}
|
||||
|
||||
if (whitelist.includes(req.path)) {
|
||||
return reply.continue();
|
||||
return h.continue;
|
||||
}
|
||||
|
||||
const isSafeMethod = req.method === 'get' || req.method === 'head';
|
||||
|
@ -39,9 +39,9 @@ export function setupXsrf(server, config) {
|
|||
const hasXsrfHeader = xsrfHeader in req.headers;
|
||||
|
||||
if (!isSafeMethod && !hasVersionHeader && !hasXsrfHeader) {
|
||||
return reply(badRequest(`Request must contain a ${xsrfHeader} header.`));
|
||||
throw badRequest(`Request must contain a ${xsrfHeader} header.`);
|
||||
}
|
||||
|
||||
return reply.continue();
|
||||
return h.continue;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -32,8 +32,8 @@ export function indexPatternsMixin(kbnServer, server) {
|
|||
*/
|
||||
getIndexPatternsService: {
|
||||
assign: 'indexPatterns',
|
||||
method(request, reply) {
|
||||
reply(request.getIndexPatternsService());
|
||||
method(request) {
|
||||
return request.getIndexPatternsService();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -31,7 +31,7 @@ export const createFieldsForTimePatternRoute = pre => ({
|
|||
meta_fields: Joi.array().items(Joi.string()).default([]),
|
||||
}).default()
|
||||
},
|
||||
handler(req, reply) {
|
||||
async handler(req) {
|
||||
const { indexPatterns } = req.pre;
|
||||
const {
|
||||
pattern,
|
||||
|
@ -40,15 +40,14 @@ export const createFieldsForTimePatternRoute = pre => ({
|
|||
meta_fields: metaFields,
|
||||
} = req.query;
|
||||
|
||||
reply(
|
||||
indexPatterns.getFieldsForTimePattern({
|
||||
pattern,
|
||||
interval,
|
||||
lookBack,
|
||||
metaFields
|
||||
})
|
||||
.then(fields => ({ fields }))
|
||||
);
|
||||
const fields = await indexPatterns.getFieldsForTimePattern({
|
||||
pattern,
|
||||
interval,
|
||||
lookBack,
|
||||
metaFields
|
||||
});
|
||||
|
||||
return { fields };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -30,20 +30,19 @@ export const createFieldsForWildcardRoute = pre => ({
|
|||
meta_fields: Joi.array().items(Joi.string()).default([]),
|
||||
}).default()
|
||||
},
|
||||
handler(req, reply) {
|
||||
async handler(req) {
|
||||
const { indexPatterns } = req.pre;
|
||||
const {
|
||||
pattern,
|
||||
meta_fields: metaFields,
|
||||
} = req.query;
|
||||
|
||||
reply(
|
||||
indexPatterns.getFieldsForWildcard({
|
||||
pattern,
|
||||
metaFields
|
||||
})
|
||||
.then(fields => ({ fields }))
|
||||
);
|
||||
const fields = await indexPatterns.getFieldsForWildcard({
|
||||
pattern,
|
||||
metaFields
|
||||
});
|
||||
|
||||
return { fields };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
*/
|
||||
|
||||
import { constant, once, compact, flatten } from 'lodash';
|
||||
import { fromNode } from 'bluebird';
|
||||
import { isWorker } from 'cluster';
|
||||
import { fromRoot, pkg } from '../utils';
|
||||
import { Config } from './config';
|
||||
|
@ -161,7 +160,7 @@ export default class KbnServer {
|
|||
return;
|
||||
}
|
||||
|
||||
await fromNode(cb => this.server.stop(cb));
|
||||
await this.server.stop();
|
||||
}
|
||||
|
||||
async inject(opts) {
|
||||
|
@ -185,6 +184,6 @@ export default class KbnServer {
|
|||
};
|
||||
const plain = JSON.stringify(subset, null, 2);
|
||||
this.server.log(['info', 'config'], 'New logging configuration:\n' + plain);
|
||||
this.server.plugins['even-better'].monitor.reconfigure(loggingOptions);
|
||||
this.server.plugins['@elastic/good'].reconfigure(loggingOptions);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import logReporter from './log_reporter';
|
||||
import { getLoggerStream } from './log_reporter';
|
||||
|
||||
export default function loggingConfiguration(config) {
|
||||
const events = config.get('logging.events');
|
||||
|
@ -51,33 +51,37 @@ export default function loggingConfiguration(config) {
|
|||
});
|
||||
}
|
||||
|
||||
const options = {
|
||||
opsInterval: config.get('ops.interval'),
|
||||
requestHeaders: true,
|
||||
requestPayload: true,
|
||||
reporters: [
|
||||
{
|
||||
reporter: logReporter,
|
||||
config: {
|
||||
json: config.get('logging.json'),
|
||||
dest: config.get('logging.dest'),
|
||||
timezone: config.get('logging.timezone'),
|
||||
const loggerStream = getLoggerStream({
|
||||
config: {
|
||||
json: config.get('logging.json'),
|
||||
dest: config.get('logging.dest'),
|
||||
timezone: config.get('logging.timezone'),
|
||||
|
||||
// I'm adding the default here because if you add another filter
|
||||
// using the commandline it will remove authorization. I want users
|
||||
// to have to explicitly set --logging.filter.authorization=none or
|
||||
// --logging.filter.cookie=none to have it show up in the logs.
|
||||
filter: _.defaults(config.get('logging.filter'), {
|
||||
authorization: 'remove',
|
||||
cookie: 'remove'
|
||||
})
|
||||
},
|
||||
events: _.transform(events, function (filtered, val, key) {
|
||||
// provide a string compatible way to remove events
|
||||
if (val !== '!') filtered[key] = val;
|
||||
}, {})
|
||||
}
|
||||
]
|
||||
// I'm adding the default here because if you add another filter
|
||||
// using the commandline it will remove authorization. I want users
|
||||
// to have to explicitly set --logging.filter.authorization=none or
|
||||
// --logging.filter.cookie=none to have it show up in the logs.
|
||||
filter: _.defaults(config.get('logging.filter'), {
|
||||
authorization: 'remove',
|
||||
cookie: 'remove'
|
||||
})
|
||||
},
|
||||
events: _.transform(events, function (filtered, val, key) {
|
||||
// provide a string compatible way to remove events
|
||||
if (val !== '!') filtered[key] = val;
|
||||
}, {})
|
||||
});
|
||||
|
||||
const options = {
|
||||
ops: {
|
||||
interval: config.get('ops.interval')
|
||||
},
|
||||
includes: {
|
||||
request: ['headers', 'payload']
|
||||
},
|
||||
reporters: {
|
||||
logReporter: [loggerStream]
|
||||
}
|
||||
};
|
||||
return options;
|
||||
}
|
||||
|
|
|
@ -17,16 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { fromNode } from 'bluebird';
|
||||
import evenBetter from 'even-better';
|
||||
import good from '@elastic/good';
|
||||
import loggingConfiguration from './configuration';
|
||||
|
||||
export function setupLogging(server, config) {
|
||||
return fromNode((cb) => {
|
||||
server.register({
|
||||
register: evenBetter,
|
||||
options: loggingConfiguration(config)
|
||||
}, cb);
|
||||
export async function setupLogging(server, config) {
|
||||
return await server.register({
|
||||
plugin: good,
|
||||
options: loggingConfiguration(config)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ function doesMessageMatch(errorMessage, match) {
|
|||
// converts the given event into a debug log if it's an error of the given type
|
||||
function downgradeIfErrorType(errorType, event, field = 'errno') {
|
||||
const isClientError = doTagsMatch(event, ['connection', 'client', 'error']);
|
||||
const matchesErrorType = isClientError && get(event, `data.${field}`) === errorType;
|
||||
const matchesErrorType = isClientError && get(event, `error.${field}`) === errorType;
|
||||
|
||||
if (!matchesErrorType) return null;
|
||||
|
||||
|
@ -52,7 +52,7 @@ function downgradeIfErrorType(errorType, event, field = 'errno') {
|
|||
|
||||
function downgradeIfErrorMessage(match, event) {
|
||||
const isClientError = doTagsMatch(event, ['connection', 'client', 'error']);
|
||||
const errorMessage = get(event, 'data.message');
|
||||
const errorMessage = get(event, 'error.message');
|
||||
const matchesErrorMessage = isClientError && doesMessageMatch(errorMessage, match);
|
||||
|
||||
if (!matchesErrorMessage) return null;
|
||||
|
|
|
@ -27,7 +27,7 @@ function stubClientErrorEvent(errorMeta) {
|
|||
pid: 1234,
|
||||
timestamp: Date.now(),
|
||||
tags: ['connection', 'client', 'error'],
|
||||
data: error
|
||||
error
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -24,38 +24,29 @@ import LogFormatJson from './log_format_json';
|
|||
import LogFormatString from './log_format_string';
|
||||
import { LogInterceptor } from './log_interceptor';
|
||||
|
||||
export default class KbnLogger {
|
||||
constructor(events, config) {
|
||||
this.squeeze = new Squeeze(events);
|
||||
this.format = config.json ? new LogFormatJson(config) : new LogFormatString(config);
|
||||
this.logInterceptor = new LogInterceptor();
|
||||
export function getLoggerStream({ events, config }) {
|
||||
const squeeze = new Squeeze(events);
|
||||
const format = config.json ? new LogFormatJson(config) : new LogFormatString(config);
|
||||
const logInterceptor = new LogInterceptor();
|
||||
|
||||
if (config.dest === 'stdout') {
|
||||
this.dest = process.stdout;
|
||||
} else {
|
||||
this.dest = writeStr(config.dest, {
|
||||
flags: 'a',
|
||||
encoding: 'utf8'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
init(readstream, emitter, callback) {
|
||||
|
||||
this.output = readstream
|
||||
.pipe(this.logInterceptor)
|
||||
.pipe(this.squeeze)
|
||||
.pipe(this.format);
|
||||
|
||||
this.output.pipe(this.dest);
|
||||
|
||||
emitter.on('stop', () => {
|
||||
this.output.unpipe(this.dest);
|
||||
if (this.dest !== process.stdout) {
|
||||
this.dest.end();
|
||||
}
|
||||
let dest;
|
||||
if (config.dest === 'stdout') {
|
||||
dest = process.stdout;
|
||||
} else {
|
||||
dest = writeStr(config.dest, {
|
||||
flags: 'a',
|
||||
encoding: 'utf8'
|
||||
});
|
||||
|
||||
callback();
|
||||
logInterceptor.on('end', () => {
|
||||
dest.end();
|
||||
});
|
||||
}
|
||||
|
||||
logInterceptor
|
||||
.pipe(squeeze)
|
||||
.pipe(format)
|
||||
.pipe(dest);
|
||||
|
||||
return logInterceptor;
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ export default Promise.method(function (kbnServer, server, config) {
|
|||
};
|
||||
|
||||
if (config.get('pid.exclusive')) {
|
||||
throw Boom.create(500, _.template(log.tmpl)(log), log);
|
||||
throw Boom.internal(_.template(log.tmpl)(log), log);
|
||||
} else {
|
||||
server.log(['pid', 'warning'], log);
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ export class Plugin {
|
|||
const { config } = kbnServer;
|
||||
|
||||
// setup the hapi register function and get on with it
|
||||
const asyncRegister = async (server, options) => {
|
||||
const register = async (server, options) => {
|
||||
this._server = server;
|
||||
this._options = options;
|
||||
|
||||
|
@ -85,15 +85,8 @@ export class Plugin {
|
|||
}
|
||||
};
|
||||
|
||||
const register = (server, options, next) => {
|
||||
asyncRegister(server, options)
|
||||
.then(() => next(), next);
|
||||
};
|
||||
|
||||
register.attributes = { name: id, version: version };
|
||||
|
||||
await kbnServer.server.register({
|
||||
register: register,
|
||||
plugin: { register, name: id, version },
|
||||
options: config.has(configPrefix) ? config.get(configPrefix) : null
|
||||
});
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
import Joi from 'joi';
|
||||
|
||||
import { loadData } from './lib/load_data';
|
||||
|
@ -75,13 +76,13 @@ export const createInstallRoute = () => ({
|
|||
id: Joi.string().required(),
|
||||
}).required()
|
||||
},
|
||||
handler: async (request, reply) => {
|
||||
handler: async (request, h) => {
|
||||
const server = request.server;
|
||||
const sampleDataset = server.getSampleDatasets().find(sampleDataset => {
|
||||
return sampleDataset.id === request.params.id;
|
||||
});
|
||||
if (!sampleDataset) {
|
||||
return reply().code(404);
|
||||
return h.response().code(404);
|
||||
}
|
||||
|
||||
const { callWithRequest } = server.plugins.elasticsearch.getCluster('data');
|
||||
|
@ -122,7 +123,7 @@ export const createInstallRoute = () => ({
|
|||
} catch (err) {
|
||||
const errMsg = `Unable to create sample data index "${index}", error: ${err.message}`;
|
||||
server.log(['warning'], errMsg);
|
||||
return reply(errMsg).code(err.status);
|
||||
return h.response(errMsg).code(err.status);
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -131,7 +132,7 @@ export const createInstallRoute = () => ({
|
|||
counts[index] = count;
|
||||
} catch (err) {
|
||||
server.log(['warning'], `sample_data install errors while loading data. Error: ${err}`);
|
||||
return reply(err.message).code(500);
|
||||
return h.response(err.message).code(500);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,17 +141,17 @@ export const createInstallRoute = () => ({
|
|||
createResults = await request.getSavedObjectsClient().bulkCreate(sampleDataset.savedObjects, { overwrite: true });
|
||||
} catch (err) {
|
||||
server.log(['warning'], `bulkCreate failed, error: ${err.message}`);
|
||||
return reply(`Unable to load kibana saved objects, see kibana logs for details`).code(500);
|
||||
return Boom.badImplementation(`Unable to load kibana saved objects, see kibana logs for details`);
|
||||
}
|
||||
const errors = createResults.saved_objects.filter(savedObjectCreateResult => {
|
||||
return savedObjectCreateResult.hasOwnProperty('error');
|
||||
});
|
||||
if (errors.length > 0) {
|
||||
server.log(['warning'], `sample_data install errors while loading saved objects. Errors: ${errors.join(',')}`);
|
||||
return reply(`Unable to load kibana saved objects, see kibana logs for details`).code(403);
|
||||
return h.response(`Unable to load kibana saved objects, see kibana logs for details`).code(403);
|
||||
}
|
||||
|
||||
return reply({ elasticsearchIndicesCreated: counts, kibanaSavedObjectsLoaded: sampleDataset.savedObjects.length });
|
||||
return h.response({ elasticsearchIndicesCreated: counts, kibanaSavedObjectsLoaded: sampleDataset.savedObjects.length });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -28,7 +28,7 @@ export const createListRoute = () => ({
|
|||
path: '/api/sample_data',
|
||||
method: 'GET',
|
||||
config: {
|
||||
handler: async (request, reply) => {
|
||||
handler: async (request) => {
|
||||
const { callWithRequest } = request.server.plugins.elasticsearch.getCluster('data');
|
||||
|
||||
const sampleDatasets = request.server.getSampleDatasets().map(sampleDataset => {
|
||||
|
@ -86,7 +86,7 @@ export const createListRoute = () => ({
|
|||
});
|
||||
|
||||
await Promise.all(isInstalledPromises);
|
||||
reply(sampleDatasets);
|
||||
return sampleDatasets;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -31,15 +31,14 @@ export const createUninstallRoute = () => ({
|
|||
id: Joi.string().required(),
|
||||
}).required()
|
||||
},
|
||||
handler: async (request, reply) => {
|
||||
handler: async (request, h) => {
|
||||
const server = request.server;
|
||||
const sampleDataset = server.getSampleDatasets().find(({ id }) => {
|
||||
return id === request.params.id;
|
||||
});
|
||||
|
||||
if (!sampleDataset) {
|
||||
reply().code(404);
|
||||
return;
|
||||
return h.response().code(404);
|
||||
}
|
||||
|
||||
const { callWithRequest } = server.plugins.elasticsearch.getCluster('data');
|
||||
|
@ -51,7 +50,7 @@ export const createUninstallRoute = () => ({
|
|||
try {
|
||||
await callWithRequest(request, 'indices.delete', { index: index });
|
||||
} catch (err) {
|
||||
return reply(`Unable to delete sample data index "${index}", error: ${err.message}`).code(err.status);
|
||||
return h.response(`Unable to delete sample data index "${index}", error: ${err.message}`).code(err.status);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,11 +62,11 @@ export const createUninstallRoute = () => ({
|
|||
} catch (err) {
|
||||
// ignore 404s since users could have deleted some of the saved objects via the UI
|
||||
if (_.get(err, 'output.statusCode') !== 404) {
|
||||
return reply(`Unable to delete sample dataset saved objects, error: ${err.message}`).code(403);
|
||||
return h.response(`Unable to delete sample dataset saved objects, error: ${err.message}`).code(403);
|
||||
}
|
||||
}
|
||||
|
||||
reply({});
|
||||
return {};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -17,14 +17,22 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
const Hapi = require('hapi');
|
||||
import Hapi from 'hapi';
|
||||
import { defaultValidationErrorHandler } from '../../../core/server/http/http_tools';
|
||||
|
||||
const defaultConfig = {
|
||||
'kibana.index': '.kibana'
|
||||
};
|
||||
|
||||
export function MockServer(config = defaultConfig) {
|
||||
const server = new Hapi.Server();
|
||||
server.connection({ port: 8080 });
|
||||
const server = new Hapi.Server({
|
||||
port: 0,
|
||||
routes: {
|
||||
validate: {
|
||||
failAction: defaultValidationErrorHandler
|
||||
}
|
||||
}
|
||||
});
|
||||
server.config = function () {
|
||||
return {
|
||||
get: (key) => {
|
||||
|
|
|
@ -40,11 +40,11 @@ export const createBulkCreateRoute = prereqs => ({
|
|||
}).required()
|
||||
),
|
||||
},
|
||||
handler(request, reply) {
|
||||
handler(request) {
|
||||
const { overwrite } = request.query;
|
||||
const { savedObjectsClient } = request.pre;
|
||||
|
||||
reply(savedObjectsClient.bulkCreate(request.payload, { overwrite }));
|
||||
return savedObjectsClient.bulkCreate(request.payload, { overwrite });
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -30,10 +30,10 @@ export const createBulkGetRoute = (prereqs) => ({
|
|||
id: Joi.string().required(),
|
||||
}).required())
|
||||
},
|
||||
handler(request, reply) {
|
||||
handler(request) {
|
||||
const { savedObjectsClient } = request.pre;
|
||||
|
||||
reply(savedObjectsClient.bulkGet(request.payload));
|
||||
return savedObjectsClient.bulkGet(request.payload);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -22,7 +22,7 @@ import { createBulkGetRoute } from './bulk_get';
|
|||
import { MockServer } from './_mock_server';
|
||||
|
||||
describe('POST /api/saved_objects/_bulk_get', () => {
|
||||
const savedObjectsClient = { bulkGet: sinon.stub() };
|
||||
const savedObjectsClient = { bulkGet: sinon.stub().returns('') };
|
||||
let server;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -31,8 +31,8 @@ describe('POST /api/saved_objects/_bulk_get', () => {
|
|||
const prereqs = {
|
||||
getSavedObjectsClient: {
|
||||
assign: 'savedObjectsClient',
|
||||
method(request, reply) {
|
||||
reply(savedObjectsClient);
|
||||
method() {
|
||||
return savedObjectsClient;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ export const createCreateRoute = prereqs => {
|
|||
return {
|
||||
path: '/api/saved_objects/{type}/{id?}',
|
||||
method: 'POST',
|
||||
config: {
|
||||
options: {
|
||||
pre: [prereqs.getSavedObjectsClient],
|
||||
validate: {
|
||||
query: Joi.object()
|
||||
|
@ -42,14 +42,14 @@ export const createCreateRoute = prereqs => {
|
|||
migrationVersion: Joi.object().optional(),
|
||||
}).required(),
|
||||
},
|
||||
handler(request, reply) {
|
||||
handler(request) {
|
||||
const { savedObjectsClient } = request.pre;
|
||||
const { type, id } = request.params;
|
||||
const { overwrite } = request.query;
|
||||
const { migrationVersion } = request.payload;
|
||||
const options = { id, overwrite, migrationVersion };
|
||||
|
||||
reply(savedObjectsClient.create(type, request.payload.attributes, options));
|
||||
return savedObjectsClient.create(type, request.payload.attributes, options);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -22,7 +22,7 @@ import { createCreateRoute } from './create';
|
|||
import { MockServer } from './_mock_server';
|
||||
|
||||
describe('POST /api/saved_objects/{type}', () => {
|
||||
const savedObjectsClient = { create: sinon.stub() };
|
||||
const savedObjectsClient = { create: sinon.stub().returns('') };
|
||||
let server;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -31,8 +31,8 @@ describe('POST /api/saved_objects/{type}', () => {
|
|||
const prereqs = {
|
||||
getSavedObjectsClient: {
|
||||
assign: 'savedObjectsClient',
|
||||
method(request, reply) {
|
||||
reply(savedObjectsClient);
|
||||
method() {
|
||||
return savedObjectsClient;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -100,7 +100,7 @@ describe('POST /api/saved_objects/{type}', () => {
|
|||
expect(savedObjectsClient.create.calledOnce).toBe(true);
|
||||
|
||||
const args = savedObjectsClient.create.getCall(0).args;
|
||||
const options = { overwrite: false, id: undefined };
|
||||
const options = { overwrite: false, id: undefined, migrationVersion: undefined };
|
||||
const attributes = { title: 'Testing' };
|
||||
|
||||
expect(args).toEqual(['index-pattern', attributes, options]);
|
||||
|
|
|
@ -30,11 +30,11 @@ export const createDeleteRoute = (prereqs) => ({
|
|||
id: Joi.string().required(),
|
||||
}).required()
|
||||
},
|
||||
handler(request, reply) {
|
||||
handler(request) {
|
||||
const { savedObjectsClient } = request.pre;
|
||||
const { type, id } = request.params;
|
||||
|
||||
reply(savedObjectsClient.delete(type, id));
|
||||
return savedObjectsClient.delete(type, id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -31,8 +31,8 @@ describe('DELETE /api/saved_objects/{type}/{id}', () => {
|
|||
const prereqs = {
|
||||
getSavedObjectsClient: {
|
||||
assign: 'savedObjectsClient',
|
||||
method(request, reply) {
|
||||
reply(savedObjectsClient);
|
||||
method() {
|
||||
return savedObjectsClient;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -36,9 +36,9 @@ export const createFindRoute = (prereqs) => ({
|
|||
fields: Joi.array().items(Joi.string()).single()
|
||||
}).default()
|
||||
},
|
||||
handler(request, reply) {
|
||||
handler(request) {
|
||||
const options = keysToCamelCaseShallow(request.query);
|
||||
reply(request.pre.savedObjectsClient.find(options));
|
||||
return request.pre.savedObjectsClient.find(options);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -22,7 +22,7 @@ import { createFindRoute } from './find';
|
|||
import { MockServer } from './_mock_server';
|
||||
|
||||
describe('GET /api/saved_objects/_find', () => {
|
||||
const savedObjectsClient = { find: sinon.stub() };
|
||||
const savedObjectsClient = { find: sinon.stub().returns('') };
|
||||
let server;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -31,8 +31,8 @@ describe('GET /api/saved_objects/_find', () => {
|
|||
const prereqs = {
|
||||
getSavedObjectsClient: {
|
||||
assign: 'savedObjectsClient',
|
||||
method(request, reply) {
|
||||
reply(savedObjectsClient);
|
||||
method() {
|
||||
return savedObjectsClient;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -30,11 +30,11 @@ export const createGetRoute = (prereqs) => ({
|
|||
id: Joi.string().required(),
|
||||
}).required()
|
||||
},
|
||||
handler(request, reply) {
|
||||
handler(request) {
|
||||
const { savedObjectsClient } = request.pre;
|
||||
const { type, id } = request.params;
|
||||
|
||||
reply(savedObjectsClient.get(type, id));
|
||||
return savedObjectsClient.get(type, id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -22,7 +22,7 @@ import { createGetRoute } from './get';
|
|||
import { MockServer } from './_mock_server';
|
||||
|
||||
describe('GET /api/saved_objects/{type}/{id}', () => {
|
||||
const savedObjectsClient = { get: sinon.stub() };
|
||||
const savedObjectsClient = { get: sinon.stub().returns('') };
|
||||
let server;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -31,8 +31,8 @@ describe('GET /api/saved_objects/{type}/{id}', () => {
|
|||
const prereqs = {
|
||||
getSavedObjectsClient: {
|
||||
assign: 'savedObjectsClient',
|
||||
method(request, reply) {
|
||||
reply(savedObjectsClient);
|
||||
method() {
|
||||
return savedObjectsClient;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -35,13 +35,13 @@ export const createUpdateRoute = (prereqs) => {
|
|||
version: Joi.number().min(1)
|
||||
}).required()
|
||||
},
|
||||
handler(request, reply) {
|
||||
handler(request) {
|
||||
const { savedObjectsClient } = request.pre;
|
||||
const { type, id } = request.params;
|
||||
const { attributes, version } = request.payload;
|
||||
const options = { version };
|
||||
|
||||
reply(savedObjectsClient.update(type, id, attributes, options));
|
||||
return savedObjectsClient.update(type, id, attributes, options);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -22,7 +22,7 @@ import { createUpdateRoute } from './update';
|
|||
import { MockServer } from './_mock_server';
|
||||
|
||||
describe('PUT /api/saved_objects/{type}/{id?}', () => {
|
||||
const savedObjectsClient = { update: sinon.stub() };
|
||||
const savedObjectsClient = { update: sinon.stub().returns('') };
|
||||
let server;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -31,8 +31,8 @@ describe('PUT /api/saved_objects/{type}/{id?}', () => {
|
|||
const prereqs = {
|
||||
getSavedObjectsClient: {
|
||||
assign: 'savedObjectsClient',
|
||||
method(request, reply) {
|
||||
reply(savedObjectsClient);
|
||||
method() {
|
||||
return savedObjectsClient;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -51,8 +51,8 @@ export function savedObjectsMixin(kbnServer, server) {
|
|||
const prereqs = {
|
||||
getSavedObjectsClient: {
|
||||
assign: 'savedObjectsClient',
|
||||
method(req, reply) {
|
||||
reply(req.getSavedObjectsClient());
|
||||
method(req) {
|
||||
return req.getSavedObjectsClient();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -21,6 +21,8 @@ import ServerStatus from './server_status';
|
|||
import { Metrics } from './lib/metrics';
|
||||
import { registerStatusPage, registerStatusApi, registerStatsApi } from './routes';
|
||||
import { getOpsStatsCollector } from './collectors';
|
||||
import Oppsy from 'oppsy';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
export function statusMixin(kbnServer, server, config) {
|
||||
kbnServer.status = new ServerStatus(kbnServer.server);
|
||||
|
@ -29,15 +31,23 @@ export function statusMixin(kbnServer, server, config) {
|
|||
const { collectorSet } = server.usage;
|
||||
collectorSet.register(statsCollector);
|
||||
|
||||
const { ['even-better']: evenBetter } = server.plugins;
|
||||
const metrics = new Metrics(config, server);
|
||||
|
||||
if (evenBetter) {
|
||||
const metrics = new Metrics(config, server);
|
||||
const oppsy = new Oppsy(server);
|
||||
oppsy.on('ops', event => {
|
||||
// Oppsy has a bad race condition that will modify this data before
|
||||
// we ship it off to the buffer. Let's create our copy first.
|
||||
event = cloneDeep(event);
|
||||
// Oppsy used to provide this, but doesn't anymore. Grab it ourselves.
|
||||
server.listener.getConnections((_, count) => {
|
||||
event.concurrent_connections = count;
|
||||
|
||||
evenBetter.monitor.on('ops', event => {
|
||||
metrics.capture(event).then(data => { kbnServer.metrics = data; }); // captures (performs transforms on) the latest event data and stashes the metrics for status/stats API payload
|
||||
// captures (performs transforms on) the latest event data and stashes
|
||||
// the metrics for status/stats API payload
|
||||
metrics.capture(event).then(data => { kbnServer.metrics = data; });
|
||||
});
|
||||
}
|
||||
});
|
||||
oppsy.start(config.get('ops.interval'));
|
||||
|
||||
// init routes
|
||||
registerStatusPage(kbnServer, server, config);
|
||||
|
|
|
@ -23,6 +23,12 @@ import { get, isObject, merge } from 'lodash';
|
|||
import { keysToSnakeCaseShallow } from '../../../utils/case_conversion';
|
||||
import { getAllStats as cGroupStats } from './cgroup';
|
||||
|
||||
const requestDefaults = {
|
||||
disconnects: 0,
|
||||
statusCodes: {},
|
||||
total: 0,
|
||||
};
|
||||
|
||||
export class Metrics {
|
||||
constructor(config, server) {
|
||||
this.config = config;
|
||||
|
@ -98,8 +104,11 @@ export class Metrics {
|
|||
avg_in_millis: isNaN(avgInMillis) ? undefined : avgInMillis, // convert NaN to undefined
|
||||
max_in_millis: maxInMillis
|
||||
},
|
||||
requests: keysToSnakeCaseShallow(get(hapiEvent, ['requests', port])),
|
||||
concurrent_connections: get(hapiEvent, ['concurrents', port]),
|
||||
requests: {
|
||||
...requestDefaults,
|
||||
...keysToSnakeCaseShallow(get(hapiEvent, ['requests', port]))
|
||||
},
|
||||
concurrent_connections: hapiEvent.concurrent_connections
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ describe('Metrics', function () {
|
|||
'osup': 1008991,
|
||||
'psup': 7.168,
|
||||
'psmem': { 'rss': 193716224, 'heapTotal': 168194048, 'heapUsed': 130553400, 'external': 1779619 },
|
||||
'concurrents': { '5603': 0 },
|
||||
'concurrent_connections': 0,
|
||||
'psdelay': 1.6091690063476562,
|
||||
'host': 'blahblah.local'
|
||||
};
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue