-
Notifications
You must be signed in to change notification settings - Fork 8k
/
plugin.js
169 lines (146 loc) · 6.05 KB
/
plugin.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import _ from 'lodash';
import Joi from 'joi';
import Bluebird, { attempt, fromNode } from 'bluebird';
import { basename, resolve } from 'path';
import { inherits } from 'util';
const extendInitFns = Symbol('extend plugin initialization');
const defaultConfigSchema = Joi.object({
enabled: Joi.boolean().default(true)
}).default();
/**
* The server plugin class, used to extend the server
* and add custom behavior. A "scoped" plugin class is
* created by the PluginApi class and provided to plugin
* providers that automatically binds all but the `opts`
* arguments.
*
* @class Plugin
* @param {KbnServer} kbnServer - the KbnServer this plugin
* belongs to.
* @param {String} path - the path from which the plugin hails
* @param {Object} pkg - the value of package.json for the plugin
* @param {Objects} opts - the options for this plugin
* @param {String} [opts.id=pkg.name] - the id for this plugin.
* @param {Object} [opts.uiExports] - a mapping of UiExport types
* to UI modules or metadata about
* the UI module
* @param {Array} [opts.require] - the other plugins that this plugin
* requires. These plugins must exist and
* be enabled for this plugin to function.
* The require'd plugins will also be
* initialized first, in order to make sure
* that dependencies provided by these plugins
* are available
* @param {String} [opts.version=pkg.version] - the version of this plugin
* @param {Function} [opts.init] - A function that will be called to initialize
* this plugin at the appropriate time.
* @param {Function} [opts.configPrefix=this.id] - The prefix to use for configuration
* values in the main configuration service
* @param {Function} [opts.config] - A function that produces a configuration
* schema using Joi, which is passed as its
* first argument.
* @param {String|False} [opts.publicDir=path + '/public']
* - the public directory for this plugin. The final directory must
* have the name "public", though it can be located somewhere besides
* the root of the plugin. Set this to false to disable exposure of a
* public directory
*/
module.exports = class Plugin {
constructor(kbnServer, path, pkg, opts) {
this.kbnServer = kbnServer;
this.pkg = pkg;
this.path = path;
this.id = opts.id || pkg.name;
this.uiExportsSpecs = opts.uiExports || {};
this.requiredIds = opts.require || [];
this.version = opts.version || pkg.version;
// Plugins must specify their version, and by default that version should match
// the version of kibana down to the patch level. If these two versions need
// to diverge, they can specify a kibana.version in the package to indicate the
// version of kibana the plugin is intended to work with.
this.kibanaVersion = opts.kibanaVersion || _.get(pkg, 'kibana.version', this.version);
this.externalPreInit = opts.preInit || _.noop;
this.externalInit = opts.init || _.noop;
this.configPrefix = opts.configPrefix || this.id;
this.getExternalConfigSchema = opts.config || _.noop;
this.preInit = _.once(this.preInit);
this.init = _.once(this.init);
this[extendInitFns] = [];
if (opts.publicDir === false) {
this.publicDir = null;
}
else if (!opts.publicDir) {
this.publicDir = resolve(this.path, 'public');
}
else {
this.publicDir = opts.publicDir;
if (basename(this.publicDir) !== 'public') {
throw new Error(`publicDir for plugin ${this.id} must end with a "public" directory.`);
}
}
}
static scoped(kbnServer, path, pkg) {
return class ScopedPlugin extends Plugin {
constructor(opts) {
super(kbnServer, path, pkg, opts || {});
}
};
}
async getConfigSchema() {
let schema = await this.getExternalConfigSchema(Joi);
return schema || defaultConfigSchema;
}
async preInit() {
return await this.externalPreInit(this.kbnServer.server);
}
async init() {
let { id, version, kbnServer, configPrefix } = this;
let { config } = kbnServer;
// setup the hapi register function and get on with it
const asyncRegister = async (server, options) => {
this.server = server;
await Promise.all(this[extendInitFns].map(async fn => {
await fn.call(this, server, options);
}));
server.log(['plugins', 'debug'], {
tmpl: 'Initializing plugin <%= plugin.toString() %>',
plugin: this
});
if (this.publicDir) {
server.exposeStaticDir(`/plugins/${id}/{path*}`, this.publicDir);
}
// Many of the plugins are simply adding static assets to the server and we don't need
// to track their "status". Since plugins must have an init() function to even set its status
// we shouldn't even create a status unless the plugin can use it.
if (this.externalInit !== _.noop) {
this.status = kbnServer.status.createForPlugin(this);
server.expose('status', this.status);
}
return await attempt(this.externalInit, [server, options], this);
};
const register = (server, options, next) => {
Bluebird.resolve(asyncRegister(server, options)).nodeify(next);
};
register.attributes = { name: id, version: version };
await fromNode(cb => {
kbnServer.server.register({
register: register,
options: config.has(configPrefix) ? config.get(configPrefix) : null
}, cb);
});
// Only change the plugin status to green if the
// intial status has not been changed
if (this.status && this.status.state === 'uninitialized') {
this.status.green('Ready');
}
}
extendInit(fn) {
this[extendInitFns].push(fn);
}
toJSON() {
return this.pkg;
}
toString() {
return `${this.id}@${this.version}`;
}
};