kibana/docs/developer/architecture/core/index.asciidoc
Mikhail Shustov b3a9754394
[Core] Explicit typings for request handler context (#88718)
* move context to server part. couple with RequestHandlerContext

Context implementation will be simplified in follow-up.

* adopt core code

* adopt bfetch code

* adopt data code

* adopt search examples

* adopt vis_type_timelion

* adopt vis_type_timeseries

* adopt plugin functional tests

* adopt actions

* adopt alerting plugin

* adopt APM plugin

* adopt beats_management

* adopt case plugin

* adopt cross_cluster_replication

* adopt data_enhanced

* adopt event_log

* adopt global_search

* adopt index_management

* adopt infra

* adopt licensing

* adopt lists

* adopt logstash

* adopt reporting

* adopt observability

* adopt monitoring

* adopt rollup

* adopt so tagging

* adopt security

* adopt security_solutions

* adopt watcher

* adopt uptime

* adopt spaces

* adopt snapshot_restore

* adopt features changes

* mute error when null used to extend context

* update docs

* small cleanup

* add type safety for return type

* refactor registerRouteHandlerContext type

* update docs

* update license header

* update docs

* fix type error. fetch body does not accept array of strings

* fix telemetry test

* remove unnecessary ts-ignore

* address comments

* update docs
2021-01-21 15:20:22 +01:00

447 lines
15 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[[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 wont 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 dont need to specify
the whole property path, but use the relative path from your plugins
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, RequestHandlerContext, IScopedClusterClient } from 'kibana/server';
interface MyRequestHandlerContext extends RequestHandlerContext {
myPlugin: {
client: IScopedClusterClient;
};
}
class MyPlugin {
setup(core: CoreSetup) {
const client = core.elasticsearch.createClient('myClient');
core.http.registerRouteHandlerContext<MyRequestHandlerContext, 'myPlugin'>('myPlugin', (context, req, res) => {
return { client: client.asScoped(req) };
});
const router = core.http.createRouter<MyRequestHandlerContext>();
router.get(
{ path: '/api/my-plugin/', validate: … },
async (context, req, res) => {
// context type is inferred as MyPluginContext
const data = await context.myPlugin.client.asCurrentUser('endpoint');
}
);
}
----