mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
* Initial conversion to asciidoc * Update and split migration guide * Convert MIGRATION_EXAMPLES to asciidoc * build with --focus flag * convert migration guide to asciidoc * cleanup migration_examples * fix wrong Heading size * update links in docs * Apply suggestions from code review Co-authored-by: Rudolf Meijering <skaapgif@gmail.com> * Apply suggestions from code review Co-authored-by: Rudolf Meijering <skaapgif@gmail.com> * add tooling section * explain purpose of each lifecycle method * cleanup docs * cleanup p2 * fix wrong link * resturcture core docs * fix wrong link * update missing links * Apply suggestions from code review Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * address comments * add a commenta about plugin-helpers preconfigured * improve density of tables * fix lik * remove links to the migration guide * address comments * Apply suggestions from code review Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * address @gchaps comments * Apply suggestions from code review Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> * change format of ES client change list Co-authored-by: Josh Dover <me@joshdover.com> Co-authored-by: Rudolf Meijering <skaapgif@gmail.com> Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com>
451 lines
15 KiB
Text
451 lines
15 KiB
Text
[[kibana-platform-api]]
|
||
== {kib} Core API
|
||
|
||
experimental[]
|
||
|
||
{kib} Core provides a set of low-level API's required to run all {kib} plugins.
|
||
These API's are injected into your plugin's lifecycle methods and may be invoked during that lifecycle only:
|
||
|
||
[source,typescript]
|
||
----
|
||
import type { PluginInitializerContext, CoreSetup, CoreStart } from 'kibana/server';
|
||
|
||
export class MyPlugin {
|
||
constructor(initializerContext: PluginInitializerContext) {}
|
||
|
||
public setup(core: CoreSetup) {
|
||
// called when plugin is setting up during Kibana's startup sequence
|
||
}
|
||
|
||
public start(core: CoreStart) {
|
||
// called after all plugins are set up
|
||
}
|
||
|
||
public stop() {
|
||
// called when plugin is torn down during Kibana's shutdown sequence
|
||
}
|
||
}
|
||
----
|
||
|
||
=== Server-side
|
||
[[configuration-service]]
|
||
==== Configuration service
|
||
{kib} provides `ConfigService` if a plugin developer may want to support
|
||
adjustable runtime behavior for their plugins.
|
||
Plugins can only read their own configuration values, it is not possible to access the configuration values from {kib} Core or other plugins directly.
|
||
|
||
[source,js]
|
||
----
|
||
// in Legacy platform
|
||
const basePath = config.get('server.basePath');
|
||
// in Kibana Platform 'basePath' belongs to the http service
|
||
const basePath = core.http.basePath.get(request);
|
||
----
|
||
|
||
To have access to your plugin config, you _should_:
|
||
|
||
* Declare plugin-specific `configPath` (will fallback to plugin `id`
|
||
if not specified) in {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md[`kibana.json`] manifest file.
|
||
* Export schema validation for the config from plugin's main file. Schema is
|
||
mandatory. If a plugin reads from the config without schema declaration,
|
||
`ConfigService` will throw an error.
|
||
|
||
*my_plugin/server/index.ts*
|
||
[source,typescript]
|
||
----
|
||
import { schema, TypeOf } from '@kbn/config-schema';
|
||
export const plugin = …
|
||
export const config = {
|
||
schema: schema.object(…),
|
||
};
|
||
export type MyPluginConfigType = TypeOf<typeof config.schema>;
|
||
----
|
||
|
||
* Read config value exposed via `PluginInitializerContext`.
|
||
*my_plugin/server/index.ts*
|
||
[source,typescript]
|
||
----
|
||
import type { PluginInitializerContext } from 'kibana/server';
|
||
export class MyPlugin {
|
||
constructor(initializerContext: PluginInitializerContext) {
|
||
this.config$ = initializerContext.config.create<MyPluginConfigType>();
|
||
// or if config is optional:
|
||
this.config$ = initializerContext.config.createIfExists<MyPluginConfigType>();
|
||
}
|
||
----
|
||
|
||
If your plugin also has a client-side part, you can also expose
|
||
configuration properties to it using the configuration `exposeToBrowser`
|
||
allow-list property.
|
||
|
||
*my_plugin/server/index.ts*
|
||
[source,typescript]
|
||
----
|
||
import { schema, TypeOf } from '@kbn/config-schema';
|
||
import type { PluginConfigDescriptor } from 'kibana/server';
|
||
|
||
const configSchema = schema.object({
|
||
secret: schema.string({ defaultValue: 'Only on server' }),
|
||
uiProp: schema.string({ defaultValue: 'Accessible from client' }),
|
||
});
|
||
|
||
type ConfigType = TypeOf<typeof configSchema>;
|
||
|
||
export const config: PluginConfigDescriptor<ConfigType> = {
|
||
exposeToBrowser: {
|
||
uiProp: true,
|
||
},
|
||
schema: configSchema,
|
||
};
|
||
----
|
||
|
||
Configuration containing only the exposed properties will be then
|
||
available on the client-side using the plugin's `initializerContext`:
|
||
|
||
*my_plugin/public/index.ts*
|
||
[source,typescript]
|
||
----
|
||
interface ClientConfigType {
|
||
uiProp: string;
|
||
}
|
||
|
||
export class MyPlugin implements Plugin<PluginSetup, PluginStart> {
|
||
constructor(private readonly initializerContext: PluginInitializerContext) {}
|
||
|
||
public async setup(core: CoreSetup, deps: {}) {
|
||
const config = this.initializerContext.config.get<ClientConfigType>();
|
||
}
|
||
----
|
||
|
||
All plugins are considered enabled by default. If you want to disable
|
||
your plugin, you could declare the `enabled` flag in the plugin
|
||
config. This is a special {kib} Platform key. {kib} reads its
|
||
value and won’t create a plugin instance if `enabled: false`.
|
||
|
||
[source,js]
|
||
----
|
||
export const config = {
|
||
schema: schema.object({ enabled: schema.boolean({ defaultValue: false }) }),
|
||
};
|
||
----
|
||
[[handle-plugin-configuration-deprecations]]
|
||
===== Handle plugin configuration deprecations
|
||
If your plugin has deprecated configuration keys, you can describe them using
|
||
the `deprecations` config descriptor field.
|
||
Deprecations are managed on a per-plugin basis, meaning you don’t need to specify
|
||
the whole property path, but use the relative path from your plugin’s
|
||
configuration root.
|
||
|
||
*my_plugin/server/index.ts*
|
||
[source,typescript]
|
||
----
|
||
import { schema, TypeOf } from '@kbn/config-schema';
|
||
import type { PluginConfigDescriptor } from 'kibana/server';
|
||
|
||
const configSchema = schema.object({
|
||
newProperty: schema.string({ defaultValue: 'Some string' }),
|
||
});
|
||
|
||
type ConfigType = TypeOf<typeof configSchema>;
|
||
|
||
export const config: PluginConfigDescriptor<ConfigType> = {
|
||
schema: configSchema,
|
||
deprecations: ({ rename, unused }) => [
|
||
rename('oldProperty', 'newProperty'),
|
||
unused('someUnusedProperty'),
|
||
],
|
||
};
|
||
----
|
||
|
||
In some cases, accessing the whole configuration for deprecations is
|
||
necessary. For these edge cases, `renameFromRoot` and `unusedFromRoot`
|
||
are also accessible when declaring deprecations.
|
||
|
||
*my_plugin/server/index.ts*
|
||
[source,typescript]
|
||
----
|
||
export const config: PluginConfigDescriptor<ConfigType> = {
|
||
schema: configSchema,
|
||
deprecations: ({ renameFromRoot, unusedFromRoot }) => [
|
||
renameFromRoot('oldplugin.property', 'myplugin.property'),
|
||
unusedFromRoot('oldplugin.deprecated'),
|
||
],
|
||
};
|
||
----
|
||
==== Logging service
|
||
Allows a plugin to provide status and diagnostic information.
|
||
For detailed instructions see the {kib-repo}blob/{branch}/src/core/server/logging/README.md[logging service documentation].
|
||
|
||
[source,typescript]
|
||
----
|
||
import type { PluginInitializerContext, CoreSetup, Plugin, Logger } from 'kibana/server';
|
||
|
||
export class MyPlugin implements Plugin {
|
||
private readonly logger: Logger;
|
||
|
||
constructor(initializerContext: PluginInitializerContext) {
|
||
this.logger = initializerContext.logger.get();
|
||
}
|
||
|
||
public setup(core: CoreSetup) {
|
||
try {
|
||
this.logger.debug('doing something...');
|
||
// …
|
||
} catch (e) {
|
||
this.logger.error('failed doing something...');
|
||
}
|
||
}
|
||
}
|
||
----
|
||
|
||
==== Elasticsearch service
|
||
`Elasticsearch service` provides `elasticsearch.client` program API to communicate with Elasticsearch server REST API.
|
||
`elasticsearch.client` interacts with Elasticsearch service on behalf of:
|
||
|
||
- `kibana_system` user via `elasticsearch.client.asInternalUser.*` methods.
|
||
- a current end-user via `elasticsearch.client.asCurrentUser.*` methods. In this case Elasticsearch client should be given the current user credentials.
|
||
See <<scoped-services>> and <<development-security>>.
|
||
|
||
{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md[Elasticsearch service API docs]
|
||
|
||
[source,typescript]
|
||
----
|
||
import { CoreStart, Plugin } from 'kibana/public';
|
||
|
||
export class MyPlugin implements Plugin {
|
||
public start(core: CoreStart) {
|
||
async function asyncTask() {
|
||
const result = await core.elasticsearch.client.asInternalUser.ping(…);
|
||
}
|
||
asyncTask();
|
||
}
|
||
}
|
||
----
|
||
|
||
For advanced use-cases, such as a search, use {kib-repo}blob/{branch}/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md[Data plugin]
|
||
|
||
include::saved-objects-service.asciidoc[leveloffset=+1]
|
||
|
||
==== HTTP service
|
||
Allows plugins:
|
||
|
||
* to extend the {kib} server with custom REST API.
|
||
* to execute custom logic on an incoming request or server response.
|
||
* implement custom authentication and authorization strategy.
|
||
|
||
See {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.md[HTTP service API docs]
|
||
|
||
[source,typescript]
|
||
----
|
||
import { schema } from '@kbn/config-schema';
|
||
import type { CoreSetup, Plugin } from 'kibana/server';
|
||
|
||
export class MyPlugin implements Plugin {
|
||
public setup(core: CoreSetup) {
|
||
const router = core.http.createRouter();
|
||
|
||
const validate = {
|
||
params: schema.object({
|
||
id: schema.string(),
|
||
}),
|
||
};
|
||
|
||
router.get({
|
||
path: 'my_plugin/{id}',
|
||
validate
|
||
},
|
||
async (context, request, response) => {
|
||
const data = await findObject(request.params.id);
|
||
if (!data) return response.notFound();
|
||
return response.ok({
|
||
body: data,
|
||
headers: {
|
||
'content-type': 'application/json'
|
||
}
|
||
});
|
||
});
|
||
}
|
||
}
|
||
----
|
||
|
||
==== UI settings service
|
||
The program interface to <<advanced-options, UI settings>>.
|
||
It makes it possible for Kibana plugins to extend Kibana UI Settings Management with custom settings.
|
||
|
||
See:
|
||
|
||
- {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.uisettingsservicesetup.register.md[UI settings service Setup API docs]
|
||
- {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.uisettingsservicestart.register.md[UI settings service Start API docs]
|
||
|
||
[source,typescript]
|
||
----
|
||
import { schema } from '@kbn/config-schema';
|
||
import type { CoreSetup,Plugin } from 'kibana/server';
|
||
|
||
export class MyPlugin implements Plugin {
|
||
public setup(core: CoreSetup) {
|
||
core.uiSettings.register({
|
||
custom: {
|
||
value: '42',
|
||
schema: schema.string(),
|
||
},
|
||
});
|
||
const router = core.http.createRouter();
|
||
router.get({
|
||
path: 'my_plugin/{id}',
|
||
validate: …,
|
||
},
|
||
async (context, request, response) => {
|
||
const customSetting = await context.uiSettings.client.get('custom');
|
||
…
|
||
});
|
||
}
|
||
}
|
||
|
||
----
|
||
|
||
=== Client-side
|
||
==== Application service
|
||
Kibana has migrated to be a Single Page Application. Plugins should use `Application service` API to instruct Kibana what an application should be loaded & rendered in the UI in response to user interactions.
|
||
[source,typescript]
|
||
----
|
||
import { AppMountParameters, CoreSetup, Plugin, DEFAULT_APP_CATEGORIES } from 'kibana/public';
|
||
|
||
export class MyPlugin implements Plugin {
|
||
public setup(core: CoreSetup) {
|
||
core.application.register({ // <1>
|
||
category: DEFAULT_APP_CATEGORIES.kibana,
|
||
id: 'my-plugin',
|
||
title: 'my plugin title',
|
||
euiIconType: '/path/to/some.svg',
|
||
order: 100,
|
||
appRoute: '/app/my_plugin', // <2>
|
||
async mount(params: AppMountParameters) { // <3>
|
||
// Load application bundle
|
||
const { renderApp } = await import('./application');
|
||
// Get start services
|
||
const [coreStart, depsStart] = await core.getStartServices(); // <4>
|
||
// Render the application
|
||
return renderApp(coreStart, depsStart, params); // <5>
|
||
},
|
||
});
|
||
}
|
||
}
|
||
----
|
||
<1> See {kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.applicationsetup.register.md[application.register interface]
|
||
<2> Application specific URL.
|
||
<3> `mount` callback is invoked when a user navigates to the application-specific URL.
|
||
<4> `core.getStartServices` method provides API available during `start` lifecycle.
|
||
<5> `mount` method must return a function that will be called to unmount the application.
|
||
|
||
NOTE:: you are free to use any UI library to render a plugin application in DOM.
|
||
However, we recommend using React and https://elastic.github.io/eui[EUI] for all your basic UI
|
||
components to create a consistent UI experience.
|
||
|
||
==== HTTP service
|
||
Provides API to communicate with the {kib} server. Feel free to use another HTTP client library to request 3rd party services.
|
||
|
||
[source,typescript]
|
||
----
|
||
import { CoreStart } from 'kibana/public';
|
||
interface ResponseType {…};
|
||
async function fetchData<ResponseType>(core: CoreStart) {
|
||
return await core.http.get<>(
|
||
'/api/my_plugin/',
|
||
{ query: … },
|
||
);
|
||
}
|
||
----
|
||
See {kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.httpsetup.md[for all available API].
|
||
|
||
==== Elasticsearch service
|
||
Not available in the browser. Use {kib-repo}blob/{branch}/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md[Data plugin] instead.
|
||
|
||
== Patterns
|
||
[[scoped-services]]
|
||
=== Scoped services
|
||
Whenever Kibana needs to get access to data saved in elasticsearch, it
|
||
should perform a check whether an end-user has access to the data. In
|
||
the legacy platform, Kibana requires binding elasticsearch related API
|
||
with an incoming request to access elasticsearch service on behalf of a
|
||
user.
|
||
|
||
[source,js]
|
||
----
|
||
async function handler(req, res) {
|
||
const dataCluster = server.plugins.elasticsearch.getCluster('data');
|
||
const data = await dataCluster.callWithRequest(req, 'ping');
|
||
}
|
||
----
|
||
|
||
The Kibana Platform introduced a handler interface on the server-side to perform that association
|
||
internally. Core services, that require impersonation with an incoming
|
||
request, are exposed via `context` argument of
|
||
{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.requesthandler.md[the
|
||
request handler interface.] The above example looks in the Kibana Platform
|
||
as
|
||
|
||
[source,js]
|
||
----
|
||
async function handler(context, req, res) {
|
||
const data = await context.core.elasticsearch.client.asCurrentUser('ping');
|
||
}
|
||
----
|
||
|
||
The
|
||
{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md[request
|
||
handler context] exposed the next scoped *core* services:
|
||
|
||
[width="100%",cols="30%,70%",options="header",]
|
||
|===
|
||
|Legacy Platform |Kibana Platform
|
||
|`request.getSavedObjectsClient`
|
||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.savedobjectsclient.md[`context.savedObjects.client`]
|
||
|
||
|`server.plugins.elasticsearch.getCluster('admin')`
|
||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md[`context.elasticsearch.client`]
|
||
|
||
|`server.plugins.elasticsearch.getCluster('data')`
|
||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md[`context.elasticsearch.client`]
|
||
|
||
|`request.getUiSettingsService`
|
||
|{kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md[`context.uiSettings.client`]
|
||
|===
|
||
|
||
==== Declare a custom scoped service
|
||
|
||
Plugins can extend the handler context with a custom API that will be
|
||
available to the plugin itself and all dependent plugins. For example,
|
||
the plugin creates a custom elasticsearch client and wants to use it via
|
||
the request handler context:
|
||
|
||
[source,typescript]
|
||
----
|
||
import type { CoreSetup, IScopedClusterClient } from 'kibana/server';
|
||
|
||
export interface MyPluginContext {
|
||
client: IScopedClusterClient;
|
||
}
|
||
|
||
// extend RequestHandlerContext when a dependent plugin imports MyPluginContext from the file
|
||
declare module 'kibana/server' {
|
||
interface RequestHandlerContext {
|
||
myPlugin?: MyPluginContext;
|
||
}
|
||
}
|
||
|
||
class MyPlugin {
|
||
setup(core: CoreSetup) {
|
||
const client = core.elasticsearch.createClient('myClient');
|
||
core.http.registerRouteHandlerContext('myPlugin', (context, req, res) => {
|
||
return { client: client.asScoped(req) };
|
||
});
|
||
const router = core.http.createRouter();
|
||
router.get(
|
||
{ path: '/api/my-plugin/', validate: … },
|
||
async (context, req, res) => {
|
||
const data = await context.myPlugin.client.asCurrentUser('endpoint');
|
||
}
|
||
);
|
||
}
|
||
----
|