mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Completing plugin system
This commit is contained in:
parent
c12e80c615
commit
76ddea6e99
64 changed files with 1456 additions and 392 deletions
|
@ -3,6 +3,7 @@
|
|||
"node": true,
|
||||
|
||||
"globals": {
|
||||
"Promise": true
|
||||
"Promise": true,
|
||||
"status": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
],
|
||||
"private": false,
|
||||
"version": "4.1.0-snapshot",
|
||||
"main": "src/server/app.js",
|
||||
"main": "src/hapi/index.js",
|
||||
"homepage": "https://www.elastic.co/products/kibana",
|
||||
"bugs": {
|
||||
"url": "http://github.com/elastic/kibana/issues"
|
||||
|
@ -57,10 +57,12 @@
|
|||
"hapi": "^8.4.0",
|
||||
"http-auth": "^2.2.5",
|
||||
"jade": "~1.8.2",
|
||||
"joi": "^6.2.0",
|
||||
"js-yaml": "^3.2.5",
|
||||
"json-stringify-safe": "^5.0.0",
|
||||
"less-middleware": "1.0.x",
|
||||
"lodash": "^2.4.1",
|
||||
"lodash-deep": "^1.6.0",
|
||||
"moment": "^2.9.0",
|
||||
"morgan": "~1.5.1",
|
||||
"numeral": "^1.5.3",
|
||||
|
@ -96,6 +98,7 @@
|
|||
"http-proxy": "~1.8.1",
|
||||
"husky": "~0.6.0",
|
||||
"istanbul": "~0.2.4",
|
||||
"libesvm": "0.0.12",
|
||||
"load-grunt-config": "~0.7.0",
|
||||
"lodash": "~2.4.1",
|
||||
"marked": "^0.3.2",
|
||||
|
@ -103,8 +106,10 @@
|
|||
"mkdirp": "^0.5.0",
|
||||
"mocha": "~1.20.1",
|
||||
"mocha-screencast-reporter": "~0.1.4",
|
||||
"nock": "^1.6.0",
|
||||
"opn": "~1.0.0",
|
||||
"path-browserify": "0.0.0",
|
||||
"portscanner": "^1.0.0",
|
||||
"progress": "^1.1.8",
|
||||
"requirejs": "~2.1.14",
|
||||
"rjs-build-analysis": "0.0.3",
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
var _ = require('lodash');
|
||||
var fs = require('fs');
|
||||
var yaml = require('js-yaml');
|
||||
var path = require('path');
|
||||
var listPlugins = require('../lib/list_plugins');
|
||||
var configPath = process.env.CONFIG_PATH || path.join(__dirname, 'kibana.yml');
|
||||
var kibana = yaml.safeLoad(fs.readFileSync(configPath, 'utf8'));
|
||||
var env = process.env.NODE_ENV || 'development';
|
||||
|
||||
function checkPath(path) {
|
||||
try {
|
||||
fs.statSync(path);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the local public folder is present. This means we are running in
|
||||
// the NPM module. If it's not there then we are running in the git root.
|
||||
var public_folder = path.resolve(__dirname, '..', 'public');
|
||||
if (!checkPath(public_folder)) public_folder = path.resolve(__dirname, '..', '..', 'kibana');
|
||||
|
||||
// Check to see if htpasswd file exists in the root directory otherwise set it to false
|
||||
var htpasswdPath = path.resolve(__dirname, '..', '..', '.htpasswd');
|
||||
if (!checkPath(htpasswdPath)) htpasswdPath = path.resolve(__dirname, '..', '..', '..', '..', '.htpasswd');
|
||||
if (!checkPath(htpasswdPath)) htpasswdPath = false;
|
||||
|
||||
var packagePath = path.resolve(__dirname, '..', 'package.json');
|
||||
try {
|
||||
fs.statSync(packagePath);
|
||||
} catch (err) {
|
||||
packagePath = path.resolve(__dirname, '..', '..', '..', 'package.json');
|
||||
}
|
||||
|
||||
var config = module.exports = {
|
||||
port : kibana.port || 5601,
|
||||
host : kibana.host || '0.0.0.0',
|
||||
elasticsearch : kibana.elasticsearch_url || 'http : //localhost : 9200',
|
||||
root : path.normalize(path.join(__dirname, '..')),
|
||||
quiet : false,
|
||||
public_folder : public_folder,
|
||||
external_plugins_folder : process.env.PLUGINS_FOLDER || null,
|
||||
bundled_plugins_folder : path.resolve(public_folder, 'plugins'),
|
||||
kibana : kibana,
|
||||
package : require(packagePath),
|
||||
htpasswd : htpasswdPath,
|
||||
buildNum : '@@buildNum',
|
||||
maxSockets : kibana.maxSockets || Infinity,
|
||||
log_file : kibana.log_file || null
|
||||
};
|
||||
|
||||
config.plugins = listPlugins(config);
|
|
@ -1,54 +0,0 @@
|
|||
# Kibana is served by a back end server. This controls which port to use.
|
||||
port: 5601
|
||||
|
||||
# The host to bind the server to.
|
||||
host: "0.0.0.0"
|
||||
|
||||
# The Elasticsearch instance to use for all your queries.
|
||||
elasticsearch_url: "http://localhost:9200"
|
||||
|
||||
# preserve_elasticsearch_host true will send the hostname specified in `elasticsearch`. If you set it to false,
|
||||
# then the host you use to connect to *this* Kibana instance will be sent.
|
||||
elasticsearch_preserve_host: true
|
||||
|
||||
# Kibana uses an index in Elasticsearch to store saved searches, visualizations
|
||||
# and dashboards. It will create a new index if it doesn't already exist.
|
||||
kibana_index: ".kibana"
|
||||
|
||||
# If your Elasticsearch is protected with basic auth, this is the user credentials
|
||||
# used by the Kibana server to perform maintence on the kibana_index at statup. Your Kibana
|
||||
# users will still need to authenticate with Elasticsearch (which is proxied thorugh
|
||||
# the Kibana server)
|
||||
# kibana_elasticsearch_username: user
|
||||
# kibana_elasticsearch_password: pass
|
||||
|
||||
|
||||
# The default application to load.
|
||||
default_app_id: "discover"
|
||||
|
||||
# Time in milliseconds to wait for responses from the back end or elasticsearch.
|
||||
# This must be > 0
|
||||
request_timeout: 300000
|
||||
|
||||
# Time in milliseconds for Elasticsearch to wait for responses from shards.
|
||||
# Set to 0 to disable.
|
||||
shard_timeout: 0
|
||||
|
||||
# Set to false to have a complete disregard for the validity of the SSL
|
||||
# certificate.
|
||||
verify_ssl: true
|
||||
|
||||
# If you need to provide a CA certificate for your Elasticsarech instance, put
|
||||
# the path of the pem file here.
|
||||
# ca: /path/to/your/CA.pem
|
||||
|
||||
# SSL for outgoing requests from the Kibana Server (PEM formatted)
|
||||
# ssl_key_file: /path/to/your/server.key
|
||||
# ssl_cert_file: /path/to/your/server.crt
|
||||
|
||||
# Set the path to where you would like the process id file to be created.
|
||||
# pid_file: /var/run/kibana.pid
|
||||
|
||||
# If you would like to send the log output to a file you can set the path below.
|
||||
# This will also turn off the STDOUT log output.
|
||||
# log_file: ./kibana.log
|
|
@ -1,5 +1,5 @@
|
|||
module.exports.extendHapi = require('./lib/extend_hapi');
|
||||
module.exports.Plugin = require('./lib/plugin');
|
||||
module.exports.Plugin = require('./lib/plugins/plugin');
|
||||
module.exports.start = require('./lib/start');
|
||||
|
||||
if (require.main === module) {
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
module.exports = function () {
|
||||
return require('../config');
|
||||
};
|
10
src/hapi/lib/config/check_path.js
Normal file
10
src/hapi/lib/config/check_path.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
var fs = require('fs');
|
||||
module.exports = function checkPath(path) {
|
||||
try {
|
||||
fs.statSync(path);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
72
src/hapi/lib/config/config.js
Normal file
72
src/hapi/lib/config/config.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
var Promise = require('bluebird');
|
||||
var Joi = require('joi');
|
||||
var _ = require('lodash');
|
||||
var override = require('./override');
|
||||
_.mixin(require('lodash-deep'));
|
||||
|
||||
function Config(schema, config) {
|
||||
config = config || {};
|
||||
this.schema = Joi.compile(schema || {});
|
||||
this.reset(config);
|
||||
}
|
||||
|
||||
Config.prototype.reset = function (obj) {
|
||||
var results = Joi.validate(obj, this.schema);
|
||||
if (results.error) {
|
||||
throw results.error;
|
||||
}
|
||||
this.config = results.value;
|
||||
};
|
||||
|
||||
Config.prototype.set = function (key, value) {
|
||||
var config = _.cloneDeep(this.config);
|
||||
if (_.isPlainObject(key)) {
|
||||
config = override(config, key);
|
||||
} else {
|
||||
_.deepSet(config, key, value);
|
||||
}
|
||||
var results = Joi.validate(config, this.schema);
|
||||
if (results.error) {
|
||||
throw results.error;
|
||||
}
|
||||
this.config = results.value;
|
||||
};
|
||||
|
||||
Config.prototype.get = function (key) {
|
||||
if (!key) {
|
||||
return _.cloneDeep(this.config);
|
||||
}
|
||||
|
||||
var value = _.deepGet(this.config, key);
|
||||
if (value === undefined) {
|
||||
if (!this.has(key)) {
|
||||
throw new Error('Unknown config key: ' + key);
|
||||
}
|
||||
}
|
||||
return _.cloneDeep(value);
|
||||
};
|
||||
|
||||
Config.prototype.has = function (key) {
|
||||
function has(key, schema, path) {
|
||||
path = path || [];
|
||||
// Catch the partial paths
|
||||
if (path.join('.') === key) return true;
|
||||
// Only go deep on inner objects with children
|
||||
if (schema._inner.children.length) {
|
||||
for (var i = 0; i < schema._inner.children.length; i++) {
|
||||
var child = schema._inner.children[i];
|
||||
// If the child is an object recurse through it's children and return
|
||||
// true if there's a match
|
||||
if (child.schema._type === 'object') {
|
||||
if (has(key, child.schema, path.concat([child.key]))) return true;
|
||||
// if the child matches, return true
|
||||
} else if (path.concat([child.key]).join('.') === key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return !!has(key, this.schema);
|
||||
};
|
||||
|
||||
module.exports = Config;
|
19
src/hapi/lib/config/explode_by.js
Normal file
19
src/hapi/lib/config/explode_by.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
var _ = require('lodash');
|
||||
module.exports = function (dot, flatObject) {
|
||||
var fullObject = {};
|
||||
_.each(flatObject, function (value, key) {
|
||||
var keys = key.split(dot);
|
||||
(function walk(memo, keys, value) {
|
||||
var _key = keys.shift();
|
||||
if (keys.length === 0) {
|
||||
memo[_key] = value;
|
||||
} else {
|
||||
if (!memo[_key]) memo[_key] = {};
|
||||
walk(memo[_key], keys, value);
|
||||
}
|
||||
})(fullObject, keys, value);
|
||||
});
|
||||
return fullObject;
|
||||
};
|
||||
|
||||
|
18
src/hapi/lib/config/flatten_with.js
Normal file
18
src/hapi/lib/config/flatten_with.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
var _ = require('lodash');
|
||||
module.exports = function (dot, nestedObj, flattenArrays) {
|
||||
var key; // original key
|
||||
var stack = []; // track key stack
|
||||
var flatObj = {};
|
||||
(function flattenObj(obj) {
|
||||
_.keys(obj).forEach(function (key) {
|
||||
stack.push(key);
|
||||
if (!flattenArrays && _.isArray(obj[key])) flatObj[stack.join(dot)] = obj[key];
|
||||
else if (_.isObject(obj[key])) flattenObj(obj[key]);
|
||||
else flatObj[stack.join(dot)] = obj[key];
|
||||
stack.pop();
|
||||
});
|
||||
}(nestedObj));
|
||||
return flatObj;
|
||||
};
|
||||
|
||||
|
6
src/hapi/lib/config/index.js
Normal file
6
src/hapi/lib/config/index.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
var Config = require('./config');
|
||||
var schema = require('./schema');
|
||||
var config = new Config(schema);
|
||||
module.exports = function () {
|
||||
return config;
|
||||
};
|
11
src/hapi/lib/config/override.js
Normal file
11
src/hapi/lib/config/override.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
var _ = require('lodash');
|
||||
var flattenWith = require('./flatten_with');
|
||||
var explodeBy = require('./explode_by');
|
||||
|
||||
module.exports = function (target, source) {
|
||||
var _target = flattenWith('.', target);
|
||||
var _source = flattenWith('.', source);
|
||||
return explodeBy('.', _.defaults(_source, _target));
|
||||
};
|
||||
|
||||
|
65
src/hapi/lib/config/schema.js
Normal file
65
src/hapi/lib/config/schema.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
var Joi = require('joi');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var checkPath = require('./check_path');
|
||||
var packagePath = path.resolve(__dirname, '..', '..', 'package.json');
|
||||
|
||||
// Check if the local public folder is present. This means we are running in
|
||||
// the NPM module. If it's not there then we are running in the git root.
|
||||
var publicFolder = path.resolve(__dirname, '..', '..', 'public');
|
||||
if (!checkPath(publicFolder)) publicFolder = path.resolve(__dirname, '..', '..', '..', 'kibana');
|
||||
|
||||
try {
|
||||
fs.statSync(packagePath);
|
||||
} catch (err) {
|
||||
packagePath = path.resolve(__dirname, '..', '..', '..', '..', 'package.json');
|
||||
}
|
||||
|
||||
var bundledPluginsFolder = path.resolve(publicFolder, 'plugins');
|
||||
|
||||
|
||||
module.exports = Joi.object({
|
||||
kibana: Joi.object({
|
||||
server: Joi.object({
|
||||
host: Joi.string().hostname().default('0.0.0.0'),
|
||||
port: Joi.number().default(5601),
|
||||
maxSockets: Joi.any().default(Infinity),
|
||||
pidFile: Joi.string(),
|
||||
root: Joi.string().default(path.normalize(path.join(__dirname, '..')))
|
||||
}).default(),
|
||||
index: Joi.string().default('.kibana'),
|
||||
publicFolder: Joi.string().default(publicFolder),
|
||||
externalPluginsFolder: Joi.alternatives().try(Joi.array().items(Joi.string()), Joi.string()),
|
||||
bundledPluginsFolder: Joi.string().default(bundledPluginsFolder),
|
||||
defaultAppId: Joi.string().default('discover'),
|
||||
package: Joi.any().default(require(packagePath)),
|
||||
buildNum: Joi.string().default('@@buildNum'),
|
||||
bundledPluginIds: Joi.array().items(Joi.string())
|
||||
}).default(),
|
||||
elasticsearch: Joi.object({
|
||||
url: Joi.string().uri({ scheme: ['http', 'https'] }).default('http://localhost:9200'),
|
||||
preserveHost: Joi.boolean().default(true),
|
||||
username: Joi.string(),
|
||||
password: Joi.string(),
|
||||
sharedTimeout: Joi.number().default(0),
|
||||
requestTimeout: Joi.number().default(30000),
|
||||
ssl: Joi.object({
|
||||
verify: Joi.boolean().default(true),
|
||||
ca: Joi.string(),
|
||||
cert: Joi.string(),
|
||||
key: Joi.string()
|
||||
}).default(),
|
||||
}).default(),
|
||||
logging: Joi.object({
|
||||
quiet: Joi.boolean().default(false),
|
||||
file: Joi.string(),
|
||||
console: Joi.object({
|
||||
ops: Joi.any().default('*'),
|
||||
log: Joi.any().default('*'),
|
||||
response: Joi.any().default('*'),
|
||||
error: Joi.any().default('*'),
|
||||
json: Joi.boolean().default(false),
|
||||
}).default()
|
||||
}).default(),
|
||||
}).default();
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
module.exports = function (server) {
|
||||
server.decorate('server', 'config', require('./config'));
|
||||
server.decorate('server', 'loadKibanaPlugins', require('./load_kibana_plugins'));
|
||||
server.decorate('server', 'loadKibanaPlugins', require('./plugins/load_kibana_plugins'));
|
||||
};
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
var _ = require('lodash');
|
||||
var glob = require('glob');
|
||||
var path = require('path');
|
||||
|
||||
var plugins = function (dir) {
|
||||
if (!dir) return [];
|
||||
var files = glob.sync(path.join(dir, '*', 'index.js')) || [];
|
||||
return files.map(function (file) {
|
||||
return file.replace(dir, 'plugins').replace(/\.js$/, '');
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = function (config) {
|
||||
var bundled_plugin_ids = config.kibana.bundled_plugin_ids || [];
|
||||
var bundled_plugins = plugins(config.bundled_plugins_folder);
|
||||
var external_plugins = plugins(config.external_plugins_folder);
|
||||
return bundled_plugin_ids.concat(bundled_plugins, external_plugins);
|
||||
};
|
||||
|
35
src/hapi/lib/logging/index.js
Normal file
35
src/hapi/lib/logging/index.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
var Promise = require('bluebird');
|
||||
var good = require('good');
|
||||
var path = require('path');
|
||||
var join = path.join;
|
||||
var Console = require('./good_reporters/console');
|
||||
|
||||
|
||||
module.exports = function (server) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var reporters = [];
|
||||
var config = server.config();
|
||||
|
||||
// If we are not quite then add the console logger
|
||||
var filters = {};
|
||||
if (!config.get('logging.quiet')) {
|
||||
if (config.get('logging.console.ops') != null) filters.ops = config.get('logging.console.ops');
|
||||
if (config.get('logging.console.log') != null) filters.log = config.get('logging.console.log');
|
||||
if (config.get('logging.console.response') != null) filters.response = config.get('logging.console.response');
|
||||
if (config.get('logging.console.error') != null) filters.error = config.get('logging.console.error');
|
||||
}
|
||||
reporters.push({ reporter: Console, args: [filters, { json: config.get('logging.console.json') } ] });
|
||||
server.register({
|
||||
register: good,
|
||||
options: {
|
||||
opsInterval: 5000,
|
||||
logRequestHeaders: true,
|
||||
logResponsePayload: true,
|
||||
reporters: reporters
|
||||
}
|
||||
}, function (err) {
|
||||
if (err) return reject(err);
|
||||
resolve(server);
|
||||
});
|
||||
});
|
||||
};
|
|
@ -1,17 +0,0 @@
|
|||
var _ = require('lodash');
|
||||
var Promise = require('bluebird');
|
||||
var getStatus = require('./get_status');
|
||||
var setStatus = require('./set_status');
|
||||
var util = require('util');
|
||||
|
||||
function Plugin(options) {
|
||||
options = _.defaults(options, {
|
||||
require: [],
|
||||
init: function (server, options) {
|
||||
return Promise.reject(new Error('You must override the init function for plugins'));
|
||||
}
|
||||
});
|
||||
_.assign(this, options);
|
||||
}
|
||||
|
||||
module.exports = Plugin;
|
15
src/hapi/lib/plugins/add_statics_for_public.js
Normal file
15
src/hapi/lib/plugins/add_statics_for_public.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
var Promise = require('bluebird');
|
||||
module.exports = function (plugin) {
|
||||
if (plugin.publicPath) {
|
||||
plugin.server.route({
|
||||
method: 'GET',
|
||||
path: '/' + plugin.name + '/{paths*}',
|
||||
handler: {
|
||||
directory: {
|
||||
path: plugin.publicPath
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return Promise.resolve(plugin);
|
||||
};
|
24
src/hapi/lib/plugins/list_plugins.js
Normal file
24
src/hapi/lib/plugins/list_plugins.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
var _ = require('lodash');
|
||||
var glob = require('glob');
|
||||
var path = require('path');
|
||||
|
||||
var plugins = function (dir) {
|
||||
if (!dir) return [];
|
||||
var files = glob.sync(path.join(dir, '*', 'index.js')) || [];
|
||||
return files.map(function (file) {
|
||||
return file.replace(dir, 'plugins').replace(/\.js$/, '');
|
||||
});
|
||||
};
|
||||
|
||||
var cache;
|
||||
|
||||
module.exports = function (config) {
|
||||
if (!cache) {
|
||||
var bundled_plugin_ids = config.get('kibana.bundledPluginIds') || [];
|
||||
var bundled_plugins = plugins(config.get('kibana.bundledPluginsFolder'));
|
||||
var external_plugins = plugins(config.get('kibana.externalPluginsFolder'));
|
||||
cache = bundled_plugin_ids.concat(bundled_plugins, external_plugins);
|
||||
}
|
||||
return cache;
|
||||
};
|
||||
|
|
@ -1,11 +1,9 @@
|
|||
var _ = require('lodash');
|
||||
var Promise = require('bluebird');
|
||||
var registerPlugins = require('./register_plugins');
|
||||
var requirePlugins = require('./require_plugins');
|
||||
var setupLogging = require('./setup_logging');
|
||||
var logging = require('../logging/');
|
||||
module.exports = function (externalPlugins) {
|
||||
var plugins = requirePlugins().concat(externalPlugins);
|
||||
return setupLogging(this).then(function (server) {
|
||||
return logging(this).then(function (server) {
|
||||
registerPlugins(server, plugins);
|
||||
});
|
||||
};
|
15
src/hapi/lib/plugins/plugin.js
Normal file
15
src/hapi/lib/plugins/plugin.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
var _ = require('lodash');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
function Plugin(options) {
|
||||
this.server = null;
|
||||
this.status = null;
|
||||
this.publicPath = null;
|
||||
this.require = [];
|
||||
this.init = function (server, options) {
|
||||
return Promise.reject(new Error('You must override the init function for plugins'));
|
||||
};
|
||||
_.assign(this, options);
|
||||
}
|
||||
|
||||
module.exports = Plugin;
|
|
@ -1,7 +1,8 @@
|
|||
var _ = require('lodash');
|
||||
var Promise = require('bluebird');
|
||||
var checkDependencies = require('./check_dependencies');
|
||||
var systemStatus = require('./system_status');
|
||||
var status = require('../status');
|
||||
var addStaticsForPublic = require('./add_statics_for_public');
|
||||
|
||||
function checkForCircularDependency(tasks) {
|
||||
var deps = {};
|
||||
|
@ -35,16 +36,15 @@ module.exports = function (server, plugins) {
|
|||
return new Promise(function (resolve, reject) {
|
||||
var register = function (server, options, next) {
|
||||
plugin.server = server;
|
||||
systemStatus.createStatus(plugin);
|
||||
plugin.status.yellow('Initializing');
|
||||
status.createStatus(plugin);
|
||||
Promise.try(plugin.init, [server, options], plugin).nodeify(next);
|
||||
};
|
||||
register.attributes = { name: plugin.name };
|
||||
var options = config[plugin.name] || {};
|
||||
server.register({ register: register, options: options }, function (err) {
|
||||
if (err) return reject(err);
|
||||
resolve();
|
||||
plugin.status.green('Ready');
|
||||
resolve(plugin);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ module.exports = function (server, plugins) {
|
|||
if (!plugin.require || (plugin.require && allDone(plugin.require))) {
|
||||
running[plugin.name] = true;
|
||||
registerPlugin(plugin)
|
||||
.then(addStaticsForPublic)
|
||||
.then(function () {
|
||||
results[plugin.name] = true;
|
||||
runPending();
|
|
@ -2,9 +2,10 @@ var path = require('path');
|
|||
var join = path.join;
|
||||
var glob = require('glob');
|
||||
var Promise = require('bluebird');
|
||||
var checkPath = require('../config/check_path');
|
||||
|
||||
module.exports = function (globPath) {
|
||||
globPath = globPath || join( __dirname, '..', 'plugins', '*', 'index.js');
|
||||
globPath = globPath || join( __dirname, '..', '..', 'plugins', '*', 'index.js');
|
||||
return glob.sync(globPath).map(function (file) {
|
||||
var module = require(file);
|
||||
var regex = new RegExp('([^' + path.sep + ']+)' + path.sep + 'index.js');
|
||||
|
@ -12,6 +13,12 @@ module.exports = function (globPath) {
|
|||
if (!module.name && matches) {
|
||||
module.name = matches[1];
|
||||
}
|
||||
|
||||
// has a public folder?
|
||||
var publicPath = join(path.dirname(file), 'public');
|
||||
if (checkPath(publicPath)) {
|
||||
module.publicPath = publicPath;
|
||||
}
|
||||
return module;
|
||||
});
|
||||
};
|
|
@ -1,29 +0,0 @@
|
|||
var Promise = require('bluebird');
|
||||
var good = require('good');
|
||||
var path = require('path');
|
||||
var join = path.join;
|
||||
var Console = require('./good_reporters/console');
|
||||
|
||||
var reporters = [
|
||||
{
|
||||
reporter: Console,
|
||||
args: [{ ops: '*', log: '*', response: '*', error: '*' }, { json: false }]
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = function (server) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
server.register({
|
||||
register: good,
|
||||
options: {
|
||||
opsInterval: 5000,
|
||||
logRequestHeaders: true,
|
||||
logResponsePayload: true,
|
||||
reporters: reporters
|
||||
}
|
||||
}, function (err) {
|
||||
if (err) return reject(err);
|
||||
resolve(server);
|
||||
});
|
||||
});
|
||||
};
|
|
@ -1,10 +1,13 @@
|
|||
var _ = require('lodash');
|
||||
var Promise = require('bluebird');
|
||||
var Hapi = require('hapi');
|
||||
var requirePlugins = require('./require_plugins');
|
||||
var validatePlugin = require('./validate_plugin');
|
||||
var requirePlugins = require('./plugins/require_plugins');
|
||||
var validatePlugin = require('./plugins/validate_plugin');
|
||||
var extendHapi = require('./extend_hapi');
|
||||
var join = require('path').join;
|
||||
|
||||
module.exports = function (plugins) {
|
||||
|
||||
module.exports = function (settings, plugins) {
|
||||
// Plugin authors can use this to add plugins durring development
|
||||
plugins = plugins || [];
|
||||
|
||||
|
@ -18,13 +21,24 @@ module.exports = function (plugins) {
|
|||
// Extend Hapi with Kibana
|
||||
extendHapi(server);
|
||||
|
||||
var config = server.config();
|
||||
if (settings) config.set(settings);
|
||||
|
||||
// Create a new connection
|
||||
server.connection({ host: server.config().host, port: server.config().port });
|
||||
server.connection({
|
||||
host: config.get('kibana.server.host'),
|
||||
port: config.get('kibana.server.port')
|
||||
});
|
||||
|
||||
// Load external plugins
|
||||
var externalPlugins = [];
|
||||
if (server.config().external_plugins_folder) {
|
||||
externalPlugins = requirePlugins(server.config().external_plugins_folder);
|
||||
var externalPluginsFolder = config.get('kibana.externalPluginsFolder');
|
||||
if (externalPluginsFolder) {
|
||||
externalPlugins = _([externalPluginsFolder])
|
||||
.flatten()
|
||||
.map(requirePlugins)
|
||||
.flatten()
|
||||
.value();
|
||||
}
|
||||
|
||||
// Load the plugins
|
||||
|
@ -41,6 +55,7 @@ module.exports = function (plugins) {
|
|||
})
|
||||
.catch(function (err) {
|
||||
server.log('fatal', err);
|
||||
console.log(err.stack);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ SystemStatus.prototype.createStatus = function (plugin) {
|
|||
plugin.server.expose('status', plugin.status);
|
||||
plugin.status.on('change', logStatusChange(plugin));
|
||||
this.data[plugin.name] = plugin.status;
|
||||
plugin.status.yellow('Initializing');
|
||||
};
|
||||
|
||||
SystemStatus.prototype.toJSON = function () {
|
|
@ -19,7 +19,7 @@ function createStatusFn(color) {
|
|||
this.state = color;
|
||||
this.message = message;
|
||||
if (previous.state === this.state && previous.message === this.message) return;
|
||||
this.emit(color, message);
|
||||
this.emit(color, message, previous);
|
||||
this.emit('change', this.toJSON(), previous);
|
||||
};
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
var _ = require('lodash');
|
||||
var Promise = require('bluebird');
|
||||
var kibana = require('../../');
|
||||
var listPlugins = require('../../lib/plugins/list_plugins');
|
||||
|
||||
module.exports = new kibana.Plugin({
|
||||
init: function (server, options) {
|
||||
|
@ -10,14 +11,12 @@ module.exports = new kibana.Plugin({
|
|||
path: '/config',
|
||||
handler: function (request, reply) {
|
||||
var config = server.config();
|
||||
var keys = [
|
||||
'kibana_index',
|
||||
'default_app_id',
|
||||
'shard_timeout'
|
||||
];
|
||||
var data = _.pick(config.kibana, keys);
|
||||
data.plugins = config.plugins;
|
||||
reply(data);
|
||||
reply({
|
||||
kibana_index: config.get('kibana.index'),
|
||||
default_app_id: config.get('kibana.defaultAppId'),
|
||||
shard_timeout: config.get('elasticsearch.sharedTimeout'),
|
||||
plugins: listPlugins(config)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
var url = require('url');
|
||||
var http = require('http');
|
||||
var fs = require('fs');
|
||||
var resolve = require('url').resolve;
|
||||
var querystring = require('querystring');
|
||||
var kibana = require('../../');
|
||||
var healthCheck = require('./lib/health_check');
|
||||
var exposeClient = require('./lib/expose_client');
|
||||
var createProxy = require('./lib/create_proxy');
|
||||
|
||||
module.exports = new kibana.Plugin({
|
||||
|
||||
|
@ -17,44 +19,28 @@ module.exports = new kibana.Plugin({
|
|||
// Set up the health check service
|
||||
healthCheck(this, server);
|
||||
|
||||
var target = url.parse(config.elasticsearch);
|
||||
createProxy(server, 'GET', '/elasticsearch/{paths*}');
|
||||
createProxy(server, 'POST', '/elasticsearch/_mget');
|
||||
createProxy(server, 'POST', '/elasticsearch/_msearch');
|
||||
|
||||
var agentOptions = {
|
||||
rejectUnauthorized: config.kibana.verify_ssl
|
||||
};
|
||||
|
||||
var customCA;
|
||||
if (/^https/.test(target.protocol) && config.kibana.ca) {
|
||||
customCA = fs.readFileSync(config.kibana.ca, 'utf8');
|
||||
agentOptions.ca = [customCA];
|
||||
}
|
||||
|
||||
// Add client certificate and key if required by elasticsearch
|
||||
if (/^https/.test(target.protocol) &&
|
||||
config.kibana.kibana_elasticsearch_client_crt &&
|
||||
config.kibana.kibana_elasticsearch_client_key) {
|
||||
agentOptions.crt = fs.readFileSync(config.kibana.kibana_elasticsearch_client_crt, 'utf8');
|
||||
agentOptions.key = fs.readFileSync(config.kibana.kibana_elasticsearch_client_key, 'utf8');
|
||||
}
|
||||
|
||||
server.route({
|
||||
method: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
|
||||
path: '/elasticsearch/{path*}',
|
||||
handler: {
|
||||
proxy: {
|
||||
mapUri: function (request, callback) {
|
||||
var url = config.elasticsearch;
|
||||
if (!/\/$/.test(url)) url += '/';
|
||||
if (request.params.path) url += request.params.path;
|
||||
var query = querystring.stringify(request.query);
|
||||
if (query) url += '?' + query;
|
||||
callback(null, url);
|
||||
},
|
||||
passThrough: true,
|
||||
agent: new http.Agent(agentOptions)
|
||||
}
|
||||
function noBulkCheck(request, reply) {
|
||||
if (/\/_bulk/.test(request.path)) {
|
||||
return reply({
|
||||
error: 'You can not send _bulk requests to this interface.'
|
||||
}).code(400).takeover();
|
||||
}
|
||||
});
|
||||
return reply.continue();
|
||||
}
|
||||
|
||||
createProxy(
|
||||
server,
|
||||
['PUT', 'POST', 'DELETE'],
|
||||
'/elasticsearch/' + config.get('kibana.index') + '/{paths*}',
|
||||
{
|
||||
prefix: '/' + config.get('kibana.index'),
|
||||
config: { pre: [ noBulkCheck ] }
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
});
|
||||
|
|
30
src/hapi/plugins/elasticsearch/lib/create_agent.js
Normal file
30
src/hapi/plugins/elasticsearch/lib/create_agent.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
var url = require('url');
|
||||
var fs = require('fs');
|
||||
var http = require('http');
|
||||
var agentOptions;
|
||||
module.exports = function (server) {
|
||||
var config = server.config();
|
||||
var target = url.parse(config.get('elasticsearch.url'));
|
||||
|
||||
if (!agentOptions) {
|
||||
agentOptions = {
|
||||
rejectUnauthorized: config.get('elasticsearch.ssl.verify')
|
||||
};
|
||||
|
||||
var customCA;
|
||||
if (/^https/.test(target.protocol) && config.get('elasticsearch.ssl.ca')) {
|
||||
customCA = fs.readFileSync(config.get('elasticsearch.ssl.ca'), 'utf8');
|
||||
agentOptions.ca = [customCA];
|
||||
}
|
||||
|
||||
// Add client certificate and key if required by elasticsearch
|
||||
if (/^https/.test(target.protocol) &&
|
||||
config.get('elasticsearch.ssl.cert') &&
|
||||
config.get('elasticsearch.ssl.key')) {
|
||||
agentOptions.crt = fs.readFileSync(config.get('elasticsearch.ssl.cert'), 'utf8');
|
||||
agentOptions.key = fs.readFileSync(config.get('elasticsearch.ssl.key'), 'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
return new http.Agent(agentOptions);
|
||||
};
|
19
src/hapi/plugins/elasticsearch/lib/create_proxy.js
Normal file
19
src/hapi/plugins/elasticsearch/lib/create_proxy.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
var createAgent = require('./create_agent');
|
||||
var mapUri = require('./map_uri');
|
||||
module.exports = function createProxy(server, method, route, opts) {
|
||||
opts = opts || {};
|
||||
var options = {
|
||||
method: method,
|
||||
path: route,
|
||||
handler: {
|
||||
proxy: {
|
||||
mapUri: mapUri(server, opts.prefix),
|
||||
passThrough: true,
|
||||
agent: createAgent(server)
|
||||
}
|
||||
}
|
||||
};
|
||||
if (opts && opts.config) options.config = opts.config;
|
||||
server.route(options);
|
||||
};
|
||||
|
|
@ -6,13 +6,13 @@ var url = require('url');
|
|||
|
||||
module.exports = function (server) {
|
||||
var config = server.config();
|
||||
var uri = url.parse(config.elasticsearch);
|
||||
var username = config.kibana.kibana_elasticsearch_username;
|
||||
var password = config.kibana.kibana_elasticsearch_password;
|
||||
var verify_ssl = config.kibana.verify_ssl;
|
||||
var client_crt = config.kibana.kibana_elasticsearch_client_crt;
|
||||
var client_key = config.kibana.kibana_elasticsearch_client_key;
|
||||
var ca = config.kibana.ca;
|
||||
var uri = url.parse(config.get('elasticsearch.url'));
|
||||
var username = config.get('elasticsearch.username');
|
||||
var password = config.get('elasticsearch.password');
|
||||
var verify_ssl = config.get('elasticsearch.ssl.verify');
|
||||
var client_crt = config.get('elasticsearch.ssl.cert');
|
||||
var client_key = config.get('elasticsearch.ssl.key');
|
||||
var ca = config.get('elasticsearch.ssl.ca');
|
||||
|
||||
if (username && password) {
|
||||
uri.auth = util.format('%s:%s', username, password);
|
||||
|
|
|
@ -15,7 +15,7 @@ module.exports = function (plugin, server) {
|
|||
return client.ping({ requestTimeout: 1500 }).catch(function (err) {
|
||||
if (!(err instanceof NoConnections)) throw err;
|
||||
|
||||
plugin.status.red(format('Unable to connect to Elasticsearch at %s. Retrying in 2.5 seconds.', config.elasticsearch));
|
||||
plugin.status.red(format('Unable to connect to Elasticsearch at %s. Retrying in 2.5 seconds.', config.get('elasticsearch.url')));
|
||||
|
||||
return Promise.delay(2500).then(waitForPong);
|
||||
});
|
||||
|
@ -24,7 +24,7 @@ module.exports = function (plugin, server) {
|
|||
function waitForShards() {
|
||||
return client.cluster.health({
|
||||
timeout: '5s', // tells es to not sit around and wait forever
|
||||
index: config.kibana.kibana_index
|
||||
index: config.get('kibana.index')
|
||||
})
|
||||
.then(function (resp) {
|
||||
// if "timed_out" === true then elasticsearch could not
|
||||
|
|
20
src/hapi/plugins/elasticsearch/lib/map_uri.js
Normal file
20
src/hapi/plugins/elasticsearch/lib/map_uri.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
var querystring = require('querystring');
|
||||
var resolve = require('url').resolve;
|
||||
module.exports = function mapUri(server, prefix) {
|
||||
var config = server.config();
|
||||
return function (request, done) {
|
||||
var paths = request.params.paths;
|
||||
if (!paths) {
|
||||
paths = request.path.replace('/elasticsearch', '');
|
||||
}
|
||||
if (prefix) {
|
||||
paths = prefix + '/' + paths;
|
||||
}
|
||||
var url = config.get('elasticsearch.url');
|
||||
if (!/\/$/.test(url)) url += '/';
|
||||
if (paths) url = resolve(url, paths);
|
||||
var query = querystring.stringify(request.query);
|
||||
if (query) url += '?' + query;
|
||||
done(null, url);
|
||||
};
|
||||
};
|
112
src/hapi/plugins/elasticsearch/lib/validate.js
Normal file
112
src/hapi/plugins/elasticsearch/lib/validate.js
Normal file
|
@ -0,0 +1,112 @@
|
|||
var _ = require('lodash');
|
||||
var parse = require('url').parse;
|
||||
|
||||
validate.Fail = function (index) {
|
||||
this.message = 'Kibana only support modifying the "' + index +
|
||||
'" index. Requests that might modify other indicies are not sent to elasticsearch.';
|
||||
};
|
||||
|
||||
validate.BadIndex = function (index) {
|
||||
validate.Fail.call(this, index);
|
||||
this.message = 'Bad index "' + index + '" in request. ' + this.message;
|
||||
};
|
||||
|
||||
function validate(server, req) {
|
||||
var config = server.config();
|
||||
var method = req.method.toUpperCase();
|
||||
if (method === 'GET' || method === 'HEAD') return true;
|
||||
|
||||
var segments = _.compact(parse(req.path).pathname.split('/'));
|
||||
var maybeIndex = _.first(segments);
|
||||
var maybeMethod = _.last(segments);
|
||||
|
||||
var add = (method === 'POST' || method === 'PUT');
|
||||
var rem = (method === 'DELETE');
|
||||
|
||||
// everything below this point assumes a destructive request of some sort
|
||||
if (!add && !rem) throw new validate.Fail(config.get('kibana.index'));
|
||||
|
||||
var bodyStr = String(req.payload);
|
||||
var jsonBody = bodyStr && parseJson(bodyStr);
|
||||
var bulkBody = bodyStr && parseBulk(bodyStr);
|
||||
|
||||
// methods that accept standard json bodies
|
||||
var maybeMGet = ('_mget' === maybeMethod && add && jsonBody);
|
||||
var maybeSearch = ('_search' === maybeMethod && add);
|
||||
var maybeValidate = ('_validate' === maybeMethod && add);
|
||||
|
||||
// methods that accept bulk bodies
|
||||
var maybeBulk = ('_bulk' === maybeMethod && add && bulkBody);
|
||||
var maybeMsearch = ('_msearch' === maybeMethod && add && bulkBody);
|
||||
|
||||
// indication that this request is against kibana
|
||||
var maybeKibanaIndex = (maybeIndex === config.get('kibana.index'));
|
||||
|
||||
if (!maybeBulk) validateNonBulkDestructive();
|
||||
else validateBulkBody(bulkBody);
|
||||
|
||||
return true;
|
||||
|
||||
function parseJson(str) {
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function parseBulk(str) {
|
||||
var parts = str.split(/\r?\n/);
|
||||
|
||||
var finalLine = parts.pop();
|
||||
var evenJsons = (parts.length % 2 === 0);
|
||||
|
||||
if (finalLine !== '' || !evenJsons) return;
|
||||
|
||||
var body = new Array(parts.length);
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var part = parseJson(parts[i]);
|
||||
if (!part) throw new validate.Fail(config.get('kibana.index'));
|
||||
|
||||
body[i] = part;
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
function stringifyBulk(body) {
|
||||
return body.map(JSON.stringify).join('\n') + '\n';
|
||||
}
|
||||
|
||||
function validateNonBulkDestructive() {
|
||||
// allow any destructive request against the kibana index
|
||||
if (maybeKibanaIndex) return;
|
||||
|
||||
// allow json bodies sent to _mget _search and _validate
|
||||
if (maybeMGet || maybeSearch || maybeValidate) return;
|
||||
|
||||
// allow bulk bodies sent to _msearch
|
||||
if (maybeMsearch) return;
|
||||
|
||||
throw new validate.Fail(config.get('kibana.index'));
|
||||
}
|
||||
|
||||
function validateBulkBody(body) {
|
||||
while (body.length) {
|
||||
var header = body.shift();
|
||||
var req = body.shift();
|
||||
|
||||
var op = _.keys(header).join('');
|
||||
var meta = header[op];
|
||||
|
||||
if (!meta) throw new validate.Fail(config.get('kibana.index'));
|
||||
|
||||
var index = meta._index || maybeIndex;
|
||||
if (index !== config.get('kibana.index')) {
|
||||
throw new validate.BadIndex(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = validate;
|
||||
|
|
@ -8,7 +8,7 @@ module.exports = new kibana.Plugin({
|
|||
path: '/{param*}',
|
||||
handler: {
|
||||
directory: {
|
||||
path: config.public_folder
|
||||
path: config.get('kibana.publicFolder')
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,18 +1,7 @@
|
|||
var join = require('path').join;
|
||||
var kibana = require('../../');
|
||||
var systemStatus = require('../../lib/system_status');
|
||||
|
||||
function Series(size) {
|
||||
this.size = size;
|
||||
this.data = [];
|
||||
}
|
||||
Series.prototype.push = function (value) {
|
||||
this.data.unshift([Date.now(), value]);
|
||||
if (this.data.length > this.size) this.data.pop();
|
||||
};
|
||||
Series.prototype.toJSON = function () {
|
||||
return this.data;
|
||||
};
|
||||
var status = require('../../lib/status');
|
||||
var Series = require('./lib/series');
|
||||
|
||||
module.exports = new kibana.Plugin({
|
||||
|
||||
|
@ -33,7 +22,7 @@ module.exports = new kibana.Plugin({
|
|||
};
|
||||
|
||||
server.plugins.good.monitor.on('ops', function (event) {
|
||||
var port = String(config.port);
|
||||
var port = String(config.get('kibana.server.port'));
|
||||
fiveMinuteData.rss.push(event.psmem.rss);
|
||||
fiveMinuteData.heapTotal.push(event.psmem.heapTotal);
|
||||
fiveMinuteData.heapUsed.push(event.psmem.heapUsed);
|
||||
|
@ -56,15 +45,15 @@ module.exports = new kibana.Plugin({
|
|||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: '/status/{param*}',
|
||||
handler: {
|
||||
directory: {
|
||||
path: join(__dirname, 'public')
|
||||
}
|
||||
}
|
||||
});
|
||||
// server.route({
|
||||
// method: 'GET',
|
||||
// path: '/status/{param*}',
|
||||
// handler: {
|
||||
// directory: {
|
||||
// path: join(__dirname, 'public')
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
server.route({
|
||||
method: 'GET',
|
||||
|
@ -72,7 +61,7 @@ module.exports = new kibana.Plugin({
|
|||
handler: function (request, reply) {
|
||||
return reply({
|
||||
metrics: fiveMinuteData,
|
||||
status: systemStatus
|
||||
status: status
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
15
src/hapi/plugins/status/lib/series.js
Normal file
15
src/hapi/plugins/status/lib/series.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
function Series(size) {
|
||||
this.size = size;
|
||||
this.data = [];
|
||||
}
|
||||
|
||||
Series.prototype.push = function (value) {
|
||||
this.data.unshift([Date.now(), value]);
|
||||
if (this.data.length > this.size) this.data.pop();
|
||||
};
|
||||
|
||||
Series.prototype.toJSON = function () {
|
||||
return this.data;
|
||||
};
|
||||
|
||||
module.exports = Series;
|
0
test/unit/fixtures/require_from.js
Normal file
0
test/unit/fixtures/require_from.js
Normal file
4
test/unit/server/.jshintrc
Normal file
4
test/unit/server/.jshintrc
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"extends": "../../../.jshintrc.node",
|
||||
"mocha": true
|
||||
}
|
18
test/unit/server/lib/config/check_path.js
Normal file
18
test/unit/server/lib/config/check_path.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
var root = require('requirefrom')('');
|
||||
var checkPath = root('src/hapi/lib/config/check_path');
|
||||
var expect = require('expect.js');
|
||||
var path = require('path');
|
||||
var _ = require('lodash');
|
||||
|
||||
describe('checkPath(path)', function () {
|
||||
|
||||
it('should return true for files that exist', function () {
|
||||
expect(checkPath(__dirname)).to.be(true);
|
||||
});
|
||||
|
||||
it('should return true for files that exist', function () {
|
||||
expect(checkPath(path.join(__dirname, 'something_fake'))).to.be(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
192
test/unit/server/lib/config/config.js
Normal file
192
test/unit/server/lib/config/config.js
Normal file
|
@ -0,0 +1,192 @@
|
|||
var root = require('requirefrom')('');
|
||||
var Config = root('src/hapi/lib/config/config');
|
||||
var expect = require('expect.js');
|
||||
var _ = require('lodash');
|
||||
var Joi = require('joi');
|
||||
|
||||
/**
|
||||
* Plugins should defined a config method that takes a joi object. By default
|
||||
* it should return a way to disallow config
|
||||
*
|
||||
* Config should be newed up with a joi schema (containing defaults via joi)
|
||||
*
|
||||
* var schema = { ... }
|
||||
* new Config(schema);
|
||||
*
|
||||
*/
|
||||
|
||||
var data = {
|
||||
test: {
|
||||
hosts: ['host-01', 'host-02'],
|
||||
client: {
|
||||
type: 'datastore',
|
||||
host: 'store-01',
|
||||
port: 5050
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var schema = Joi.object({
|
||||
test: Joi.object({
|
||||
enable: Joi.boolean().default(true),
|
||||
hosts: Joi.array().items(Joi.string()),
|
||||
client: Joi.object({
|
||||
type: Joi.string().default('datastore'),
|
||||
host: Joi.string(),
|
||||
port: Joi.number()
|
||||
}).default(),
|
||||
undefValue: Joi.string()
|
||||
}).default()
|
||||
}).default();
|
||||
|
||||
describe('lib/config/config', function () {
|
||||
describe('class Config()', function () {
|
||||
|
||||
describe('constructor', function () {
|
||||
|
||||
it('should not allow any config if the schema is not passed', function (done) {
|
||||
var config = new Config();
|
||||
var run = function () {
|
||||
config.set('something.enable', true);
|
||||
};
|
||||
expect(run).to.throwException();
|
||||
done();
|
||||
});
|
||||
|
||||
it('should set defaults', function () {
|
||||
var config = new Config(schema);
|
||||
expect(config.get('test.enable')).to.be(true);
|
||||
expect(config.get('test.client.type')).to.be('datastore');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#reset(object)', function () {
|
||||
|
||||
var config;
|
||||
beforeEach(function () {
|
||||
config = new Config(schema);
|
||||
});
|
||||
|
||||
it('should reset the config object with new values', function () {
|
||||
config.set(data);
|
||||
var newData = config.get();
|
||||
newData.test.enable = false;
|
||||
config.reset(newData);
|
||||
expect(config.get()).to.eql(newData);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#has(key)', function () {
|
||||
|
||||
var config;
|
||||
beforeEach(function () {
|
||||
config = new Config(schema);
|
||||
});
|
||||
|
||||
it('should return true for fields that exist in the schema', function () {
|
||||
expect(config.has('test.undefValue')).to.be(true);
|
||||
});
|
||||
|
||||
it('should return true for partial objects that exist in the schema', function () {
|
||||
expect(config.has('test.client')).to.be(true);
|
||||
});
|
||||
|
||||
it('should return false for fields that do not exist in the schema', function () {
|
||||
expect(config.has('test.client.pool')).to.be(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#set(key, value)', function () {
|
||||
var config;
|
||||
|
||||
beforeEach(function () {
|
||||
config = new Config(schema);
|
||||
});
|
||||
|
||||
it('should use a key and value to set a config value', function () {
|
||||
config.set('test.enable', false);
|
||||
expect(config.get('test.enable')).to.be(false);
|
||||
});
|
||||
|
||||
it('should use an object to set config values', function () {
|
||||
var hosts = ['host-01', 'host-02'];
|
||||
config.set({ test: { enable: false, hosts: hosts } });
|
||||
expect(config.get('test.enable')).to.be(false);
|
||||
expect(config.get('test.hosts')).to.eql(hosts);
|
||||
});
|
||||
|
||||
it('should use a flatten object to set config values', function () {
|
||||
var hosts = ['host-01', 'host-02'];
|
||||
config.set({ 'test.enable': false, 'test.hosts': hosts });
|
||||
expect(config.get('test.enable')).to.be(false);
|
||||
expect(config.get('test.hosts')).to.eql(hosts);
|
||||
});
|
||||
|
||||
it('should override values with just the values present', function () {
|
||||
var newData = _.cloneDeep(data);
|
||||
config.set(data);
|
||||
newData.test.enable = false;
|
||||
config.set({ test: { enable: false } });
|
||||
expect(config.get()).to.eql(newData);
|
||||
});
|
||||
|
||||
it('should thow an exception when setting a value with the wrong type', function (done) {
|
||||
var run = function () {
|
||||
config.set('test.enable', 'something');
|
||||
};
|
||||
expect(run).to.throwException(function (err) {
|
||||
expect(err).to.have.property('name', 'ValidationError');
|
||||
expect(err.details[0].message).to.be('"enable" must be a boolean');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
describe('#get(key)', function () {
|
||||
|
||||
var config;
|
||||
|
||||
beforeEach(function () {
|
||||
config = new Config(schema);
|
||||
config.set(data);
|
||||
});
|
||||
|
||||
it('should return the whole config object when called without a key', function () {
|
||||
var newData = _.cloneDeep(data);
|
||||
newData.test.enable = true;
|
||||
expect(config.get()).to.eql(newData);
|
||||
});
|
||||
|
||||
it('should return the value using dot notation', function () {
|
||||
expect(config.get('test.enable')).to.be(true);
|
||||
});
|
||||
|
||||
it('should return the clone of partial object using dot notation', function () {
|
||||
expect(config.get('test.client')).to.not.be(data.test.client);
|
||||
expect(config.get('test.client')).to.eql(data.test.client);
|
||||
});
|
||||
|
||||
it('should throw exception for unknown config values', function () {
|
||||
var run = function () {
|
||||
config.get('test.does.not.exist');
|
||||
};
|
||||
expect(run).to.throwException(/Unknown config key: test.does.not.exist/);
|
||||
});
|
||||
|
||||
it('should not throw exception for undefined known config values', function () {
|
||||
var run = function getUndefValue() {
|
||||
config.get('test.undefValue');
|
||||
};
|
||||
expect(run).to.not.throwException();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
34
test/unit/server/lib/config/explode_by.js
Normal file
34
test/unit/server/lib/config/explode_by.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
var root = require('requirefrom')('');
|
||||
var explodeBy = root('src/hapi/lib/config/explode_by');
|
||||
var expect = require('expect.js');
|
||||
var _ = require('lodash');
|
||||
|
||||
describe('explode_by(dot, flatObject)', function () {
|
||||
|
||||
it('should explode a flatten object with dots', function () {
|
||||
var flatObject = {
|
||||
'test.enable': true,
|
||||
'test.hosts': ['host-01', 'host-02']
|
||||
};
|
||||
expect(explodeBy('.', flatObject)).to.eql({
|
||||
test: {
|
||||
enable: true,
|
||||
hosts: ['host-01', 'host-02']
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should explode a flatten object with slashes', function () {
|
||||
var flatObject = {
|
||||
'test/enable': true,
|
||||
'test/hosts': ['host-01', 'host-02']
|
||||
};
|
||||
expect(explodeBy('/', flatObject)).to.eql({
|
||||
test: {
|
||||
enable: true,
|
||||
hosts: ['host-01', 'host-02']
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
29
test/unit/server/lib/config/flatten_with.js
Normal file
29
test/unit/server/lib/config/flatten_with.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
var root = require('requirefrom')('');
|
||||
var flattenWith = root('src/hapi/lib/config/flatten_with');
|
||||
var expect = require('expect.js');
|
||||
var _ = require('lodash');
|
||||
|
||||
describe('flatten_with(dot, nestedObj)', function () {
|
||||
|
||||
it('should flatten object with dot', function () {
|
||||
var nestedObj = {
|
||||
test: {
|
||||
enable: true,
|
||||
hosts: ['host-01', 'host-02'],
|
||||
client: {
|
||||
type: 'nosql',
|
||||
pool: [{ port: 5051 }, { port: 5052 }]
|
||||
}
|
||||
}
|
||||
};
|
||||
expect(flattenWith('.', nestedObj)).to.eql({
|
||||
'test.enable': true,
|
||||
'test.hosts': ['host-01', 'host-02'],
|
||||
'test.client.type': 'nosql',
|
||||
'test.client.pool': [{ port: 5051 }, { port: 5052 }]
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
15
test/unit/server/lib/config/index.js
Normal file
15
test/unit/server/lib/config/index.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
var root = require('requirefrom')('');
|
||||
var config = root('src/hapi/lib/config');
|
||||
var Config = root('src/hapi/lib/config/config');
|
||||
var expect = require('expect.js');
|
||||
var _ = require('lodash');
|
||||
|
||||
describe('server.config()', function (arg) {
|
||||
|
||||
it('should return a Config object', function () {
|
||||
var conf = config();
|
||||
expect(conf).to.be.an(Config);
|
||||
});
|
||||
|
||||
});
|
||||
|
30
test/unit/server/lib/config/override.js
Normal file
30
test/unit/server/lib/config/override.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
var root = require('requirefrom')('');
|
||||
var override = root('src/hapi/lib/config/override');
|
||||
var expect = require('expect.js');
|
||||
var _ = require('lodash');
|
||||
|
||||
describe('override(target, source)', function () {
|
||||
|
||||
it('should override the values form source to target', function () {
|
||||
var target = {
|
||||
test: {
|
||||
enable: true,
|
||||
host: ['host-01', 'host-02'],
|
||||
client: {
|
||||
type: 'sql'
|
||||
}
|
||||
}
|
||||
};
|
||||
var source = { test: { client: { type: 'nosql' } } };
|
||||
expect(override(target, source)).to.eql({
|
||||
test: {
|
||||
enable: true,
|
||||
host: ['host-01', 'host-02'],
|
||||
client: {
|
||||
type: 'nosql'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
28
test/unit/server/lib/config/schema.js
Normal file
28
test/unit/server/lib/config/schema.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
var root = require('requirefrom')('');
|
||||
var schema = root('src/hapi/lib/config/schema');
|
||||
var expect = require('expect.js');
|
||||
var _ = require('lodash');
|
||||
var Joi = require('joi');
|
||||
var package = root('./package.json');
|
||||
var path = require('path');
|
||||
|
||||
describe('lib/config/schema', function () {
|
||||
|
||||
describe('defaults', function () {
|
||||
|
||||
it('should resolve the package.json', function () {
|
||||
var results = Joi.validate({}, schema);
|
||||
expect(results.value.kibana.package).to.eql(package);
|
||||
});
|
||||
|
||||
it('should resolve the publicFolder', function () {
|
||||
var results = Joi.validate({}, schema);
|
||||
var publicFolder = path.resolve(__dirname, '..', '..', '..', '..', '..', 'src', 'kibana');
|
||||
expect(results.value.kibana.publicFolder).to.eql(publicFolder);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
var checkDependencies = require('../../../../src/hapi/lib/check_dependencies');
|
||||
var checkDependencies = require('../../../../../src/hapi/lib/plugins/check_dependencies');
|
||||
var expect = require('expect.js');
|
||||
|
||||
describe('src/server/lib/check_dependencies', function () {
|
28
test/unit/server/lib/plugins/plugin.js
Normal file
28
test/unit/server/lib/plugins/plugin.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
var expect = require('expect.js');
|
||||
var Plugin = require('../../../../../src/hapi/lib/plugins/plugin');
|
||||
|
||||
describe('lib/plugins/plugin', function () {
|
||||
|
||||
it('should assign attributes passed into the created to the object', function () {
|
||||
var plugin = new Plugin({ name: 'test', require: ['config'] });
|
||||
expect(plugin).to.have.property('name', 'test');
|
||||
expect(plugin).to.have.property('require');
|
||||
expect(plugin.require).to.eql(['config']);
|
||||
});
|
||||
|
||||
it('should by default assign an empty array to the require attribute', function () {
|
||||
var plugin = new Plugin();
|
||||
expect(plugin).to.have.property('require');
|
||||
expect(plugin.require).to.eql([]);
|
||||
});
|
||||
|
||||
it('should by default assign a function to init attribute that rejects a promise', function (done) {
|
||||
var plugin = new Plugin();
|
||||
expect(plugin).to.have.property('init');
|
||||
plugin.init().catch(function (err) {
|
||||
expect(err.message).to.be('You must override the init function for plugins');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
176
test/unit/server/lib/plugins/register_plugins.js
Normal file
176
test/unit/server/lib/plugins/register_plugins.js
Normal file
|
@ -0,0 +1,176 @@
|
|||
var _ = require('lodash');
|
||||
var expect = require('expect.js');
|
||||
var sinon = require('sinon');
|
||||
var registerPlugins = require('../../../../../src/hapi/lib/plugins/register_plugins');
|
||||
var Status = require('../../../../../src/hapi/lib/status/status');
|
||||
var systemStatus = require('../../../../../src/hapi/lib/status');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
function createInit() {
|
||||
return sinon.stub().returns(Promise.resolve());
|
||||
}
|
||||
|
||||
describe('server/lib/register_plugins', function () {
|
||||
var server;
|
||||
|
||||
beforeEach(function () {
|
||||
server = {
|
||||
register: sinon.stub(),
|
||||
config: sinon.stub.returns({}),
|
||||
expose: sinon.stub(),
|
||||
log: sinon.stub()
|
||||
};
|
||||
});
|
||||
|
||||
describe('registerPlugins() wrapper', function () {
|
||||
|
||||
var options = { foo: 'bar' };
|
||||
|
||||
it('should pass server, options and next to the init function', function () {
|
||||
var next = function (err) {
|
||||
server.register.args[0][1](err);
|
||||
};
|
||||
server.register.yieldsTo('register', server, options, next);
|
||||
var plugin = { name: 'first', init: createInit() };
|
||||
var plugins = [plugin];
|
||||
return registerPlugins(server, plugins).then(function () {
|
||||
expect(plugin.init.args[0][0]).to.equal(server);
|
||||
expect(plugin.init.args[0][1]).to.equal(options);
|
||||
});
|
||||
});
|
||||
|
||||
it('should call next() when plugin.init completes', function () {
|
||||
var called = false;
|
||||
var next = function (err) {
|
||||
called = true;
|
||||
server.register.args[0][1](err);
|
||||
};
|
||||
server.register.yieldsTo('register', server, options, next);
|
||||
var plugin = { name: 'first', init: createInit() };
|
||||
var plugins = [plugin];
|
||||
return registerPlugins(server, plugins).then(function () {
|
||||
expect(called).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should attach the server to the plugin', function () {
|
||||
var next = function (err) {
|
||||
server.register.args[0][1](err);
|
||||
};
|
||||
server.register.yieldsTo('register', server, options, next);
|
||||
var plugin = { name: 'first', init: createInit() };
|
||||
var plugins = [plugin];
|
||||
return registerPlugins(server, plugins).then(function () {
|
||||
expect(plugin).to.have.property('server');
|
||||
expect(plugin.server).to.eql(server);
|
||||
});
|
||||
});
|
||||
|
||||
var greenSpy, yellowSpy, createStatus;
|
||||
beforeEach(function () {
|
||||
greenSpy = sinon.spy(Status.prototype, 'green');
|
||||
yellowSpy = sinon.spy(Status.prototype, 'yellow');
|
||||
createStatus = sinon.spy(systemStatus, 'createStatus');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Status.prototype.green.restore();
|
||||
Status.prototype.yellow.restore();
|
||||
systemStatus.createStatus.restore();
|
||||
});
|
||||
|
||||
it('should call status.createStatus() with plugin', function () {
|
||||
var next = function (err) {
|
||||
server.register.args[0][1](err);
|
||||
};
|
||||
server.register.yieldsTo('register', server, options, next);
|
||||
var plugin = { name: 'first', init: createInit() };
|
||||
var plugins = [plugin];
|
||||
return registerPlugins(server, plugins).then(function () {
|
||||
sinon.assert.calledOnce(createStatus);
|
||||
expect(plugin).to.have.property('status');
|
||||
expect(createStatus.args[0][0]).to.eql(plugin);
|
||||
});
|
||||
});
|
||||
it('should set the status to yellow and "Initializing" before init is called', function () {
|
||||
var next = function (err) {
|
||||
server.register.args[0][1](err);
|
||||
};
|
||||
server.register.yieldsTo('register', server, options, next);
|
||||
var plugin = { name: 'first', init: createInit() };
|
||||
var plugins = [plugin];
|
||||
return registerPlugins(server, plugins).then(function () {
|
||||
sinon.assert.calledOnce(yellowSpy);
|
||||
expect(plugin.init.calledAfter(yellowSpy)).to.be(true);
|
||||
expect(yellowSpy.args[0][0]).to.be('Initializing');
|
||||
});
|
||||
});
|
||||
|
||||
it('should set the status to green and "Ready" after init', function () {
|
||||
var next = function (err) {
|
||||
server.register.args[0][1](err);
|
||||
};
|
||||
server.register.yieldsTo('register', server, options, next);
|
||||
var plugin = { name: 'first', init: createInit() };
|
||||
var plugins = [plugin];
|
||||
return registerPlugins(server, plugins).then(function () {
|
||||
sinon.assert.calledOnce(greenSpy);
|
||||
expect(greenSpy.calledAfter(plugin.init)).to.be(true);
|
||||
expect(greenSpy.args[0][0]).to.be('Ready');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('dependencies', function () {
|
||||
var nextStub;
|
||||
|
||||
beforeEach(function () {
|
||||
var count = 0;
|
||||
var next = function (err) {
|
||||
server.register.args[count++][1](err);
|
||||
};
|
||||
server.register.yieldsTo('register', server, {}, next);
|
||||
});
|
||||
|
||||
it('should run second after first and third and third after first', function () {
|
||||
var first = { name: 'first', init: createInit() };
|
||||
var second = { name: 'second', require: ['first', 'third'], init: createInit() };
|
||||
var third = { name: 'third', require: ['first'], init: createInit() };
|
||||
var plugins = [second, first, third];
|
||||
return registerPlugins(server, plugins).then(function () {
|
||||
expect(second.init.calledAfter(first.init)).to.be(true);
|
||||
expect(second.init.calledAfter(third.init)).to.be(true);
|
||||
expect(third.init.calledAfter(first.init)).to.be(true);
|
||||
sinon.assert.calledThrice(server.register);
|
||||
});
|
||||
});
|
||||
|
||||
it('should run first, second, third', function () {
|
||||
var first = { name: 'first', init: createInit() };
|
||||
var second = { name: 'second', require: ['first'], init: createInit() };
|
||||
var third = { name: 'third', require: ['second'], init: createInit() };
|
||||
var plugins = [second, first, third];
|
||||
return registerPlugins(server, plugins).then(function () {
|
||||
sinon.assert.calledOnce(first.init);
|
||||
expect(second.init.calledAfter(first.init)).to.be(true);
|
||||
expect(third.init.calledAfter(second.init)).to.be(true);
|
||||
sinon.assert.calledThrice(server.register);
|
||||
});
|
||||
});
|
||||
|
||||
it('should detect circular dependencies', function (done) {
|
||||
var first = { name: 'first', require: ['third'], init: sinon.stub() };
|
||||
var second = { name: 'second', require: ['first'], init: sinon.stub() };
|
||||
var third = { name: 'third', require: ['second'], init: sinon.stub() };
|
||||
var plugins = [second, first, third];
|
||||
registerPlugins(server, plugins).catch(function (err) {
|
||||
expect(err).to.be.a(Error);
|
||||
expect(err.message).to.be('Circular dependency: second -> first -> third -> second');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
}); // end dependencies tests
|
||||
|
||||
});
|
|
@ -1,96 +0,0 @@
|
|||
var expect = require('expect.js');
|
||||
var sinon = require('sinon');
|
||||
var registerPlugins = require('../../../../src/hapi/lib/register_plugins');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
describe('server/lib/register_plugins', function () {
|
||||
|
||||
describe('registerPlugins() wrapper', function () {
|
||||
|
||||
it('should pass server, options and next to the init function', function () {
|
||||
var options = { foo: 'bar' };
|
||||
var server = { register: sinon.stub() };
|
||||
var next = function (err) {
|
||||
server.register.args[0][1](err);
|
||||
};
|
||||
server.register.yieldsTo('register', [server, options, next]);
|
||||
var plugin = { name: 'first', init: sinon.stub().yields() };
|
||||
var plugins = [plugin];
|
||||
return registerPlugins(server, plugins).then(function () {
|
||||
expect(plugin.init.args[0][0]).to.equal(server);
|
||||
expect(plugin.init.args[0][1]).to.equal(options);
|
||||
expect(plugin.init.args[0][2]).to.equal(next);
|
||||
});
|
||||
});
|
||||
|
||||
it('should call next() when plugin.init completes', function () {
|
||||
var called = false;
|
||||
var options = { foo: 'bar' };
|
||||
var server = { register: sinon.stub() };
|
||||
var next = function (err) {
|
||||
called = true;
|
||||
server.register.args[0][1](err);
|
||||
};
|
||||
server.register.yieldsTo('register', [server, options, next]);
|
||||
var plugin = { name: 'first', init: sinon.stub().yields() };
|
||||
var plugins = [plugin];
|
||||
return registerPlugins(server, plugins).then(function () {
|
||||
expect(called).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('dependencies', function () {
|
||||
var server, nextStub;
|
||||
|
||||
beforeEach(function () {
|
||||
server = { register: sinon.stub() };
|
||||
var count = 0;
|
||||
var next = function (err) {
|
||||
server.register.args[count++][1](err);
|
||||
};
|
||||
server.register.yieldsTo('register', [server, {}, next]);
|
||||
});
|
||||
|
||||
it('should run second after first and third and third after first', function () {
|
||||
var first = { name: 'first', init: sinon.stub().yields() };
|
||||
var second = { name: 'second', require: ['first', 'third'], init: sinon.stub().yields() };
|
||||
var third = { name: 'third', require: ['first'], init: sinon.stub().yields() };
|
||||
var plugins = [second, first, third];
|
||||
return registerPlugins(server, plugins).then(function () {
|
||||
expect(second.init.calledAfter(first.init)).to.be(true);
|
||||
expect(second.init.calledAfter(third.init)).to.be(true);
|
||||
expect(third.init.calledAfter(first.init)).to.be(true);
|
||||
sinon.assert.calledThrice(server.register);
|
||||
});
|
||||
});
|
||||
|
||||
it('should run first, second, third', function () {
|
||||
var first = { name: 'first', init: sinon.stub().yields() };
|
||||
var second = { name: 'second', require: ['first'], init: sinon.stub().yields() };
|
||||
var third = { name: 'third', require: ['second'], init: sinon.stub().yields() };
|
||||
var plugins = [second, first, third];
|
||||
return registerPlugins(server, plugins).then(function () {
|
||||
sinon.assert.calledOnce(first.init);
|
||||
expect(second.init.calledAfter(first.init)).to.be(true);
|
||||
expect(third.init.calledAfter(second.init)).to.be(true);
|
||||
sinon.assert.calledThrice(server.register);
|
||||
});
|
||||
});
|
||||
|
||||
it('should detect circular dependencies', function (done) {
|
||||
var first = { name: 'first', require: ['third'], init: sinon.stub() };
|
||||
var second = { name: 'second', require: ['first'], init: sinon.stub() };
|
||||
var third = { name: 'third', require: ['second'], init: sinon.stub() };
|
||||
var plugins = [second, first, third];
|
||||
registerPlugins(server, plugins).catch(function (err) {
|
||||
expect(err).to.be.a(Error);
|
||||
expect(err.message).to.be('Circular dependency: second -> first -> third -> second');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
}); // end dependencies tests
|
||||
|
||||
});
|
45
test/unit/server/lib/status/index.js
Normal file
45
test/unit/server/lib/status/index.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
var expect = require('expect.js');
|
||||
var sinon = require('sinon');
|
||||
var status = require('../../../../../src/hapi/lib/status');
|
||||
var Status = require('../../../../../src/hapi/lib/status/status');
|
||||
|
||||
describe('lib/status/index.js', function () {
|
||||
|
||||
var plugin, yellowSpy;
|
||||
beforeEach(function () {
|
||||
plugin = {
|
||||
name: 'test',
|
||||
server: { expose: sinon.stub(), log: sinon.stub() }
|
||||
};
|
||||
yellowSpy = sinon.spy(Status.prototype, 'yellow');
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
Status.prototype.yellow.restore();
|
||||
});
|
||||
|
||||
it('should create a new status for a plugin', function () {
|
||||
status.createStatus(plugin);
|
||||
expect(status.data).to.have.property('test');
|
||||
expect(status.data.test).to.eql(plugin.status);
|
||||
});
|
||||
|
||||
it('should attach a logger to the change status', function () {
|
||||
status.createStatus(plugin);
|
||||
sinon.assert.calledOnce(plugin.server.log);
|
||||
});
|
||||
|
||||
it('should call the yellow status method with "Initializing"', function () {
|
||||
status.createStatus(plugin);
|
||||
sinon.assert.calledOnce(yellowSpy);
|
||||
expect(yellowSpy.args[0][0]).to.be('Initializing');
|
||||
});
|
||||
|
||||
it('should serialize the statuses when toJSON is called', function () {
|
||||
status.createStatus(plugin);
|
||||
expect(JSON.stringify(status)).to.eql(JSON.stringify({
|
||||
test: { state: 'yellow', message: 'Initializing' }
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
29
test/unit/server/lib/status/log_status_change.js
Normal file
29
test/unit/server/lib/status/log_status_change.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
var expect = require('expect.js');
|
||||
var sinon = require('sinon');
|
||||
var logStatusChange = require('../../../../../src/hapi/lib/status/log_status_change');
|
||||
|
||||
describe('lib/status/log_status_change', function () {
|
||||
|
||||
var plugin;
|
||||
var current = { state: 'yellow', message: 'Initialize' };
|
||||
var previous = { state: 'red', message: '' };
|
||||
|
||||
beforeEach(function () {
|
||||
plugin = { name: 'test', server: { log: sinon.stub() } };
|
||||
});
|
||||
|
||||
it('should call plugin.server.log', function () {
|
||||
var fn = logStatusChange(plugin);
|
||||
fn(current, previous);
|
||||
sinon.assert.calledOnce(plugin.server.log);
|
||||
});
|
||||
|
||||
it('should call plugin.server.log with plugin and error message', function () {
|
||||
var fn = logStatusChange(plugin);
|
||||
fn(current, previous);
|
||||
sinon.assert.calledOnce(plugin.server.log);
|
||||
expect(plugin.server.log.args[0][0]).to.be('plugin');
|
||||
expect(plugin.server.log.args[0][1]).to.be('[ test ] Change status from red to yellow - Initialize');
|
||||
});
|
||||
|
||||
});
|
64
test/unit/server/lib/status/status.js
Normal file
64
test/unit/server/lib/status/status.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
var expect = require('expect.js');
|
||||
var sinon = require('sinon');
|
||||
var Status = require('../../../../../src/hapi/lib/status/status');
|
||||
|
||||
describe('lib/status/status.js', function () {
|
||||
|
||||
it('should have a red state when initialized', function () {
|
||||
var obj = new Status('test');
|
||||
expect(obj).to.have.property('state', 'red');
|
||||
});
|
||||
|
||||
it('should only trigger the change listner when something changes', function () {
|
||||
var obj = new Status('test');
|
||||
var stub = sinon.stub();
|
||||
obj.on('change', stub);
|
||||
obj.green('Ready');
|
||||
obj.green('Ready');
|
||||
obj.red('Not Ready');
|
||||
sinon.assert.calledTwice(stub);
|
||||
});
|
||||
|
||||
it('should create a JSON representation of the status', function () {
|
||||
var obj = new Status('test');
|
||||
obj.green('Ready');
|
||||
expect(obj.toJSON()).to.eql({ state: 'green', message: 'Ready' });
|
||||
});
|
||||
|
||||
function testState(color) {
|
||||
it('should change the state to ' + color + ' when #' + color + '() is called', function () {
|
||||
var obj = new Status('test');
|
||||
var message = 'testing ' + color;
|
||||
obj[color](message);
|
||||
expect(obj).to.have.property('state', color);
|
||||
expect(obj).to.have.property('message', message);
|
||||
});
|
||||
|
||||
it('should trigger the "change" listner when #' + color + '() is called', function (done) {
|
||||
var obj = new Status('test');
|
||||
var message = 'testing ' + color;
|
||||
obj.on('change', function (current, previous) {
|
||||
expect(current).to.eql({ state: color, message: message });
|
||||
expect(previous).to.eql({ state: 'red', message: '' });
|
||||
done();
|
||||
});
|
||||
obj[color](message);
|
||||
});
|
||||
|
||||
it('should trigger the "' + color + '" listner when #' + color + '() is called', function (done) {
|
||||
var obj = new Status('test');
|
||||
var message = 'testing ' + color;
|
||||
obj.on(color, function (msg, prev) {
|
||||
expect(msg).to.be(message);
|
||||
expect(prev).to.eql({ state: 'red', message: '' });
|
||||
done();
|
||||
});
|
||||
obj[color](message);
|
||||
});
|
||||
}
|
||||
|
||||
testState('green');
|
||||
testState('yellow');
|
||||
testState('red');
|
||||
|
||||
});
|
|
@ -1,8 +1,9 @@
|
|||
var _ = require('lodash');
|
||||
var root = require('requirefrom')('');
|
||||
var validateRequest = root('src/server/lib/validateRequest');
|
||||
var validateRequest = root('src/hapi/plugins/elasticsearch/lib/validate');
|
||||
var expect = require('expect.js');
|
||||
var config = root('src/server/config');
|
||||
var server = { config: root('src/hapi/lib/config') };
|
||||
var config = server.config();
|
||||
|
||||
describe('lib/isValid', function () {
|
||||
|
||||
|
@ -29,10 +30,10 @@ describe('lib/isValid', function () {
|
|||
|
||||
var pass = false;
|
||||
try {
|
||||
validateRequest({
|
||||
method: method,
|
||||
url: path,
|
||||
rawBody: body
|
||||
validateRequest(server, {
|
||||
method: method.toLowerCase(),
|
||||
path: path,
|
||||
payload: body
|
||||
});
|
||||
pass = true;
|
||||
} catch (e) {}
|
||||
|
@ -52,11 +53,11 @@ describe('lib/isValid', function () {
|
|||
|
||||
describe('index management', function () {
|
||||
it('allows creating kibana index', function () {
|
||||
send('/' + config.kibana.kibana_index, true);
|
||||
send('/' + config.get('kibana.index'), true);
|
||||
});
|
||||
|
||||
it('allows deleting the kibana index', function () {
|
||||
del('/' + config.kibana.kibana_index, true);
|
||||
del('/' + config.get('kibana.index'), true);
|
||||
});
|
||||
|
||||
it('blocks creating a non-kibana index', function () {
|
||||
|
@ -70,24 +71,24 @@ describe('lib/isValid', function () {
|
|||
|
||||
describe('doc management', function () {
|
||||
it('allows indexing to the kibana index', function () {
|
||||
send('/' + config.kibana.kibana_index, true);
|
||||
send('/' + config.kibana.kibana_index + '/index-patterns', true);
|
||||
send('/' + config.kibana.kibana_index + '/index-patterns/pattern-id', true);
|
||||
send('/' + config.get('kibana.index'), true);
|
||||
send('/' + config.get('kibana.index') + '/index-patterns', true);
|
||||
send('/' + config.get('kibana.index') + '/index-patterns/pattern-id', true);
|
||||
});
|
||||
|
||||
it('allows deleting kibana documents', function () {
|
||||
del('/' + config.kibana.kibana_index + '/index-patterns', true);
|
||||
del('/' + config.kibana.kibana_index + '/index-patterns/pattern-id', true);
|
||||
del('/' + config.get('kibana.index') + '/index-patterns', true);
|
||||
del('/' + config.get('kibana.index') + '/index-patterns/pattern-id', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('allows any destructive non-bulk requests against kibana index', function () {
|
||||
it('refresh', function () {
|
||||
send('/' + config.kibana.kibana_index + '/_refresh', true);
|
||||
send('/' + config.get('kibana.index') + '/_refresh', true);
|
||||
});
|
||||
|
||||
it('delete', function () {
|
||||
del('/' + config.kibana.kibana_index + '/pasta/lasagna', true);
|
||||
del('/' + config.get('kibana.index') + '/pasta/lasagna', true);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -99,10 +100,10 @@ describe('lib/isValid', function () {
|
|||
run('GET', '/_aliases', true);
|
||||
run('GET', '/_nodes/', true);
|
||||
run('HEAD', '/', true);
|
||||
run('HEAD', '/' + config.kibana.kibana_index, true);
|
||||
run('HEAD', '/' + config.get('kibana.index'), true);
|
||||
run('HEAD', '/other-index', true);
|
||||
run('GET', '/_cluster/health', true);
|
||||
run('POST', '/' + config.kibana.kibana_index + '/__notRealIndex__/_validate/query?q=foo:bar', true);
|
||||
run('POST', '/' + config.get('kibana.index') + '/__notRealIndex__/_validate/query?q=foo:bar', true);
|
||||
run('POST', '/_validate', true);
|
||||
run('POST', '/_search', true);
|
||||
});
|
||||
|
@ -111,26 +112,26 @@ describe('lib/isValid', function () {
|
|||
describe('bulk indexing', function () {
|
||||
it('valid', function () {
|
||||
send('/_bulk', [
|
||||
{ create: { _index: config.kibana.kibana_index, _type: 'index-pattern' } },
|
||||
{ create: { _index: config.get('kibana.index'), _type: 'index-pattern' } },
|
||||
{ fields: [] },
|
||||
{ create: { _index: config.kibana.kibana_index, _type: 'vis' } },
|
||||
{ create: { _index: config.get('kibana.index'), _type: 'vis' } },
|
||||
{ aggs: [] }
|
||||
], true);
|
||||
|
||||
send('/' + config.kibana.kibana_index + '/_bulk', [
|
||||
send('/' + config.get('kibana.index') + '/_bulk', [
|
||||
// implicit index
|
||||
{ create: { _type: 'index-pattern' } },
|
||||
{ fields: [] },
|
||||
|
||||
// explicit index
|
||||
{ create: { _index: config.kibana.kibana_index, _type: 'vis' } },
|
||||
{ create: { _index: config.get('kibana.index'), _type: 'vis' } },
|
||||
{ aggs: [] }
|
||||
|
||||
], true);
|
||||
});
|
||||
|
||||
it('rejects bulks including even one other index', function () {
|
||||
send('/' + config.kibana.kibana_index + '/_bulk', [
|
||||
send('/' + config.get('kibana.index') + '/_bulk', [
|
||||
// implicit index
|
||||
{ create: { _type: 'index-pattern' } },
|
||||
{ fields: [] },
|
||||
|
|
101
test/unit/server/plugins/elasticsearch/routes.js
Normal file
101
test/unit/server/plugins/elasticsearch/routes.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
var root = require('requirefrom')('');
|
||||
var expect = require('expect.js');
|
||||
var kibana = root('src/hapi');
|
||||
var findPort = root('test/utils/find_port');
|
||||
root('test/utils/ensure_elasticsearch');
|
||||
var util = require('util');
|
||||
var format = util.format;
|
||||
|
||||
|
||||
describe('elasticsearch plugin', function () {
|
||||
var server, config;
|
||||
|
||||
beforeEach(function () {
|
||||
return findPort(7000, 8000).then(function (port) {
|
||||
config = { 'kibana.server.port': port, 'logging.quiet': true };
|
||||
return kibana.start(config).then(function (_server) {
|
||||
server = _server;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
server.stop(done);
|
||||
});
|
||||
|
||||
function testRoute(options) {
|
||||
var statusCode = options.statusCode || 200;
|
||||
describe(format('%s %s', options.method, options.url), function () {
|
||||
it('should should return ' + statusCode, function (done) {
|
||||
server.inject(options, function (res) {
|
||||
expect(res.statusCode).to.be(statusCode);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
testRoute({
|
||||
method: 'GET',
|
||||
url: '/elasticsearch/_nodes'
|
||||
});
|
||||
|
||||
testRoute({
|
||||
method: 'GET',
|
||||
url: '/elasticsearch/'
|
||||
});
|
||||
|
||||
testRoute({
|
||||
method: 'GET',
|
||||
url: '/elasticsearch/.kibana'
|
||||
});
|
||||
|
||||
testRoute({
|
||||
method: 'POST',
|
||||
url: '/elasticsearch/.kibana',
|
||||
payload: '{settings: {number_of_shards: 1, number_of_replicas: 1}}',
|
||||
statusCode: 201
|
||||
});
|
||||
|
||||
testRoute({
|
||||
method: 'POST',
|
||||
url: '/elasticsearch/.kibana/_bulk',
|
||||
payload: '{}',
|
||||
statusCode: 400
|
||||
});
|
||||
|
||||
testRoute({
|
||||
method: 'GET',
|
||||
url: '/elasticsearch/.kibana/_mapping/*/field/_source'
|
||||
});
|
||||
|
||||
testRoute({
|
||||
method: 'POST',
|
||||
url: '/elasticsearch/.kibana/index-pattern/_search?fields=',
|
||||
payload: '{query: {match_all: {}}, size: 2147483647}'
|
||||
});
|
||||
|
||||
testRoute({
|
||||
method: 'POST',
|
||||
url: '/elasticsearch/.kibana/__kibanaQueryValidator/_validate/query?explain=true&ignore_unavailable=true',
|
||||
payload: '{query: {query_string: {analyze_wildcard: true, query: "*"}}}'
|
||||
});
|
||||
|
||||
testRoute({
|
||||
method: 'POST',
|
||||
url: '/elasticsearch/_mget?timeout=0&ignore_unavailable=true&preference=1429574531063',
|
||||
payload: '{docs: [{_index: ".kibana", _type: "index-pattern", _id: "[logstash-]YYYY.MM.DD"}]}'
|
||||
});
|
||||
|
||||
/* jscs:disable maximumLineLength */
|
||||
testRoute({
|
||||
method: 'POST',
|
||||
url: '/elasticsearch/_msearch?timeout=0&ignore_unavailable=true&preference=1429577952339',
|
||||
payload: '{"index":"logstash-2015.04.21","ignore_unavailable":true}\n{"size":500,"sort":{"@timestamp":"desc"},"query":{"filtered":{"query":{"query_string":{"analyze_wildcard":true,"query":"*"}},"filter":{"bool":{"must":[{"range":{"@timestamp":{"gte":1429577068175,"lte":1429577968175}}}],"must_not":[]}}}},"highlight":{"pre_tags":["@kibana-highlighted-field@"],"post_tags":["@/kibana-highlighted-field@"],"fields":{"*":{}}},"aggs":{"2":{"date_histogram":{"field":"@timestamp","interval":"30s","pre_zone":"-07:00","pre_zone_adjust_large_interval":true,"min_doc_count":0,"extended_bounds":{"min":1429577068175,"max":1429577968175}}}},"fields":["*","_source"],"script_fields":{},"fielddata_fields":["timestamp_offset","@timestamp","utc_time"]}\n'
|
||||
});
|
||||
/* jscs:enable maximumLineLength */
|
||||
|
||||
});
|
||||
|
||||
|
41
test/utils/ensure_elasticsearch.js
Normal file
41
test/utils/ensure_elasticsearch.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
var portscanner = require('portscanner');
|
||||
var path = require('path');
|
||||
var Promise = require('bluebird');
|
||||
var libesvm = require('libesvm');
|
||||
|
||||
function startEs() {
|
||||
var options = {
|
||||
version: '1.4.4',
|
||||
directory: path.join(__dirname, '..', '..', '..', '..', '..', '..', 'esvm'),
|
||||
config: {
|
||||
'cluster.name': 'test',
|
||||
'network.host': '127.0.0.1'
|
||||
}
|
||||
};
|
||||
var cluster = libesvm.createCluster(options);
|
||||
return cluster.install().then(function () {
|
||||
return cluster.start();
|
||||
}).then(function () {
|
||||
after(function () {
|
||||
this.timeout(120000);
|
||||
return cluster.shutdown();
|
||||
});
|
||||
return cluster;
|
||||
});
|
||||
}
|
||||
|
||||
function maybeStartES() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
portscanner.checkPortStatus(9200, '127.0.0.1', function (err, status) {
|
||||
if (err) return reject(err);
|
||||
if (status === 'closed') return startEs().then(resolve);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
before(function () {
|
||||
this.timeout(120000);
|
||||
return maybeStartES();
|
||||
});
|
||||
|
12
test/utils/find_port.js
Normal file
12
test/utils/find_port.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
var Promise = require('bluebird');
|
||||
var portscanner = require('portscanner');
|
||||
module.exports = function findPort(start, end, host) {
|
||||
host = host || 'localhost';
|
||||
return new Promise(function (resolve, reject) {
|
||||
portscanner.findAPortNotInUse(start, end, host, function (err, port) {
|
||||
if (err) return reject(err);
|
||||
resolve(port);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue