Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create an Ingest API #5213

Merged
merged 114 commits into from
Jan 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
5841d22
organizing thoughts
Bargs Oct 27, 2015
586d33d
can has plugin
Bargs Oct 29, 2015
131f985
Add GET endpoints for index-patterns and specific index-pattern
Bargs Oct 29, 2015
4227dad
Add POST and DELETE
Bargs Oct 29, 2015
688b8cc
add PUT endpoint for updating index patterns
Bargs Oct 29, 2015
2ec2889
adding broken tests
Bargs Oct 29, 2015
bbbec3a
Handle common elasticsearch errors and return the appropriate status …
Bargs Oct 30, 2015
3d6c1d7
make the tests aware of their async nature
Bargs Nov 3, 2015
cac580d
Starting work on index template creation
Bargs Nov 5, 2015
472d3cf
Accept index template mappings when POSTing index pattern
Bargs Nov 10, 2015
7228c16
Roll back index pattern creation if the template creation fails for s…
Bargs Nov 11, 2015
a524824
Use template string for readability
Bargs Nov 11, 2015
6427819
only create an index template if the client provided mappings and the…
Bargs Nov 12, 2015
20591f9
account for deprecated wildcard syntax
Bargs Nov 12, 2015
2181449
Index template names must be lower case
Bargs Nov 12, 2015
05b3c3b
delete unit tests that will be replaced by functional tests
Bargs Nov 12, 2015
0b45b30
don't allow 0 matches to return true for wildcard index names
Bargs Nov 12, 2015
f202091
Don't allow updates to mappings
Bargs Nov 12, 2015
8bd0a3c
return mappings from template with index pattern
Bargs Nov 12, 2015
eea21ec
GET mostly working, still need to fix mappings if retreived from index
Bargs Nov 13, 2015
c2894dc
Pull field mappings out of their nested structure
Bargs Nov 13, 2015
7f126b3
refactor get mappings into a library function
Bargs Nov 13, 2015
9faf449
GET all index-patterns now pulls back pattern with field mappings
Bargs Nov 13, 2015
c1c3b47
Created helper function for translating between template and pattern …
Bargs Nov 14, 2015
abfb11e
DELETE will now delete both index pattern and template
Bargs Nov 14, 2015
bef7c47
support GETting old index patterns in the new format
Bargs Nov 14, 2015
e6100c2
First API test
Bargs Nov 17, 2015
07e3a33
disable xsrf protection when running api tests
Bargs Nov 17, 2015
a2ba98c
test for missing payload with POST
Bargs Nov 17, 2015
08d6685
return from handler in empty payload check to prevent error from bein…
Bargs Nov 17, 2015
f3ee0ce
Added Joi schema for index pattern resource and tests to make sure it…
Bargs Nov 18, 2015
0f92cc7
Pass the original ES error message back with the correct status code
Bargs Nov 18, 2015
565f261
adding a bunch of tests for the POST index-patterns endpoints
Bargs Nov 19, 2015
2dcbbf6
Refactor tests into modules
Bargs Nov 19, 2015
675bb7c
Ensure that field mappings are given a type
Bargs Nov 19, 2015
118608c
fix function name
Bargs Nov 19, 2015
68d3be8
Test PUTting index patterns
Bargs Nov 19, 2015
526e8e7
Don't allow updates to title when PUTting index-patterns
Bargs Nov 19, 2015
53d3557
Test 400 responds for invalid PUT payloads
Bargs Nov 19, 2015
3276872
test 404 response when PUTting to a non-existent document
Bargs Nov 19, 2015
d8d605b
Test DELETE of index-patterns
Bargs Nov 19, 2015
4b60a3e
Test GETting all index patterns with creation of test data before sui…
Bargs Nov 19, 2015
dbc512e
test GETting an index-pattern by ID
Bargs Nov 19, 2015
549d0e4
use alternative lodash syntax for readability
Bargs Nov 19, 2015
7bf6264
adhear to new API naming conventions
Bargs Nov 19, 2015
00c5359
Move the API into the kibana plugin since index patterns are a core p…
Bargs Nov 19, 2015
c7d194b
[] synatx doesn't mean anything to elasticsearch so it doesn't make s…
Bargs Nov 19, 2015
64ff43d
Converted GET index patterns over to using callWithRequest
Bargs Nov 19, 2015
996400b
Switch GET by ID to use callWithRequest
Bargs Nov 19, 2015
091ab7c
Convert POST index patterns to using callWithRequest
Bargs Nov 19, 2015
061c8f7
Convert PUT and DELETE index patterns to callWithRequest
Bargs Nov 19, 2015
d46c8a4
fix caps in require statement
Bargs Nov 20, 2015
c5565d7
Give API tests their own intern config
Bargs Nov 20, 2015
f47c4c2
Fix require for casesensitive systems
Bargs Nov 20, 2015
bd3d96d
refactor index pattern endpoints into separate modules
Bargs Nov 24, 2015
45a3f98
tests for handleESError util funtion
Bargs Nov 24, 2015
62bbd4b
Use snake_case in API payloads
Bargs Nov 26, 2015
673bea7
add tests and documentation for stitch_pattern_and_mappings util func…
Bargs Dec 1, 2015
5647f46
adhearing to styleguide
Bargs Dec 1, 2015
4d27b02
refactor getMappings so it doesn't require the hapi request object to…
Bargs Dec 2, 2015
d081fb4
Modify getMappings to use a bound version of callWithRequest to make …
Bargs Dec 3, 2015
86ddfc4
Refactor routes to use Hapi route options to setup request payload va…
Bargs Dec 3, 2015
09e6a22
add tests and argument validation for converPatternAndTemplateName
Bargs Dec 3, 2015
6e3d04e
tests for removeDeprecatedFieldProps
Bargs Dec 3, 2015
57f62ce
test deep and strict equality
Bargs Dec 3, 2015
3ec46f7
Add field mapping conflict detection
Bargs Dec 4, 2015
1f47509
no need to check for array now that removeDeprecatedFieldProps can ac…
Bargs Dec 4, 2015
6f0dcef
Make PUT return 'success' to match POST
Bargs Dec 4, 2015
66b3c1e
Beginning to update index pattern api resource schema
Bargs Dec 9, 2015
9f4c675
Add mapping info to index pattern when POST includes a template
Bargs Dec 10, 2015
8ae714b
fix key name
Bargs Dec 10, 2015
0ff6696
update POST api functional tests for the new resource schema
Bargs Dec 10, 2015
7ce3f1e
update delete tests with new fixture name
Bargs Dec 10, 2015
636b444
Updated GET-all api endpoint and tests to use the new resource schema
Bargs Dec 11, 2015
bf45998
Update GET single pattern endpoint to use the new resource schema
Bargs Dec 11, 2015
b9cf052
update PUT endpoint for new resource schema
Bargs Dec 11, 2015
97cb8a0
remove marvel index pattern example from tests to avoid conflicts
Bargs Dec 11, 2015
1f61055
cleaning up tests
Bargs Dec 11, 2015
0397a2f
refactor GET code so that it can be reused
Bargs Dec 11, 2015
6b28312
Update DELETE to use template id included in pattern resource instead…
Bargs Dec 11, 2015
dd7673f
Re-adding bluebird promise that was lost in refactoring
Bargs Dec 14, 2015
3d30ebd
tests for convertToSnakeCase function
Bargs Dec 14, 2015
56b729a
updating DELETE tests for ability to optionally delete template along…
Bargs Dec 14, 2015
3eae7cd
Update GET tests for new functionality
Bargs Dec 15, 2015
1568ddc
add test for field mapping normalization
Bargs Dec 16, 2015
83bbcb6
Convert index-pattern keys to snakeCase before sending to ES for the …
Bargs Dec 16, 2015
74f5d52
stringify fieldFormatMap for backwards compatibility and add script a…
Bargs Dec 16, 2015
172ff29
Make the data attribute required in the index pattern schema
Bargs Dec 17, 2015
42c7856
cleanup some unnecessary code
Bargs Dec 17, 2015
0fac450
avoid contaminating our variable names with dirty snakes
Bargs Dec 17, 2015
4bba671
only send one request for all templates in GET index-patterns
Bargs Dec 17, 2015
c1a32aa
Make pattern deletion more lenient by allowing include=template even …
Bargs Dec 17, 2015
bc04061
Remove PUT endpoint
Bargs Jan 4, 2016
3fae90f
Remove the concept of a template resource from the index pattern API
Bargs Jan 4, 2016
6c7d4c6
Add new notExpandable field to the index pattern Joi schema
Bargs Jan 4, 2016
d912f7c
remove name prop from the field mapping object
Bargs Jan 4, 2016
1b916a3
Added API tests for initialization of default index pattern field val…
Bargs Jan 4, 2016
8951646
clean up unused modules
Bargs Jan 4, 2016
d4e85bc
Better defaults for scripted fields and prevent scripted fields from …
Bargs Jan 4, 2016
fe296b4
First pass at adding support for fields that are part of an object
Bargs Jan 5, 2016
a0ac213
Remove endpoints that we don't need for the Add Data UI
Bargs Jan 5, 2016
6b1d0c8
add a dynamic template for strings
Bargs Jan 6, 2016
b4ef144
Created more robust defaults and removed support for overriding them
Bargs Jan 6, 2016
2bea404
clean up unused files
Bargs Jan 6, 2016
e981032
Change the name of the index_pattern api to ingest and remove the JSO…
Bargs Jan 6, 2016
e1bb5f5
return 204 for successful POST an use JSON for all response payloads
Bargs Jan 6, 2016
57fc122
String.replace only replaces the first instance of the match string t…
Bargs Jan 7, 2016
f214787
Check for matching indices before creating anything
Bargs Jan 7, 2016
a5178be
avoid repetition
Bargs Jan 7, 2016
8825135
Include the original error messages for template creation and pattern…
Bargs Jan 7, 2016
4f5aa95
ingest API requires at least one field
Bargs Jan 7, 2016
22be314
fix errors in tests
Bargs Jan 11, 2016
df9ef8c
clean up formatting and leverage error ignoring feature of ES js client
Bargs Jan 11, 2016
e5a8b12
Use more descriptive names for the case conversion helper methods
Bargs Jan 11, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@
"portscanner": "1.0.0",
"simple-git": "1.8.0",
"sinon": "1.17.2",
"source-map": "0.4.4"
"source-map": "0.4.4",
"supertest-as-promised": "2.0.2"
},
"engines": {
"node": "0.12.9",
Expand Down
6 changes: 6 additions & 0 deletions src/plugins/kibana/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const ingest = require('./server/routes/api/ingest');

module.exports = function (kibana) {
return new kibana.Plugin({

Expand Down Expand Up @@ -43,6 +45,10 @@ module.exports = function (kibana) {
};
}
}
},

init: function (server, options) {
ingest(server);
}
});

Expand Down
35 changes: 35 additions & 0 deletions src/plugins/kibana/server/lib/__tests__/case_conversion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const expect = require('expect.js');
const { keysToSnakeCaseShallow, keysToCamelCaseShallow } = require('../case_conversion');
const _ = require('lodash');

describe('keysToSnakeCaseShallow', function () {

it('should convert all of an object\'s keys to snake case', function () {
const result = keysToSnakeCaseShallow({
camelCase: 'camel_case',
'kebab-case': 'kebab_case',
snake_case: 'snake_case'
});

_.forEach(result, function (value, key) {
expect(key).to.be(value);
});
});

});

describe('keysToCamelCaseShallow', function () {

it('should convert all of an object\'s keys to camel case', function () {
const result = keysToCamelCaseShallow({
camelCase: 'camelCase',
'kebab-case': 'kebabCase',
snake_case: 'snakeCase'
});

_.forEach(result, function (value, key) {
expect(key).to.be(value);
});
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const {templateToPattern, patternToTemplate} = require('../convert_pattern_and_template_name');
const expect = require('expect.js');

describe('convertPatternAndTemplateName', function () {

describe('templateToPattern', function () {

it('should convert an index template\'s name to its matching index pattern\'s title', function () {
expect(templateToPattern('kibana-logstash-*')).to.be('logstash-*');
});

it('should throw an error if the template name isn\'t a valid kibana namespaced name', function () {
expect(templateToPattern).withArgs('logstash-*').to.throwException('not a valid kibana namespaced template name');
expect(templateToPattern).withArgs('').to.throwException(/not a valid kibana namespaced template name/);
});

});

describe('patternToTemplate', function () {

it('should convert an index pattern\'s title to its matching index template\'s name', function () {
expect(patternToTemplate('logstash-*')).to.be('kibana-logstash-*');
});

it('should throw an error if the pattern is empty', function () {
expect(patternToTemplate).withArgs('').to.throwException(/pattern must not be empty/);
});

});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const createMappingsFromPatternFields = require('../create_mappings_from_pattern_fields');
const expect = require('expect.js');
const _ = require('lodash');

let testFields;

describe('createMappingsFromPatternFields', function () {

beforeEach(function () {
testFields = [
{
'name': 'ip',
'type': 'ip'
},
{
'name': 'agent',
'type': 'string'
},
{
'name': 'bytes',
'type': 'number'
}
];
});

it('should throw an error if the argument is empty', function () {
expect(createMappingsFromPatternFields).to.throwException(/argument must not be empty/);
});

it('should not modify the original argument', function () {
const testFieldClone = _.cloneDeep(testFields);
const mappings = createMappingsFromPatternFields(testFields);

expect(mappings.ip).to.not.be(testFields[0]);
expect(_.isEqual(testFields, testFieldClone)).to.be.ok();
});

it('should set the same default mapping for all non-strings', function () {
let mappings = createMappingsFromPatternFields(testFields);

_.forEach(mappings, function (mapping) {
if (mapping.type !== 'string') {
expect(_.isEqual(mapping, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this does what you think it does. You probably have to do:

expect(mapping).is.eql({
  ...
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using lodash's isEqual because I want to test for deep and strict equality. Unfortunately expect.js's is.eql() only checks for loose equality, and there's no assertion that's both recursive and strict.

However I did forget to add to.be.ok() to the assertion.

type: mapping.type,
index: 'not_analyzed',
doc_values: true
})).to.be.ok();
}
});
});

it('should give strings a multi-field mapping', function () {
let mappings = createMappingsFromPatternFields(testFields);

_.forEach(mappings, function (mapping) {
if (mapping.type === 'string') {
expect(mapping).to.have.property('fields');
}
});
});

it('should handle nested fields', function () {
testFields.push({name: 'geo.coordinates', type: 'geo_point'});
let mappings = createMappingsFromPatternFields(testFields);

expect(mappings).to.have.property('geo');
expect(mappings.geo).to.have.property('properties');
expect(mappings.geo.properties).to.have.property('coordinates');
expect(_.isEqual(mappings.geo.properties.coordinates, {
type: 'geo_point',
index: 'not_analyzed',
doc_values: true
})).to.be.ok();
});

it('should map all number fields as an ES double', function () {
let mappings = createMappingsFromPatternFields(testFields);

expect(mappings).to.have.property('bytes');
expect(mappings.bytes).to.have.property('type', 'double');
});
});
41 changes: 41 additions & 0 deletions src/plugins/kibana/server/lib/__tests__/handle_es_error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
var expect = require('expect.js');
var Boom = require('boom');
var esErrors = require('elasticsearch').errors;
var handleESError = require('../handle_es_error');

describe('handleESError', function () {

it('should transform elasticsearch errors into boom errors with the same status code', function () {
var conflict = handleESError(new esErrors.Conflict());
expect(conflict.isBoom).to.be(true);
expect(conflict.output.statusCode).to.be(409);

var forbidden = handleESError(new esErrors[403]);
expect(forbidden.isBoom).to.be(true);
expect(forbidden.output.statusCode).to.be(403);

var notFound = handleESError(new esErrors.NotFound());
expect(notFound.isBoom).to.be(true);
expect(notFound.output.statusCode).to.be(404);

var badRequest = handleESError(new esErrors.BadRequest());
expect(badRequest.isBoom).to.be(true);
expect(badRequest.output.statusCode).to.be(400);
});

it('should return an unknown error without transforming it', function () {
var unknown = new Error('mystery error');
expect(handleESError(unknown)).to.be(unknown);
});

it('should return a boom 503 server timeout error for ES connection errors', function () {
expect(handleESError(new esErrors.ConnectionFault()).output.statusCode).to.be(503);
expect(handleESError(new esErrors.ServiceUnavailable()).output.statusCode).to.be(503);
expect(handleESError(new esErrors.NoConnections()).output.statusCode).to.be(503);
expect(handleESError(new esErrors.RequestTimeout()).output.statusCode).to.be(503);
});

it('should throw an error if called with a non-error argument', function () {
expect(handleESError).withArgs('notAnError').to.throwException();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
const initDefaultFieldProps = require('../init_default_field_props');
const expect = require('expect.js');
const _ = require('lodash');
let fields;

const testData = [
{
'name': 'ip',
'type': 'ip'
}, {
'name': '@timestamp',
'type': 'date'
}, {
'name': 'agent',
'type': 'string'
}, {
'name': 'bytes',
'type': 'number'
},
{
'name': 'geo.coordinates',
'type': 'geo_point'
}
];

describe('initDefaultFieldProps', function () {

beforeEach(function () {
fields = _.cloneDeep(testData);
});

it('should throw an error if no argument is passed or the argument is not an array', function () {
expect(initDefaultFieldProps).to.throwException(/requires an array argument/);
expect(initDefaultFieldProps).withArgs({}).to.throwException(/requires an array argument/);
});

it('should set the same defaults for everything but strings', function () {
const results = initDefaultFieldProps(fields);
_.forEach(results, function (field) {
if (field.type !== 'string') {
expect(field).to.have.property('indexed', true);
expect(field).to.have.property('analyzed', false);
expect(field).to.have.property('doc_values', true);
expect(field).to.have.property('scripted', false);
expect(field).to.have.property('count', 0);
}
});
});

it('should make string fields analyzed', function () {
const results = initDefaultFieldProps(fields);
_.forEach(results, function (field) {
if (field.type === 'string' && !_.contains(field.name, 'raw')) {
expect(field).to.have.property('indexed', true);
expect(field).to.have.property('analyzed', true);
expect(field).to.have.property('doc_values', false);
expect(field).to.have.property('scripted', false);
expect(field).to.have.property('count', 0);
}
});
});

it('should create an extra raw non-analyzed field for strings', function () {
const results = initDefaultFieldProps(fields);
const rawField = _.find(results, function (field) {
return _.contains(field.name, 'raw');
});
expect(rawField).to.have.property('indexed', true);
expect(rawField).to.have.property('analyzed', false);
expect(rawField).to.have.property('doc_values', true);
expect(rawField).to.have.property('scripted', false);
expect(rawField).to.have.property('count', 0);
});
});
15 changes: 15 additions & 0 deletions src/plugins/kibana/server/lib/case_conversion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const _ = require('lodash');

module.exports = {
keysToSnakeCaseShallow: function (object) {
return _.mapKeys(object, (value, key) => {
return _.snakeCase(key);
});
},

keysToCamelCaseShallow: function (object) {
return _.mapKeys(object, (value, key) => {
return _.camelCase(key);
});
}
};
22 changes: 22 additions & 0 deletions src/plugins/kibana/server/lib/convert_pattern_and_template_name.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// To avoid index template naming collisions the index pattern creation API
// namespaces template names by prepending 'kibana-' to the matching pattern's title.
// e.g. a pattern with title `logstash-*` will have a matching template named `kibana-logstash-*`.
// This module provides utility functions for easily converting between template and pattern names.

module.exports = {
templateToPattern: (templateName) => {
if (templateName.indexOf('kibana-') === -1) {
throw new Error('not a valid kibana namespaced template name');
}

return templateName.slice(templateName.indexOf('-') + 1);
},

patternToTemplate: (patternName) => {
if (patternName === '') {
throw new Error('pattern must not be empty');
}

return `kibana-${patternName.toLowerCase()}`;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const _ = require('lodash');

// Creates an ES field mapping from a single field object in a kibana index pattern
module.exports = function createMappingsFromPatternFields(fields) {
if (_.isEmpty(fields)) {
throw new Error('argument must not be empty');
}

const mappings = {};

_.forEach(fields, function (field) {
let mapping;

if (field.type === 'string') {
mapping = {
type: 'string',
index: 'analyzed',
omit_norms: true,
fielddata: {format: 'disabled'},
fields: {
raw: {type: 'string', index: 'not_analyzed', doc_values: true, ignore_above: 256}
}
};
}
else {
const fieldType = field.type === 'number' ? 'double' : field.type;
mapping = {
type: fieldType,
index: 'not_analyzed',
doc_values: true
};
}

_.set(mappings, field.name.split('.').join('.properties.'), mapping);
});

return mappings;
};
26 changes: 26 additions & 0 deletions src/plugins/kibana/server/lib/handle_es_error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const Boom = require('boom');
const esErrors = require('elasticsearch').errors;
const _ = require('lodash');

module.exports = function handleESError(error) {
if (!(error instanceof Error)) {
throw new Error('Expected an instance of Error');
}

if (error instanceof esErrors.ConnectionFault ||
error instanceof esErrors.ServiceUnavailable ||
error instanceof esErrors.NoConnections ||
error instanceof esErrors.RequestTimeout) {
return Boom.serverTimeout(error);
} else if (error instanceof esErrors.Conflict || _.contains(error.message, 'index_template_already_exists')) {
return Boom.conflict(error);
} else if (error instanceof esErrors[403]) {
return Boom.forbidden(error);
} else if (error instanceof esErrors.NotFound) {
return Boom.notFound(error);
} else if (error instanceof esErrors.BadRequest || error instanceof TypeError) {
return Boom.badRequest(error);
} else {
return error;
}
};
Loading