Migrate x-pack-kibana source to kibana

This commit is contained in:
Jenkins CI 2018-04-20 19:13:41 +00:00 committed by spalger
parent e8ac7d8d32
commit bc5b51554d
3256 changed files with 277621 additions and 2324 deletions

View file

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { ROUTES } from './routes';
export { PLUGIN } from './plugin';

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export const PLUGIN = {
ID: 'grokdebugger'
};

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export const ROUTES = {
API_ROOT: '/api/grokdebugger',
};

View file

@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { resolve } from 'path';
import { PLUGIN } from './common/constants';
import { registerGrokdebuggerRoutes } from './server/routes/api/grokdebugger';
import { registerLicenseChecker } from './server/lib/register_license_checker';
export const grokdebugger = (kibana) => new kibana.Plugin({
id: PLUGIN.ID,
publicDir: resolve(__dirname, 'public'),
require: ['kibana', 'elasticsearch', 'xpack_main'],
configPrefix: 'xpack.grokdebugger',
config(Joi) {
return Joi.object({
enabled: Joi.boolean().default(true)
}).default();
},
uiExports: {
devTools: ['plugins/grokdebugger/sections/grokdebugger'],
hacks: ['plugins/grokdebugger/sections/grokdebugger/register'],
home: ['plugins/grokdebugger/register_feature'],
},
init: (server) => {
registerLicenseChecker(server);
registerGrokdebuggerRoutes(server);
}
});

View file

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<g fill="none" transform="translate(2)">
<path fill="#14A7DF" d="M25.2,14.1217391 C24.22,13.1478261 23.94,11.8956522 23.94,10.3652174 L23.94,10.1565217 L23.94,3.75652174 C23.94,2.50434783 23.45,1.53043478 22.82,0.973913043 C22.19,0.347826087 20.79,0.0695652174 19.04,4e-16 L15.54,4e-16 L9.94,32 L19.04,32 C20.72,31.9304348 22.19,31.6521739 22.82,31.026087 C23.45,30.4 23.94,29.4956522 23.94,28.2434783 L23.94,21.8434783 L23.94,21.6347826 C23.94,20.1043478 24.22,18.8521739 25.2,17.8782609 C25.69,17.3913043 26.32,16.973913 27.09,16.6956522 C27.65,16.4173913 27.65,15.6521739 27.09,15.373913 C26.32,15.026087 25.69,14.6086957 25.2,14.1217391 Z"/>
<path fill="#00BFB3" d="M2.94,17.8782609 C3.99,18.8521739 4.34,20.1043478 4.34,21.6347826 L4.34,21.8434783 L4.34,28.2434783 C4.34,29.4956522 4.83,30.4695652 5.46,31.026087 C6.09,31.6521739 7.56,32 9.38,32 L12.74,32 L18.34,-7.1e-15 L9.38,-7.1e-15 C7.56,-7.1e-15 6.16,0.347826087 5.46,0.973913043 C4.83,1.6 4.34,2.50434783 4.34,3.75652174 L4.34,10.1565217 L4.34,10.3652174 C4.34,11.8956522 3.99,13.1478261 2.94,14.1217391 C2.38,14.6086957 1.75,15.026087 0.91,15.3043478 C0.28,15.5130435 0.28,16.3478261 0.91,16.626087 C1.68,16.973913 2.38,17.3913043 2.94,17.8782609 Z"/>
<polygon fill="#0078A0" points="15.54 0 9.94 32 12.74 32 18.34 0"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { get, pick } from 'lodash';
export class GrokdebuggerRequest {
constructor(props = {}) {
this.rawEvent = get(props, 'rawEvent', '');
this.pattern = get(props, 'pattern', '');
this.customPatterns = get(props, 'customPatterns', {});
}
get upstreamJSON() {
return pick(this, [ 'rawEvent', 'pattern', 'customPatterns' ]);
}
}

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { GrokdebuggerRequest } from './grokdebugger_request';

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { get } from 'lodash';
export class GrokdebuggerResponse {
constructor(props) {
this.structuredEvent = get(props, 'structuredEvent', {});
this.error = get(props, 'error', {});
}
static fromUpstreamJSON(grokdebuggerResponse) {
return new GrokdebuggerResponse(grokdebuggerResponse);
}
}

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { GrokdebuggerResponse } from './grokdebugger_response';

View file

@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
FeatureCatalogueRegistryProvider.register(() => {
return {
id: 'grokdebugger',
title: 'Grok Debugger',
description: 'Simulate and debug grok patterns for data transformation on ingestion.',
icon: '/plugins/grokdebugger/assets/app_grok.svg',
path: '/app/kibana#/dev_tools/grokdebugger',
showOnHomePage: false,
category: FeatureCatalogueCategory.ADMIN
};
});

View file

@ -0,0 +1,36 @@
<form novalidate>
<toggle-panel
toggle-panel-id="action"
button-text="Custom Patterns"
is-collapsed="customPatternsInput.isSectionCollapsed('action')"
on-toggle="customPatternsInput.onSectionToggle"
data-test-subj="btnToggleCustomPatternsInput"
>
<div class="kuiFormSection">
<div class="kuiInfoPanel kuiInfoPanel--info kuiVerticalRhythm">
<div class="kuiInfoPanelHeader">
<span class="kuiInfoPanelHeader__icon kuiIcon kuiIcon--info fa-info"></span>
<span class="kuiInfoPanelHeader__title">
Enter one custom pattern per line. For example:
</span>
</div>
<div class="kuiInfoPanelBody">
<div class="kuiInfoPanelBody__message">
<pre><code>POSTFIX_QUEUEID [0-9A-F]{10,11}
MSG message-id=<%{GREEDYDATA}></code></pre>
</div>
</div>
</div>
<div
class="custom-patterns-input-editor grokdebugger-ace-editor kuiVerticalRhythm"
require-keys="true"
ui-ace="{
onLoad: aceLoaded
}"
ng-model="customPatternsInput.customPatterns"
data-test-subj="aceCustomPatternsInput"
></div>
</div>
</toggle-panel>
</form>

View file

@ -0,0 +1,55 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { uiModules } from 'ui/modules';
import { InitAfterBindingsWorkaround } from 'ui/compat';
import template from './custom_patterns_input.html';
import './custom_patterns_input.less';
import 'ui/toggle_panel';
import 'ace';
const app = uiModules.get('xpack/grokdebugger');
app.directive('customPatternsInput', function () {
return {
restrict: 'E',
template: template,
scope: {
onChange: '='
},
bindToController: true,
controllerAs: 'customPatternsInput',
controller: class CustomPatternsInputController extends InitAfterBindingsWorkaround {
initAfterBindings($scope) {
this.isCollapsed = {
action: true
};
$scope.$watch('customPatternsInput.customPatterns', () => {
this.onChange(this.customPatterns);
});
$scope.aceLoaded = (editor) => {
this.editor = editor;
editor.getSession().setUseWrapMode(true);
editor.setOptions({
highlightActiveLine: false,
highlightGutterLine: false,
minLines: 3,
maxLines: 25
});
editor.$blockScrolling = Infinity;
};
}
onSectionToggle = (sectionId) => {
this.isCollapsed[sectionId] = !this.isCollapsed[sectionId];
}
isSectionCollapsed = (sectionId) => {
return this.isCollapsed[sectionId];
}
}
};
});

View file

@ -0,0 +1,5 @@
custom-patterns-input {
.custom-patterns-input-editor {
height: 51px;
}
}

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import './custom_patterns_input';

View file

@ -0,0 +1,16 @@
<form novalidate>
<div class="kuiFormSection">
<label class="kuiLabel kuiVerticalRhythmSmall">
Sample Data
</label>
<div
class="event-input-editor grokdebugger-ace-editor kuiVerticalRhythmSmall"
require-keys="true"
ui-ace="{
onLoad: aceLoaded
}"
ng-model="eventInput.rawEvent"
data-test-subj="aceEventInput"
></div>
</div>
</form>

View file

@ -0,0 +1,42 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { uiModules } from 'ui/modules';
import template from './event_input.html';
import './event_input.less';
import 'ace';
const app = uiModules.get('xpack/grokdebugger');
app.directive('eventInput', function () {
return {
restrict: 'E',
template: template,
scope: {
onChange: '='
},
bindToController: true,
controllerAs: 'eventInput',
controller: class EventInputController {
constructor($scope) {
$scope.$watch('eventInput.rawEvent', (newRawEvent) => {
this.onChange(newRawEvent);
});
$scope.aceLoaded = (editor) => {
this.editor = editor;
editor.getSession().setUseWrapMode(true);
editor.setOptions({
highlightActiveLine: false,
highlightGutterLine: false,
minLines: 3,
maxLines: 10
});
editor.$blockScrolling = Infinity;
};
}
}
};
});

View file

@ -0,0 +1,5 @@
event-input {
.event-input-editor {
height: 51px;
}
}

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import './event_input';

View file

@ -0,0 +1,18 @@
<form novalidate>
<div class="kuiFormSection">
<label class="kuiLabel kuiVerticalRhythmSmall">
Structured Data
</label>
<div
class="event-output-editor grokdebugger-ace-editor kuiVerticalRhythmSmall"
json-input
require-keys="true"
ui-ace="{
mode: 'json',
onLoad: aceLoaded
}"
ng-model="eventOutput.structuredEvent"
data-test-subj="aceEventOutput"
></div>
</div>
</form>

View file

@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { uiModules } from 'ui/modules';
import template from './event_output.html';
import './event_output.less';
import 'ace';
const app = uiModules.get('xpack/grokdebugger');
app.directive('eventOutput', function () {
return {
restrict: 'E',
template: template,
scope: {
structuredEvent: '='
},
bindToController: true,
controllerAs: 'eventOutput',
controller: class EventOutputController {
constructor($scope) {
$scope.aceLoaded = (editor) => {
this.editor = editor;
editor.getSession().setUseWrapMode(true);
editor.setOptions({
readOnly: true,
highlightActiveLine: false,
highlightGutterLine: false,
minLines: 20,
maxLines: 25
});
editor.$blockScrolling = Infinity;
};
}
}
};
});

View file

@ -0,0 +1,6 @@
event-output {
.event-output-editor {
height: 340px;
padding-bottom: 10px;
}
}

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import './event_output';

View file

@ -0,0 +1,23 @@
<kbn-dev-tools-app class="grokdebuggerDevTool">
<div class="kuiViewContent kuiViewContent--constrainedWidth kuiViewContentItem">
<div
class="grokdebugger-container"
data-test-subj="grokDebugger"
>
<event-input on-change="grokdebugger.onRawEventChange"></event-input>
<pattern-input on-change="grokdebugger.onPatternChange"></pattern-input>
<custom-patterns-input on-change="grokdebugger.onCustomPatternsChange"></custom-patterns-input>
<div class="grokdebugger-simulate-button">
<button
class="kuiButton kuiButton--primary"
ng-disabled="!grokdebugger.isSimulateEnabled"
ng-click="grokdebugger.onSimulateClick()"
data-test-subj="btnSimulate"
>
Simulate
</button>
</div>
<event-output structured-event="grokdebugger.structuredEvent"></event-output>
</div>
</div>
</kbn-dev-tools-app>

View file

@ -0,0 +1,87 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { uiModules } from 'ui/modules';
import template from './grokdebugger.html';
import { Notifier } from 'ui/notify';
import { GrokdebuggerRequest } from 'plugins/grokdebugger/models/grokdebugger_request';
import 'plugins/grokdebugger/services/grokdebugger';
import './grokdebugger.less';
import '../event_input';
import '../event_output';
import '../pattern_input';
import '../custom_patterns_input';
import { isEmpty, trim } from 'lodash';
const app = uiModules.get('xpack/grokdebugger');
app.directive('grokdebugger', function ($injector) {
const grokdebuggerService = $injector.get('grokdebuggerService');
return {
restrict: 'E',
template: template,
bindToController: true,
controllerAs: 'grokdebugger',
controller: class GrokdebuggerController {
constructor() {
this.structuredEvent = {};
this.grokdebuggerRequest = new GrokdebuggerRequest();
this.notifier = new Notifier({ location: 'GrokDebugger' });
}
onSimulateClick = () => {
return grokdebuggerService.simulate(this.grokdebuggerRequest)
.then(simulateResponse => {
this.structuredEvent = simulateResponse.structuredEvent;
// this error block is for responses which are 200, but still contain
// a grok debugger error like pattern not matched.
if (!isEmpty(simulateResponse.error)) {
this.notifier.error(simulateResponse.error);
}
})
.catch(e => {
// this error is for 4xx and 5xx responses
this.notifier.error(e);
});
}
onCustomPatternsChange = (customPatterns = '') => {
customPatterns = customPatterns.trim();
if (!customPatterns) {
return;
}
const customPatternsObj = {};
customPatterns.split('\n').forEach(customPattern => {
// Patterns are defined like so:
// patternName patternDefinition
// For example:
// POSTGRESQL %{DATESTAMP:timestamp} %{TZ} %{DATA:user_id} %{GREEDYDATA:connection_id} %{POSINT:pid}
const [ , patternName, patternDefinition ] = customPattern.match(/(\S+)\s+(.+)/) || [];
if (patternName && patternDefinition) {
customPatternsObj[patternName] = patternDefinition;
}
});
this.grokdebuggerRequest.customPatterns = customPatternsObj;
}
onRawEventChange = (rawEvent) => {
this.grokdebuggerRequest.rawEvent = rawEvent;
}
onPatternChange = (pattern) => {
this.grokdebuggerRequest.pattern = pattern;
}
get isSimulateEnabled() {
return !(isEmpty(trim(this.grokdebuggerRequest.rawEvent)) ||
isEmpty(trim(this.grokdebuggerRequest.pattern)));
}
}
};
});

View file

@ -0,0 +1,21 @@
@import (reference) "~ui/styles/variables";
@import (reference) "~ui/styles/mixins";
@import (reference) "~ui/styles/theme";
grokdebugger {
.grokdebugger-container {
font-size: 16px;
}
.ace_editor {
font-size: 13px;
}
.grokdebugger-ace-editor {
border: 2px solid @kibanaGray5;
}
.grokdebugger-simulate-button {
margin-bottom: 10px;
}
}

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import './grokdebugger';

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import './pattern_input';

View file

@ -0,0 +1,16 @@
<form novalidate>
<div class="kuiFormSection">
<label class="kuiLabel kuiVerticalRhythmSmall">
Grok Pattern
</label>
<div
class="pattern-input-editor grokdebugger-ace-editor kuiVerticalRhythmSmall"
require-keys="true"
ui-ace="{
onLoad: aceLoaded
}"
ng-model="patternInput.pattern"
data-test-subj="acePatternInput"
></div>
</div>
</form>

View file

@ -0,0 +1,41 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { uiModules } from 'ui/modules';
import template from './pattern_input.html';
import './pattern_input.less';
const app = uiModules.get('xpack/grokdebugger');
app.directive('patternInput', function () {
return {
restrict: 'E',
template: template,
scope: {
onChange: '='
},
bindToController: true,
controllerAs: 'patternInput',
controller: class PatternInputController {
constructor($scope) {
$scope.$watch('patternInput.pattern', (newPattern) => {
this.onChange(newPattern);
});
$scope.aceLoaded = (editor) => {
this.editor = editor;
editor.getSession().setUseWrapMode(true);
editor.setOptions({
highlightActiveLine: false,
highlightGutterLine: false,
minLines: 3,
maxLines: 10
});
editor.$blockScrolling = Infinity;
};
}
}
};
});

View file

@ -0,0 +1,5 @@
pattern-input {
.pattern-input-editor {
height: 51px;
}
}

View file

@ -0,0 +1 @@
<grokdebugger></grokdebugger>

View file

@ -0,0 +1,38 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import routes from 'ui/routes';
import { toastNotifications } from 'ui/notify';
import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
import template from './grokdebugger_route.html';
import './components/grokdebugger';
routes
.when('/dev_tools/grokdebugger', {
template: template,
resolve: {
licenseCheckResults(Private) {
const xpackInfo = Private(XPackInfoProvider);
return {
showPage: xpackInfo.get('features.grokdebugger.enableLink'),
message: xpackInfo.get('features.grokdebugger.message')
};
}
},
controller: class GrokDebuggerRouteController {
constructor($injector) {
const $route = $injector.get('$route');
const kbnUrl = $injector.get('kbnUrl');
const licenseCheckResults = $route.current.locals.licenseCheckResults;
if (!licenseCheckResults.showPage) {
kbnUrl.change('/dev_tools');
toastNotifications.addDanger(licenseCheckResults.message);
return;
}
}
}
});

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import './grokdebugger_route';

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { DevToolsRegistryProvider } from 'ui/registry/dev_tools';
import { XPackInfoProvider } from 'plugins/xpack_main/services/xpack_info';
DevToolsRegistryProvider.register((Private) => {
const xpackInfo = Private(XPackInfoProvider);
return {
order: 6,
name: 'grokdebugger',
display: 'Grok Debugger',
url: '#/dev_tools/grokdebugger',
disabled: !xpackInfo.get('features.grokdebugger.enableLink', false),
tooltipContent: xpackInfo.get('features.grokdebugger.message')
};
});

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { uiModules } from 'ui/modules';
import { GrokdebuggerService } from './grokdebugger_service';
uiModules.get('xpack/grokdebugger')
.factory('grokdebuggerService', ($injector) => {
const $http = $injector.get('$http');
return new GrokdebuggerService($http);
});

View file

@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import chrome from 'ui/chrome';
import { ROUTES } from '../../../common/constants';
import { GrokdebuggerResponse } from 'plugins/grokdebugger/models/grokdebugger_response';
export class GrokdebuggerService {
constructor($http) {
this.$http = $http;
this.basePath = chrome.addBasePath(ROUTES.API_ROOT);
}
simulate(grokdebuggerRequest) {
return this.$http.post(`${this.basePath}/simulate`, grokdebuggerRequest.upstreamJSON)
.then(response => {
return GrokdebuggerResponse.fromUpstreamJSON(response.data.grokdebuggerResponse);
})
.catch(e => {
throw e.data.message;
});
}
}

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import './grokdebugger_service.factory';

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { once } from 'lodash';
const callWithRequest = once((server) => {
const cluster = server.plugins.elasticsearch.getCluster('data');
return cluster.callWithRequest;
});
export const callWithRequestFactory = (server, request) => {
return (...args) => {
return callWithRequest(server)(request, ...args);
};
};

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { callWithRequestFactory } from './call_with_request_factory';

View file

@ -0,0 +1,88 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from 'expect.js';
import { set } from 'lodash';
import { checkLicense } from '../check_license';
describe('check_license', function () {
let mockLicenseInfo;
beforeEach(() => mockLicenseInfo = {});
describe('license information is undefined', () => {
beforeEach(() => mockLicenseInfo = undefined);
it('should set enableLink to false', () => {
expect(checkLicense(mockLicenseInfo).enableLink).to.be(false);
});
it('should set enableAPIRoute to false', () => {
expect(checkLicense(mockLicenseInfo).enableAPIRoute).to.be(false);
});
it('should set a message', () => {
expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined);
});
});
describe('license information is not available', () => {
beforeEach(() => mockLicenseInfo.isAvailable = () => false);
it('should set enableLink to false', () => {
expect(checkLicense(mockLicenseInfo).enableLink).to.be(false);
});
it('should set enableAPIRoute to false', () => {
expect(checkLicense(mockLicenseInfo).enableAPIRoute).to.be(false);
});
it('should set a message', () => {
expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined);
});
});
describe('license information is available', () => {
beforeEach(() => mockLicenseInfo = {
isAvailable: () => true,
license: {
getType: () => 'foobar'
}
});
describe('& license is active', () => {
beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true));
it ('should set enableLink to true', () => {
expect(checkLicense(mockLicenseInfo).enableLink).to.be(true);
});
it('should set enableAPIRoute to true', () => {
expect(checkLicense(mockLicenseInfo).enableAPIRoute).to.be(true);
});
it('should NOT set a message', () => {
expect(checkLicense(mockLicenseInfo).message).to.be(undefined);
});
});
describe('& license is expired', () => {
beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false));
it ('should set enableLink to false', () => {
expect(checkLicense(mockLicenseInfo).enableLink).to.be(false);
});
it('should set enableAPIRoute to false', () => {
expect(checkLicense(mockLicenseInfo).enableAPIRoute).to.be(false);
});
it('should set a message', () => {
expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined);
});
});
});
});

View file

@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export function checkLicense(xpackLicenseInfo) {
// If, for some reason, we cannot get the license information
// from Elasticsearch, assume worst case and disable the Watcher UI
if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) {
return {
enableLink: false,
enableAPIRoute: false,
message: 'You cannot use the Grok Debugger because license information is not available at this time.'
};
}
const isLicenseActive = xpackLicenseInfo.license.isActive();
const licenseType = xpackLicenseInfo.license.getType();
// License is not valid
if (!isLicenseActive) {
return {
enableLink: false,
enableAPIRoute: false,
message: `You cannot use the Grok Debugger because your ${licenseType} license has expired.`
};
}
// License is valid and active
return {
enableLink: true,
enableAPIRoute: true
};
}

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { checkLicense } from './check_license';

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { wrapEsError } from './wrap_es_error';

View file

@ -0,0 +1,18 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import Boom from 'boom';
/**
* Wraps ES errors into a Boom error response and returns it
* This also handles the permissions issue gracefully
*
* @param err Object ES error
* @return Object Boom error response
*/
export function wrapEsError(err) {
return Boom.wrap(err, err.statusCode);
}

View file

@ -0,0 +1,66 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from 'expect.js';
import Boom from 'boom';
import { licensePreRoutingFactory } from '../license_pre_routing_factory';
describe('license_pre_routing_factory', () => {
describe('#grokDebuggerFeaturePreRoutingFactory', () => {
let mockServer;
let mockLicenseCheckResults;
beforeEach(() => {
mockServer = {
plugins: {
xpack_main: {
info: {
feature: () => ({
getLicenseCheckResults: () => mockLicenseCheckResults
})
}
}
}
};
});
describe('isAvailable is false', () => {
beforeEach(() => {
mockLicenseCheckResults = {
isAvailable: false
};
});
it ('replies with 403', (done) => {
const licensePreRouting = licensePreRoutingFactory(mockServer);
const stubRequest = {};
licensePreRouting(stubRequest, (response) => {
expect(response).to.be.an(Error);
expect(response.isBoom).to.be(true);
expect(response.output.statusCode).to.be(403);
done();
});
});
});
describe('isAvailable is true', () => {
beforeEach(() => {
mockLicenseCheckResults = {
isAvailable: true
};
});
it ('replies with nothing', (done) => {
const licensePreRouting = licensePreRoutingFactory(mockServer);
const stubRequest = {};
licensePreRouting(stubRequest, (response) => {
expect(response).to.eql(Boom.forbidden());
done();
});
});
});
});
});

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { licensePreRoutingFactory } from './license_pre_routing_factory';

View file

@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import Boom from 'boom';
import { PLUGIN } from '../../../common/constants';
export const licensePreRoutingFactory = (server) => {
const xpackMainPlugin = server.plugins.xpack_main;
// License checking and enable/disable logic
function licensePreRouting(request, reply) {
const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults();
if (!licenseCheckResults.enableAPIRoute) {
reply(Boom.forbidden(licenseCheckResults.message));
} else {
reply();
}
}
return licensePreRouting;
};

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { registerLicenseChecker } from './register_license_checker';

View file

@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { mirrorPluginStatus } from '../../../../../server/lib/mirror_plugin_status';
import { checkLicense } from '../check_license';
import { PLUGIN } from '../../../common/constants';
export function registerLicenseChecker(server) {
const xpackMainPlugin = server.plugins.xpack_main;
const grokdebuggerPlugin = server.plugins[PLUGIN.ID];
mirrorPluginStatus(xpackMainPlugin, grokdebuggerPlugin);
xpackMainPlugin.status.once('green', () => {
// Register a function that is called whenever the xpack info changes,
// to re-compute the license check results for this plugin
xpackMainPlugin.info.feature(PLUGIN.ID).registerLicenseCheckResultsGenerator(checkLicense);
});
}

View file

@ -0,0 +1,108 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from 'expect.js';
import { GrokdebuggerRequest } from '../grokdebugger_request';
describe('grokdebugger_request', () => {
describe('GrokdebuggerRequest', () => {
const downstreamRequest = {
rawEvent: '55.3.244.1 GET /index.html',
pattern: '%{IP:client} %{WORD:method} %{URIPATHPARAM:request}'
};
const downstreamRequestWithCustomPatterns = {
rawEvent: '55.3.244.1 GET /index.html',
pattern: '%{IP:client} %{WORD:method} %{URIPATHPARAM:request}',
customPatterns: '%{FOO:bar}'
};
describe('fromDownstreamJSON factory method', () => {
it('returns correct GrokdebuggerRequest instance from downstreamRequest', () => {
const grokdebuggerRequest = GrokdebuggerRequest.fromDownstreamJSON(downstreamRequest);
expect(grokdebuggerRequest.rawEvent).to.eql(downstreamRequest.rawEvent);
expect(grokdebuggerRequest.pattern).to.eql(downstreamRequest.pattern);
expect(grokdebuggerRequest.customPatterns).to.eql({});
});
it('returns correct GrokdebuggerRequest instance from downstreamRequest when custom patterns are specified', () => {
const grokdebuggerRequest = GrokdebuggerRequest.fromDownstreamJSON(downstreamRequestWithCustomPatterns);
expect(grokdebuggerRequest.rawEvent).to.eql(downstreamRequest.rawEvent);
expect(grokdebuggerRequest.pattern).to.eql(downstreamRequest.pattern);
expect(grokdebuggerRequest.customPatterns).to.eql('%{FOO:bar}');
});
});
describe('upstreamJSON getter method', () => {
it('returns the upstream simulate JSON request', () => {
const expectedUpstreamJSON = {
pipeline: {
description: 'this is a grokdebugger simulation',
processors: [
{
"grok": {
"field": "rawEvent",
"patterns": ["%{IP:client} %{WORD:method} %{URIPATHPARAM:request}"],
"pattern_definitions": {}
}
}
]
},
docs: [
{
"_index": "grokdebugger",
"_type": "grokdebugger",
"_id": "grokdebugger",
"_source": {
"rawEvent": "55.3.244.1 GET /index.html"
}
}
]
};
const grokdebuggerRequest = GrokdebuggerRequest.fromDownstreamJSON(downstreamRequest);
const upstreamJson = grokdebuggerRequest.upstreamJSON;
expect(upstreamJson).to.eql(expectedUpstreamJSON);
});
it('returns the upstream simulate JSON request when custom patterns are specfied', () => {
const expectedUpstreamJSON = {
pipeline: {
description: 'this is a grokdebugger simulation',
processors: [
{
"grok": {
"field": "rawEvent",
"patterns": ["%{IP:client} %{WORD:method} %{URIPATHPARAM:request}"],
"pattern_definitions": '%{FOO:bar}'
}
}
]
},
docs: [
{
"_index": "grokdebugger",
"_type": "grokdebugger",
"_id": "grokdebugger",
"_source": {
"rawEvent": "55.3.244.1 GET /index.html"
}
}
]
};
const grokdebuggerRequest = GrokdebuggerRequest.fromDownstreamJSON(downstreamRequestWithCustomPatterns);
const upstreamJson = grokdebuggerRequest.upstreamJSON;
expect(upstreamJson).to.eql(expectedUpstreamJSON);
});
});
});
});

View file

@ -0,0 +1,58 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { get } from 'lodash';
/**
* Model to capture Grokdebugger request with upstream (ES) helpers.
*/
export class GrokdebuggerRequest {
constructor(props) {
this.rawEvent = get(props, 'rawEvent', "");
this.pattern = get(props, 'pattern', "");
this.customPatterns = get(props, 'customPatterns', {});
}
get upstreamJSON() {
return {
pipeline: {
description: 'this is a grokdebugger simulation',
processors: [
{
grok: {
field: 'rawEvent',
pattern_definitions: this.customPatterns,
patterns: [
this.pattern.toString()
]
}
}
]
},
docs: [
{
_index: 'grokdebugger',
_type: 'grokdebugger',
_id: 'grokdebugger',
_source: {
rawEvent: this.rawEvent.toString()
}
}
]
};
}
// generate GrokdebuggerRequest object from kibana
static fromDownstreamJSON(downstreamGrokdebuggerRequest) {
const opts = {
rawEvent: downstreamGrokdebuggerRequest.rawEvent,
pattern: downstreamGrokdebuggerRequest.pattern,
customPatterns: downstreamGrokdebuggerRequest.customPatterns
};
return new GrokdebuggerRequest(opts);
}
}

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { GrokdebuggerRequest } from './grokdebugger_request';

View file

@ -0,0 +1,75 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from 'expect.js';
import { GrokdebuggerResponse } from '../grokdebugger_response';
describe('grokdebugger_response', () => {
describe('GrokdebuggerResponse', () => {
describe('fromUpstreamJSON factory method', () => {
it('returns correct GrokdebuggerResponse instance when there are no grok parse errors', () => {
const upstreamJson = {
docs: [
{
doc: {
_index: 'grokdebugger',
_type: 'grokdebugger',
_id: 'grokdebugger',
_source: {
"request": "/index.html",
"rawEvent": "55.3.244.1 GET /index.html",
"method": "GET",
"client": "55.3.244.1"
},
_ingest: {
"timestamp": "2017-05-13T23:29:14.809Z"
}
}
}
]
};
// factory method should have removed rawEvent field
const expectedStructuredEvent = {
request: '/index.html',
method: 'GET',
client: '55.3.244.1'
};
const grokdebuggerResponse = GrokdebuggerResponse.fromUpstreamJSON(upstreamJson);
expect(grokdebuggerResponse.structuredEvent).to.eql(expectedStructuredEvent);
expect(grokdebuggerResponse.error).to.eql({});
});
it('returns correct GrokdebuggerResponse instance when there are valid grok parse errors', () => {
const upstreamJson = {
docs: [
{
error: {
root_cause: [
{
"type": "exception",
"reason": "java.lang.IllegalArgumentException",
"header": {
"processor_type": "grok"
}
}
],
type: 'exception'
}
}
]
};
const grokdebuggerResponse = GrokdebuggerResponse.fromUpstreamJSON(upstreamJson);
expect(grokdebuggerResponse.structuredEvent).to.eql({});
expect(grokdebuggerResponse.error).to.be('Provided Grok patterns do not match data in the input');
});
});
});
});

View file

@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { get, isEmpty, omit } from 'lodash';
/**
* This model captures the grok debugger response from upstream to be passed to
* the view
*/
export class GrokdebuggerResponse {
constructor(props) {
this.structuredEvent = get(props, 'structuredEvent', {});
this.error = get(props, 'error', {});
}
// generate GrokdebuggerResponse object from elasticsearch response
static fromUpstreamJSON(upstreamGrokdebuggerResponse) {
const docs = get(upstreamGrokdebuggerResponse, 'docs');
const error = docs[0].error;
if (!isEmpty(error)) {
const opts = { 'error': 'Provided Grok patterns do not match data in the input' };
return new GrokdebuggerResponse(opts);
}
const structuredEvent = omit(get(docs, '0.doc._source'), 'rawEvent');
const opts = { structuredEvent };
return new GrokdebuggerResponse(opts);
}
}

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { GrokdebuggerResponse } from './grokdebugger_response';

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { registerGrokdebuggerRoutes } from './register_grokdebugger_routes';

View file

@ -0,0 +1,39 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { wrapEsError } from '../../../lib/error_wrappers';
import { callWithRequestFactory } from '../../../lib/call_with_request_factory';
import { GrokdebuggerRequest } from '../../../models/grokdebugger_request';
import { GrokdebuggerResponse } from '../../../models/grokdebugger_response';
import { licensePreRoutingFactory } from'../../../lib/license_pre_routing_factory';
function simulateGrok(callWithRequest, ingestJson) {
return callWithRequest('ingest.simulate', {
body: ingestJson
});
}
export function registerGrokSimulateRoute(server) {
const licensePreRouting = licensePreRoutingFactory(server);
server.route({
path: '/api/grokdebugger/simulate',
method: 'POST',
handler: (request, reply) => {
const callWithRequest = callWithRequestFactory(server, request);
const grokdebuggerRequest = GrokdebuggerRequest.fromDownstreamJSON(request.payload);
return simulateGrok(callWithRequest, grokdebuggerRequest.upstreamJSON)
.then((simulateResponseFromES) => {
const grokdebuggerResponse = GrokdebuggerResponse.fromUpstreamJSON(simulateResponseFromES);
reply({ grokdebuggerResponse });
})
.catch(e => reply(wrapEsError(e)));
},
config: {
pre: [ licensePreRouting ]
}
});
}

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { registerGrokSimulateRoute } from './register_grok_simulate_route';
export function registerGrokdebuggerRoutes(server) {
registerGrokSimulateRoute(server);
}