mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Add execution context service (#102039)
* add execution context service on the server-side * integrate execution context service into http service * add integration tests for execution context + http server * update core code * update integration tests * update settings docs * add execution context test plugin * add a client-side test * remove requestId from execution context * add execution context service for the client side * expose execution context service to plugins * add execution context service for the server-side * update http service * update elasticsearch service * move integration tests from http to execution_context service * integrate in es client * expose to plugins * refactor functional tests * remove x-opaque-id from create_cluster tests * update test plugin package.json * fix type errors in the test mocks * fix elasticsearch service tests * add escaping to support non-ascii symbols in description field * improve test coverage * update docs * remove unnecessary import * update docs * Apply suggestions from code review Co-authored-by: Josh Dover <1813008+joshdover@users.noreply.github.com> * address comments * remove execution context cleanup * add option to disable execution_context service on the server side * put x-opaque-id test back * put tests back * add header size limitation to the server side as well * fix integration tests * address comments Co-authored-by: Josh Dover <1813008+joshdover@users.noreply.github.com>
This commit is contained in:
parent
979c6e8031
commit
e01e682917
99 changed files with 2182 additions and 32 deletions
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [CoreStart](./kibana-plugin-core-public.corestart.md) > [executionContext](./kibana-plugin-core-public.corestart.executioncontext.md)
|
||||
|
||||
## CoreStart.executionContext property
|
||||
|
||||
[ExecutionContextServiceStart](./kibana-plugin-core-public.executioncontextservicestart.md)
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
executionContext: ExecutionContextServiceStart;
|
||||
```
|
|
@ -20,6 +20,7 @@ export interface CoreStart
|
|||
| [chrome](./kibana-plugin-core-public.corestart.chrome.md) | <code>ChromeStart</code> | [ChromeStart](./kibana-plugin-core-public.chromestart.md) |
|
||||
| [deprecations](./kibana-plugin-core-public.corestart.deprecations.md) | <code>DeprecationsServiceStart</code> | [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) |
|
||||
| [docLinks](./kibana-plugin-core-public.corestart.doclinks.md) | <code>DocLinksStart</code> | [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) |
|
||||
| [executionContext](./kibana-plugin-core-public.corestart.executioncontext.md) | <code>ExecutionContextServiceStart</code> | [ExecutionContextServiceStart](./kibana-plugin-core-public.executioncontextservicestart.md) |
|
||||
| [fatalErrors](./kibana-plugin-core-public.corestart.fatalerrors.md) | <code>FatalErrorsStart</code> | [FatalErrorsStart](./kibana-plugin-core-public.fatalerrorsstart.md) |
|
||||
| [http](./kibana-plugin-core-public.corestart.http.md) | <code>HttpStart</code> | [HttpStart](./kibana-plugin-core-public.httpstart.md) |
|
||||
| [i18n](./kibana-plugin-core-public.corestart.i18n.md) | <code>I18nStart</code> | [I18nStart](./kibana-plugin-core-public.i18nstart.md) |
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ExecutionContextServiceStart](./kibana-plugin-core-public.executioncontextservicestart.md) > [create](./kibana-plugin-core-public.executioncontextservicestart.create.md)
|
||||
|
||||
## ExecutionContextServiceStart.create property
|
||||
|
||||
Creates a context container carrying the meta-data of a runtime operation. Provided meta-data will be propagated to Kibana and Elasticsearch servers.
|
||||
|
||||
```js
|
||||
const context = executionContext.create(...);
|
||||
http.fetch('/endpoint/', { context });
|
||||
|
||||
```
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
create: (context: KibanaExecutionContext) => IExecutionContextContainer;
|
||||
```
|
|
@ -0,0 +1,25 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [ExecutionContextServiceStart](./kibana-plugin-core-public.executioncontextservicestart.md)
|
||||
|
||||
## ExecutionContextServiceStart interface
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface ExecutionContextServiceStart
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [create](./kibana-plugin-core-public.executioncontextservicestart.create.md) | <code>(context: KibanaExecutionContext) => IExecutionContextContainer</code> | Creates a context container carrying the meta-data of a runtime operation. Provided meta-data will be propagated to Kibana and Elasticsearch servers.
|
||||
```js
|
||||
const context = executionContext.create(...);
|
||||
http.fetch('/endpoint/', { context });
|
||||
|
||||
```
|
||||
|
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [HttpFetchOptions](./kibana-plugin-core-public.httpfetchoptions.md) > [context](./kibana-plugin-core-public.httpfetchoptions.context.md)
|
||||
|
||||
## HttpFetchOptions.context property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
context?: IExecutionContextContainer;
|
||||
```
|
|
@ -18,6 +18,7 @@ export interface HttpFetchOptions extends HttpRequestInit
|
|||
| --- | --- | --- |
|
||||
| [asResponse](./kibana-plugin-core-public.httpfetchoptions.asresponse.md) | <code>boolean</code> | When <code>true</code> the return type of [HttpHandler](./kibana-plugin-core-public.httphandler.md) will be an [HttpResponse](./kibana-plugin-core-public.httpresponse.md) with detailed request and response information. When <code>false</code>, the return type will just be the parsed response body. Defaults to <code>false</code>. |
|
||||
| [asSystemRequest](./kibana-plugin-core-public.httpfetchoptions.assystemrequest.md) | <code>boolean</code> | Whether or not the request should include the "system request" header to differentiate an end user request from Kibana internal request. Can be read on the server-side using KibanaRequest\#isSystemRequest. Defaults to <code>false</code>. |
|
||||
| [context](./kibana-plugin-core-public.httpfetchoptions.context.md) | <code>IExecutionContextContainer</code> | |
|
||||
| [headers](./kibana-plugin-core-public.httpfetchoptions.headers.md) | <code>HttpHeadersInit</code> | Headers to send with the request. See [HttpHeadersInit](./kibana-plugin-core-public.httpheadersinit.md)<!-- -->. |
|
||||
| [prependBasePath](./kibana-plugin-core-public.httpfetchoptions.prependbasepath.md) | <code>boolean</code> | Whether or not the request should automatically prepend the basePath. Defaults to <code>true</code>. |
|
||||
| [query](./kibana-plugin-core-public.httpfetchoptions.query.md) | <code>HttpFetchQuery</code> | The query string for an HTTP request. See [HttpFetchQuery](./kibana-plugin-core-public.httpfetchquery.md)<!-- -->. |
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [IExecutionContextContainer](./kibana-plugin-core-public.iexecutioncontextcontainer.md)
|
||||
|
||||
## IExecutionContextContainer interface
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface IExecutionContextContainer
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [toHeader](./kibana-plugin-core-public.iexecutioncontextcontainer.toheader.md) | <code>() => Record<string, string></code> | |
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [IExecutionContextContainer](./kibana-plugin-core-public.iexecutioncontextcontainer.md) > [toHeader](./kibana-plugin-core-public.iexecutioncontextcontainer.toheader.md)
|
||||
|
||||
## IExecutionContextContainer.toHeader property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
toHeader: () => Record<string, string>;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [KibanaExecutionContext](./kibana-plugin-core-public.kibanaexecutioncontext.md) > [description](./kibana-plugin-core-public.kibanaexecutioncontext.description.md)
|
||||
|
||||
## KibanaExecutionContext.description property
|
||||
|
||||
human readable description. For example, a vis title, action name
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly description: string;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [KibanaExecutionContext](./kibana-plugin-core-public.kibanaexecutioncontext.md) > [id](./kibana-plugin-core-public.kibanaexecutioncontext.id.md)
|
||||
|
||||
## KibanaExecutionContext.id property
|
||||
|
||||
unique value to indentify find the source
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly id: string;
|
||||
```
|
|
@ -0,0 +1,23 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [KibanaExecutionContext](./kibana-plugin-core-public.kibanaexecutioncontext.md)
|
||||
|
||||
## KibanaExecutionContext interface
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface KibanaExecutionContext
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [description](./kibana-plugin-core-public.kibanaexecutioncontext.description.md) | <code>string</code> | human readable description. For example, a vis title, action name |
|
||||
| [id](./kibana-plugin-core-public.kibanaexecutioncontext.id.md) | <code>string</code> | unique value to indentify find the source |
|
||||
| [name](./kibana-plugin-core-public.kibanaexecutioncontext.name.md) | <code>string</code> | public name of a user-facing feature |
|
||||
| [type](./kibana-plugin-core-public.kibanaexecutioncontext.type.md) | <code>string</code> | Kibana application initated an operation. Can be narrowed to an enum later. |
|
||||
| [url](./kibana-plugin-core-public.kibanaexecutioncontext.url.md) | <code>string</code> | in browser - url to navigate to a current page, on server - endpoint path, for task: task SO url |
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [KibanaExecutionContext](./kibana-plugin-core-public.kibanaexecutioncontext.md) > [name](./kibana-plugin-core-public.kibanaexecutioncontext.name.md)
|
||||
|
||||
## KibanaExecutionContext.name property
|
||||
|
||||
public name of a user-facing feature
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly name: string;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [KibanaExecutionContext](./kibana-plugin-core-public.kibanaexecutioncontext.md) > [type](./kibana-plugin-core-public.kibanaexecutioncontext.type.md)
|
||||
|
||||
## KibanaExecutionContext.type property
|
||||
|
||||
Kibana application initated an operation. Can be narrowed to an enum later.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly type: string;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [KibanaExecutionContext](./kibana-plugin-core-public.kibanaexecutioncontext.md) > [url](./kibana-plugin-core-public.kibanaexecutioncontext.url.md)
|
||||
|
||||
## KibanaExecutionContext.url property
|
||||
|
||||
in browser - url to navigate to a current page, on server - endpoint path, for task: task SO url
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly url?: string;
|
||||
```
|
|
@ -63,6 +63,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) | |
|
||||
| [DomainDeprecationDetails](./kibana-plugin-core-public.domaindeprecationdetails.md) | |
|
||||
| [ErrorToastOptions](./kibana-plugin-core-public.errortoastoptions.md) | Options available for [IToasts](./kibana-plugin-core-public.itoasts.md) error APIs. |
|
||||
| [ExecutionContextServiceStart](./kibana-plugin-core-public.executioncontextservicestart.md) | |
|
||||
| [FatalErrorInfo](./kibana-plugin-core-public.fatalerrorinfo.md) | Represents the <code>message</code> and <code>stack</code> of a fatal Error |
|
||||
| [FatalErrorsSetup](./kibana-plugin-core-public.fatalerrorssetup.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. |
|
||||
| [HttpFetchOptions](./kibana-plugin-core-public.httpfetchoptions.md) | All options that may be used with a [HttpHandler](./kibana-plugin-core-public.httphandler.md)<!-- -->. |
|
||||
|
@ -79,12 +80,14 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [I18nStart](./kibana-plugin-core-public.i18nstart.md) | I18nStart.Context is required by any localizable React component from @<!-- -->kbn/i18n and @<!-- -->elastic/eui packages and is supposed to be used as the topmost component for any i18n-compatible React tree. |
|
||||
| [IAnonymousPaths](./kibana-plugin-core-public.ianonymouspaths.md) | APIs for denoting paths as not requiring authentication |
|
||||
| [IBasePath](./kibana-plugin-core-public.ibasepath.md) | APIs for manipulating the basePath on URL segments. |
|
||||
| [IExecutionContextContainer](./kibana-plugin-core-public.iexecutioncontextcontainer.md) | |
|
||||
| [IExternalUrl](./kibana-plugin-core-public.iexternalurl.md) | APIs for working with external URLs. |
|
||||
| [IExternalUrlPolicy](./kibana-plugin-core-public.iexternalurlpolicy.md) | A policy describing whether access to an external destination is allowed. |
|
||||
| [IHttpFetchError](./kibana-plugin-core-public.ihttpfetcherror.md) | |
|
||||
| [IHttpInterceptController](./kibana-plugin-core-public.ihttpinterceptcontroller.md) | Used to halt a request Promise chain in a [HttpInterceptor](./kibana-plugin-core-public.httpinterceptor.md)<!-- -->. |
|
||||
| [IHttpResponseInterceptorOverrides](./kibana-plugin-core-public.ihttpresponseinterceptoroverrides.md) | Properties that can be returned by HttpInterceptor.request to override the response. |
|
||||
| [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) | Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [IUiSettingsClient](./kibana-plugin-core-public.iuisettingsclient.md) |
|
||||
| [KibanaExecutionContext](./kibana-plugin-core-public.kibanaexecutioncontext.md) | |
|
||||
| [NavigateToAppOptions](./kibana-plugin-core-public.navigatetoappoptions.md) | Options for the [navigateToApp API](./kibana-plugin-core-public.applicationstart.navigatetoapp.md) |
|
||||
| [NotificationsSetup](./kibana-plugin-core-public.notificationssetup.md) | |
|
||||
| [NotificationsStart](./kibana-plugin-core-public.notificationsstart.md) | |
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreSetup](./kibana-plugin-core-server.coresetup.md) > [executionContext](./kibana-plugin-core-server.coresetup.executioncontext.md)
|
||||
|
||||
## CoreSetup.executionContext property
|
||||
|
||||
[ExecutionContextSetup](./kibana-plugin-core-server.executioncontextsetup.md)
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
executionContext: ExecutionContextSetup;
|
||||
```
|
|
@ -20,6 +20,7 @@ export interface CoreSetup<TPluginsStart extends object = object, TStart = unkno
|
|||
| [context](./kibana-plugin-core-server.coresetup.context.md) | <code>ContextSetup</code> | [ContextSetup](./kibana-plugin-core-server.contextsetup.md) |
|
||||
| [deprecations](./kibana-plugin-core-server.coresetup.deprecations.md) | <code>DeprecationsServiceSetup</code> | [DeprecationsServiceSetup](./kibana-plugin-core-server.deprecationsservicesetup.md) |
|
||||
| [elasticsearch](./kibana-plugin-core-server.coresetup.elasticsearch.md) | <code>ElasticsearchServiceSetup</code> | [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) |
|
||||
| [executionContext](./kibana-plugin-core-server.coresetup.executioncontext.md) | <code>ExecutionContextSetup</code> | [ExecutionContextSetup](./kibana-plugin-core-server.executioncontextsetup.md) |
|
||||
| [getStartServices](./kibana-plugin-core-server.coresetup.getstartservices.md) | <code>StartServicesAccessor<TPluginsStart, TStart></code> | [StartServicesAccessor](./kibana-plugin-core-server.startservicesaccessor.md) |
|
||||
| [http](./kibana-plugin-core-server.coresetup.http.md) | <code>HttpServiceSetup & {</code><br/><code> resources: HttpResources;</code><br/><code> }</code> | [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) |
|
||||
| [i18n](./kibana-plugin-core-server.coresetup.i18n.md) | <code>I18nServiceSetup</code> | [I18nServiceSetup](./kibana-plugin-core-server.i18nservicesetup.md) |
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreStart](./kibana-plugin-core-server.corestart.md) > [executionContext](./kibana-plugin-core-server.corestart.executioncontext.md)
|
||||
|
||||
## CoreStart.executionContext property
|
||||
|
||||
[ExecutionContextStart](./kibana-plugin-core-server.executioncontextstart.md)
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
executionContext: ExecutionContextStart;
|
||||
```
|
|
@ -18,6 +18,7 @@ export interface CoreStart
|
|||
| --- | --- | --- |
|
||||
| [capabilities](./kibana-plugin-core-server.corestart.capabilities.md) | <code>CapabilitiesStart</code> | [CapabilitiesStart](./kibana-plugin-core-server.capabilitiesstart.md) |
|
||||
| [elasticsearch](./kibana-plugin-core-server.corestart.elasticsearch.md) | <code>ElasticsearchServiceStart</code> | [ElasticsearchServiceStart](./kibana-plugin-core-server.elasticsearchservicestart.md) |
|
||||
| [executionContext](./kibana-plugin-core-server.corestart.executioncontext.md) | <code>ExecutionContextStart</code> | [ExecutionContextStart](./kibana-plugin-core-server.executioncontextstart.md) |
|
||||
| [http](./kibana-plugin-core-server.corestart.http.md) | <code>HttpServiceStart</code> | [HttpServiceStart](./kibana-plugin-core-server.httpservicestart.md) |
|
||||
| [metrics](./kibana-plugin-core-server.corestart.metrics.md) | <code>MetricsServiceStart</code> | [MetricsServiceStart](./kibana-plugin-core-server.metricsservicestart.md) |
|
||||
| [savedObjects](./kibana-plugin-core-server.corestart.savedobjects.md) | <code>SavedObjectsServiceStart</code> | [SavedObjectsServiceStart](./kibana-plugin-core-server.savedobjectsservicestart.md) |
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ExecutionContextSetup](./kibana-plugin-core-server.executioncontextsetup.md) > [get](./kibana-plugin-core-server.executioncontextsetup.get.md)
|
||||
|
||||
## ExecutionContextSetup.get() method
|
||||
|
||||
Retrieves an opearation meta-data for the current async context.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
get(): IExecutionContextContainer | undefined;
|
||||
```
|
||||
<b>Returns:</b>
|
||||
|
||||
`IExecutionContextContainer | undefined`
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ExecutionContextSetup](./kibana-plugin-core-server.executioncontextsetup.md)
|
||||
|
||||
## ExecutionContextSetup interface
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface ExecutionContextSetup
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| [get()](./kibana-plugin-core-server.executioncontextsetup.get.md) | Retrieves an opearation meta-data for the current async context. |
|
||||
| [set(context)](./kibana-plugin-core-server.executioncontextsetup.set.md) | Stores the meta-data of a runtime operation. Data are carried over all async operations automatically. The sequential calls merge provided "context" object shallowly. |
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ExecutionContextSetup](./kibana-plugin-core-server.executioncontextsetup.md) > [set](./kibana-plugin-core-server.executioncontextsetup.set.md)
|
||||
|
||||
## ExecutionContextSetup.set() method
|
||||
|
||||
Stores the meta-data of a runtime operation. Data are carried over all async operations automatically. The sequential calls merge provided "context" object shallowly.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
set(context: Partial<KibanaServerExecutionContext>): void;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| context | <code>Partial<KibanaServerExecutionContext></code> | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
`void`
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ExecutionContextStart](./kibana-plugin-core-server.executioncontextstart.md)
|
||||
|
||||
## ExecutionContextStart type
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type ExecutionContextStart = ExecutionContextSetup;
|
||||
```
|
|
@ -0,0 +1,20 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IExecutionContextContainer](./kibana-plugin-core-server.iexecutioncontextcontainer.md)
|
||||
|
||||
## IExecutionContextContainer interface
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface IExecutionContextContainer
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| [toJSON()](./kibana-plugin-core-server.iexecutioncontextcontainer.tojson.md) | |
|
||||
| [toString()](./kibana-plugin-core-server.iexecutioncontextcontainer.tostring.md) | |
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IExecutionContextContainer](./kibana-plugin-core-server.iexecutioncontextcontainer.md) > [toJSON](./kibana-plugin-core-server.iexecutioncontextcontainer.tojson.md)
|
||||
|
||||
## IExecutionContextContainer.toJSON() method
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
toJSON(): Readonly<KibanaServerExecutionContext>;
|
||||
```
|
||||
<b>Returns:</b>
|
||||
|
||||
`Readonly<KibanaServerExecutionContext>`
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IExecutionContextContainer](./kibana-plugin-core-server.iexecutioncontextcontainer.md) > [toString](./kibana-plugin-core-server.iexecutioncontextcontainer.tostring.md)
|
||||
|
||||
## IExecutionContextContainer.toString() method
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
toString(): string;
|
||||
```
|
||||
<b>Returns:</b>
|
||||
|
||||
`string`
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaExecutionContext](./kibana-plugin-core-server.kibanaexecutioncontext.md) > [description](./kibana-plugin-core-server.kibanaexecutioncontext.description.md)
|
||||
|
||||
## KibanaExecutionContext.description property
|
||||
|
||||
human readable description. For example, a vis title, action name
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly description: string;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaExecutionContext](./kibana-plugin-core-server.kibanaexecutioncontext.md) > [id](./kibana-plugin-core-server.kibanaexecutioncontext.id.md)
|
||||
|
||||
## KibanaExecutionContext.id property
|
||||
|
||||
unique value to indentify find the source
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly id: string;
|
||||
```
|
|
@ -0,0 +1,23 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaExecutionContext](./kibana-plugin-core-server.kibanaexecutioncontext.md)
|
||||
|
||||
## KibanaExecutionContext interface
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface KibanaExecutionContext
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [description](./kibana-plugin-core-server.kibanaexecutioncontext.description.md) | <code>string</code> | human readable description. For example, a vis title, action name |
|
||||
| [id](./kibana-plugin-core-server.kibanaexecutioncontext.id.md) | <code>string</code> | unique value to indentify find the source |
|
||||
| [name](./kibana-plugin-core-server.kibanaexecutioncontext.name.md) | <code>string</code> | public name of a user-facing feature |
|
||||
| [type](./kibana-plugin-core-server.kibanaexecutioncontext.type.md) | <code>string</code> | Kibana application initated an operation. Can be narrowed to an enum later. |
|
||||
| [url](./kibana-plugin-core-server.kibanaexecutioncontext.url.md) | <code>string</code> | in browser - url to navigate to a current page, on server - endpoint path, for task: task SO url |
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaExecutionContext](./kibana-plugin-core-server.kibanaexecutioncontext.md) > [name](./kibana-plugin-core-server.kibanaexecutioncontext.name.md)
|
||||
|
||||
## KibanaExecutionContext.name property
|
||||
|
||||
public name of a user-facing feature
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly name: string;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaExecutionContext](./kibana-plugin-core-server.kibanaexecutioncontext.md) > [type](./kibana-plugin-core-server.kibanaexecutioncontext.type.md)
|
||||
|
||||
## KibanaExecutionContext.type property
|
||||
|
||||
Kibana application initated an operation. Can be narrowed to an enum later.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly type: string;
|
||||
```
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaExecutionContext](./kibana-plugin-core-server.kibanaexecutioncontext.md) > [url](./kibana-plugin-core-server.kibanaexecutioncontext.url.md)
|
||||
|
||||
## KibanaExecutionContext.url property
|
||||
|
||||
in browser - url to navigate to a current page, on server - endpoint path, for task: task SO url
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
readonly url?: string;
|
||||
```
|
|
@ -0,0 +1,19 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaServerExecutionContext](./kibana-plugin-core-server.kibanaserverexecutioncontext.md)
|
||||
|
||||
## KibanaServerExecutionContext interface
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface KibanaServerExecutionContext extends Partial<KibanaExecutionContext>
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [requestId](./kibana-plugin-core-server.kibanaserverexecutioncontext.requestid.md) | <code>string</code> | |
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [KibanaServerExecutionContext](./kibana-plugin-core-server.kibanaserverexecutioncontext.md) > [requestId](./kibana-plugin-core-server.kibanaserverexecutioncontext.requestid.md)
|
||||
|
||||
## KibanaServerExecutionContext.requestId property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
requestId: string;
|
||||
```
|
|
@ -77,6 +77,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [ElasticsearchServiceStart](./kibana-plugin-core-server.elasticsearchservicestart.md) | |
|
||||
| [ElasticsearchStatusMeta](./kibana-plugin-core-server.elasticsearchstatusmeta.md) | |
|
||||
| [ErrorHttpResponseOptions](./kibana-plugin-core-server.errorhttpresponseoptions.md) | HTTP response parameters |
|
||||
| [ExecutionContextSetup](./kibana-plugin-core-server.executioncontextsetup.md) | |
|
||||
| [FakeRequest](./kibana-plugin-core-server.fakerequest.md) | Fake request object created manually by Kibana plugins. |
|
||||
| [GetDeprecationsContext](./kibana-plugin-core-server.getdeprecationscontext.md) | |
|
||||
| [GetResponse](./kibana-plugin-core-server.getresponse.md) | |
|
||||
|
@ -93,6 +94,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [IContextContainer](./kibana-plugin-core-server.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. |
|
||||
| [ICspConfig](./kibana-plugin-core-server.icspconfig.md) | CSP configuration for use in Kibana. |
|
||||
| [ICustomClusterClient](./kibana-plugin-core-server.icustomclusterclient.md) | See [IClusterClient](./kibana-plugin-core-server.iclusterclient.md) |
|
||||
| [IExecutionContextContainer](./kibana-plugin-core-server.iexecutioncontextcontainer.md) | |
|
||||
| [IExternalUrlConfig](./kibana-plugin-core-server.iexternalurlconfig.md) | External Url configuration for use in Kibana. |
|
||||
| [IExternalUrlPolicy](./kibana-plugin-core-server.iexternalurlpolicy.md) | A policy describing whether access to an external destination is allowed. |
|
||||
| [IKibanaResponse](./kibana-plugin-core-server.ikibanaresponse.md) | A response data object, expected to returned as a result of [RequestHandler](./kibana-plugin-core-server.requesthandler.md) execution |
|
||||
|
@ -103,8 +105,10 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [ISavedObjectsPointInTimeFinder](./kibana-plugin-core-server.isavedobjectspointintimefinder.md) | |
|
||||
| [IScopedClusterClient](./kibana-plugin-core-server.iscopedclusterclient.md) | Serves the same purpose as the normal [cluster client](./kibana-plugin-core-server.iclusterclient.md) but exposes an additional <code>asCurrentUser</code> method that doesn't use credentials of the Kibana internal user (as <code>asInternalUser</code> does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API instead. |
|
||||
| [IUiSettingsClient](./kibana-plugin-core-server.iuisettingsclient.md) | Server-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. |
|
||||
| [KibanaExecutionContext](./kibana-plugin-core-server.kibanaexecutioncontext.md) | |
|
||||
| [KibanaRequestEvents](./kibana-plugin-core-server.kibanarequestevents.md) | Request events. |
|
||||
| [KibanaRequestRoute](./kibana-plugin-core-server.kibanarequestroute.md) | Request specific route information exposed to a handler. |
|
||||
| [KibanaServerExecutionContext](./kibana-plugin-core-server.kibanaserverexecutioncontext.md) | |
|
||||
| [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md) | |
|
||||
| [LegacyCallAPIOptions](./kibana-plugin-core-server.legacycallapioptions.md) | The set of options that defines how API call should be made and result be processed. |
|
||||
| [LegacyElasticsearchError](./kibana-plugin-core-server.legacyelasticsearcherror.md) | @<!-- -->deprecated. The new elasticsearch client doesn't wrap errors anymore. 7.16 |
|
||||
|
@ -248,6 +252,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
|
|||
| [DestructiveRouteMethod](./kibana-plugin-core-server.destructiveroutemethod.md) | Set of HTTP methods changing the state of the server. |
|
||||
| [ElasticsearchClient](./kibana-plugin-core-server.elasticsearchclient.md) | Client used to query the elasticsearch cluster. |
|
||||
| [ElasticsearchClientConfig](./kibana-plugin-core-server.elasticsearchclientconfig.md) | Configuration options to be used to create a [cluster client](./kibana-plugin-core-server.iclusterclient.md) using the [createClient API](./kibana-plugin-core-server.elasticsearchservicestart.createclient.md) |
|
||||
| [ExecutionContextStart](./kibana-plugin-core-server.executioncontextstart.md) | |
|
||||
| [GetAuthHeaders](./kibana-plugin-core-server.getauthheaders.md) | Get headers to authenticate a user against Elasticsearch. |
|
||||
| [GetAuthState](./kibana-plugin-core-server.getauthstate.md) | Gets authentication state for a request. Returned by <code>auth</code> interceptor. |
|
||||
| [HandlerContextType](./kibana-plugin-core-server.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-core-server.handlerfunction.md) to represent the type of the context. |
|
||||
|
|
|
@ -627,7 +627,7 @@ identifies this {kib} instance. *Default: `"your-hostname"`*
|
|||
setting specifies the port to use. *Default: `5601`*
|
||||
|
||||
|[[server-requestId-allowFromAnyIp]] `server.requestId.allowFromAnyIp:`
|
||||
| Sets whether or not the X-Opaque-Id header should be trusted from any IP address for identifying requests in logs and forwarded to Elasticsearch.
|
||||
| Sets whether or not the `X-Opaque-Id` header should be trusted from any IP address for identifying requests in logs and forwarded to Elasticsearch.
|
||||
|
||||
| `server.requestId.ipAllowlist:`
|
||||
| A list of IPv4 and IPv6 address which the `X-Opaque-Id` header should be trusted from. Normally this would be set to the IP addresses of the load balancers or reverse-proxy that end users use to access Kibana. If any are set, <<server-requestId-allowFromAnyIp, `server.requestId.allowFromAnyIp`>> must also be set to `false.`
|
||||
|
|
|
@ -19,6 +19,7 @@ import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock';
|
|||
import { docLinksServiceMock } from './doc_links/doc_links_service.mock';
|
||||
import { renderingServiceMock } from './rendering/rendering_service.mock';
|
||||
import { integrationsServiceMock } from './integrations/integrations_service.mock';
|
||||
import { executionContextServiceMock } from './execution_context/execution_context_service.mock';
|
||||
import { coreAppMock } from './core_app/core_app.mock';
|
||||
|
||||
export const MockInjectedMetadataService = injectedMetadataServiceMock.create();
|
||||
|
@ -111,6 +112,14 @@ jest.doMock('./integrations', () => ({
|
|||
IntegrationsService: IntegrationsServiceConstructor,
|
||||
}));
|
||||
|
||||
export const MockExecutionContextService = executionContextServiceMock.create();
|
||||
export const ExecutionContextServiceConstructor = jest
|
||||
.fn()
|
||||
.mockImplementation(() => MockExecutionContextService);
|
||||
jest.doMock('./execution_context', () => ({
|
||||
ExecutionContextService: ExecutionContextServiceConstructor,
|
||||
}));
|
||||
|
||||
export const MockCoreApp = coreAppMock.create();
|
||||
export const CoreAppConstructor = jest.fn().mockImplementation(() => MockCoreApp);
|
||||
jest.doMock('./core_app', () => ({
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
RenderingServiceConstructor,
|
||||
IntegrationsServiceConstructor,
|
||||
MockIntegrationsService,
|
||||
MockExecutionContextService,
|
||||
CoreAppConstructor,
|
||||
MockCoreApp,
|
||||
} from './core_system.test.mocks';
|
||||
|
@ -182,6 +183,11 @@ describe('#setup()', () => {
|
|||
await setupCore();
|
||||
expect(MockCoreApp.setup).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls executionContext.setup()', async () => {
|
||||
await setupCore();
|
||||
expect(MockExecutionContextService.setup).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#start()', () => {
|
||||
|
@ -269,6 +275,11 @@ describe('#start()', () => {
|
|||
await startCore();
|
||||
expect(MockCoreApp.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls executionContext.start()', async () => {
|
||||
await startCore();
|
||||
expect(MockExecutionContextService.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#stop()', () => {
|
||||
|
@ -327,6 +338,14 @@ describe('#stop()', () => {
|
|||
expect(MockCoreApp.stop).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls executionContext.stop()', () => {
|
||||
const coreSystem = createCoreSystem();
|
||||
|
||||
expect(MockExecutionContextService.stop).not.toHaveBeenCalled();
|
||||
coreSystem.stop();
|
||||
expect(MockExecutionContextService.stop).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('clears the rootDomElement', async () => {
|
||||
const rootDomElement = document.createElement('div');
|
||||
const coreSystem = createCoreSystem({
|
||||
|
|
|
@ -29,6 +29,7 @@ import { SavedObjectsService } from './saved_objects';
|
|||
import { IntegrationsService } from './integrations';
|
||||
import { DeprecationsService } from './deprecations';
|
||||
import { CoreApp } from './core_app';
|
||||
import { ExecutionContextService } from './execution_context';
|
||||
import type { InternalApplicationSetup, InternalApplicationStart } from './application/types';
|
||||
|
||||
interface Params {
|
||||
|
@ -83,6 +84,7 @@ export class CoreSystem {
|
|||
private readonly integrations: IntegrationsService;
|
||||
private readonly coreApp: CoreApp;
|
||||
private readonly deprecations: DeprecationsService;
|
||||
private readonly executionContext: ExecutionContextService;
|
||||
private readonly rootDomElement: HTMLElement;
|
||||
private readonly coreContext: CoreContext;
|
||||
private fatalErrorsSetup: FatalErrorsSetup | null = null;
|
||||
|
@ -118,6 +120,7 @@ export class CoreSystem {
|
|||
this.application = new ApplicationService();
|
||||
this.integrations = new IntegrationsService();
|
||||
this.deprecations = new DeprecationsService();
|
||||
this.executionContext = new ExecutionContextService();
|
||||
|
||||
this.plugins = new PluginsService(this.coreContext, injectedMetadata.uiPlugins);
|
||||
this.coreApp = new CoreApp(this.coreContext);
|
||||
|
@ -137,6 +140,7 @@ export class CoreSystem {
|
|||
const http = this.http.setup({ injectedMetadata, fatalErrors: this.fatalErrorsSetup });
|
||||
const uiSettings = this.uiSettings.setup({ http, injectedMetadata });
|
||||
const notifications = this.notifications.setup({ uiSettings });
|
||||
this.executionContext.setup();
|
||||
|
||||
const application = this.application.setup({ http });
|
||||
this.coreApp.setup({ application, http, injectedMetadata, notifications });
|
||||
|
@ -201,6 +205,7 @@ export class CoreSystem {
|
|||
notifications,
|
||||
});
|
||||
const deprecations = this.deprecations.start({ http });
|
||||
const executionContext = this.executionContext.start();
|
||||
|
||||
this.coreApp.start({ application, docLinks, http, notifications, uiSettings });
|
||||
|
||||
|
@ -217,6 +222,7 @@ export class CoreSystem {
|
|||
uiSettings,
|
||||
fatalErrors,
|
||||
deprecations,
|
||||
executionContext,
|
||||
};
|
||||
|
||||
await this.plugins.start(core);
|
||||
|
@ -260,6 +266,7 @@ export class CoreSystem {
|
|||
this.i18n.stop();
|
||||
this.application.stop();
|
||||
this.deprecations.stop();
|
||||
this.executionContext.stop();
|
||||
this.rootDomElement.textContent = '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { KibanaExecutionContext } from '../../types';
|
||||
import {
|
||||
ExecutionContextContainer,
|
||||
BAGGAGE_MAX_PER_NAME_VALUE_PAIRS,
|
||||
} from './execution_context_container';
|
||||
|
||||
describe('KibanaExecutionContext', () => {
|
||||
describe('toHeader', () => {
|
||||
it('returns an escaped string representation of provided execution context', () => {
|
||||
const context: KibanaExecutionContext = {
|
||||
type: 'test-type',
|
||||
name: 'test-name',
|
||||
id: '42',
|
||||
description: 'test-descripton',
|
||||
};
|
||||
|
||||
const value = new ExecutionContextContainer(context).toHeader();
|
||||
expect(value).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"x-kbn-context": "%7B%22type%22%3A%22test-type%22%2C%22name%22%3A%22test-name%22%2C%22id%22%3A%2242%22%2C%22description%22%3A%22test-descripton%22%7D",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('trims a string representation of provided execution context if it is bigger max allowed size', () => {
|
||||
const context: KibanaExecutionContext = {
|
||||
type: 'test-type',
|
||||
name: 'test-name',
|
||||
id: '42',
|
||||
description: 'long long test-descripton,'.repeat(1000),
|
||||
};
|
||||
|
||||
const value = new ExecutionContextContainer(context).toHeader();
|
||||
expect(value).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"x-kbn-context": "%7B%22type%22%3A%22test-type%22%2C%22name%22%3A%22test-name%22%2C%22id%22%3A%2242%22%2C%22description%22%3A%22long%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test-descripton%2Clong%20long%20test",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(new Blob(Object.values(value)).size).toBeLessThanOrEqual(
|
||||
BAGGAGE_MAX_PER_NAME_VALUE_PAIRS
|
||||
);
|
||||
});
|
||||
|
||||
it('escapes the string representation of provided execution context', () => {
|
||||
const context: KibanaExecutionContext = {
|
||||
type: 'test-type',
|
||||
name: 'test-name',
|
||||
id: '42',
|
||||
description: 'описание',
|
||||
};
|
||||
|
||||
const value = new ExecutionContextContainer(context).toHeader();
|
||||
expect(value).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"x-kbn-context": "%7B%22type%22%3A%22test-type%22%2C%22name%22%3A%22test-name%22%2C%22id%22%3A%2242%22%2C%22description%22%3A%22%D0%BE%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5%22%7D",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { KibanaExecutionContext } from '../../types';
|
||||
|
||||
// Switch to the standard Baggage header
|
||||
// https://github.com/elastic/apm-agent-rum-js/issues/1040
|
||||
const BAGGAGE_HEADER = 'x-kbn-context';
|
||||
|
||||
// Maximum number of bytes per a single name-value pair allowed by w3c spec
|
||||
// https://w3c.github.io/baggage/
|
||||
export const BAGGAGE_MAX_PER_NAME_VALUE_PAIRS = 4096;
|
||||
|
||||
// a single character can use up to 4 bytes
|
||||
const MAX_BAGGAGE_LENGTH = BAGGAGE_MAX_PER_NAME_VALUE_PAIRS / 4;
|
||||
|
||||
// Limits the header value to max allowed "baggage" header property name-value pair
|
||||
// It will help us switch to the "baggage" header when it becomes the standard.
|
||||
// The trimmed value in the logs is better than nothing.
|
||||
function enforceMaxLength(header: string): string {
|
||||
return header.slice(0, MAX_BAGGAGE_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface IExecutionContextContainer {
|
||||
toHeader: () => Record<string, string>;
|
||||
}
|
||||
|
||||
export class ExecutionContextContainer implements IExecutionContextContainer {
|
||||
readonly #context: Readonly<KibanaExecutionContext>;
|
||||
constructor(context: Readonly<KibanaExecutionContext>) {
|
||||
this.#context = context;
|
||||
}
|
||||
private toString(): string {
|
||||
const value = JSON.stringify(this.#context);
|
||||
// escape content as the description property might contain non-ASCII symbols
|
||||
return enforceMaxLength(encodeURIComponent(value));
|
||||
}
|
||||
toHeader() {
|
||||
return { [BAGGAGE_HEADER]: this.toString() };
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { PublicMethodsOf } from '@kbn/utility-types';
|
||||
import type { Plugin } from 'src/core/public';
|
||||
import type { ExecutionContextServiceStart } from './execution_context_service';
|
||||
import type { ExecutionContextContainer } from './execution_context_container';
|
||||
|
||||
const createContainerMock = () => {
|
||||
const mock: jest.Mocked<PublicMethodsOf<ExecutionContextContainer>> = {
|
||||
toHeader: jest.fn(),
|
||||
};
|
||||
return mock;
|
||||
};
|
||||
const createStartContractMock = () => {
|
||||
const mock: jest.Mocked<ExecutionContextServiceStart> = {
|
||||
create: jest.fn().mockReturnValue(createContainerMock()),
|
||||
};
|
||||
return mock;
|
||||
};
|
||||
|
||||
const createMock = (): jest.Mocked<Plugin> => ({
|
||||
setup: jest.fn(),
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
});
|
||||
|
||||
export const executionContextServiceMock = {
|
||||
create: createMock,
|
||||
createStartContract: createStartContractMock,
|
||||
createContainer: createContainerMock,
|
||||
};
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { CoreService, KibanaExecutionContext } from '../../types';
|
||||
import {
|
||||
ExecutionContextContainer,
|
||||
IExecutionContextContainer,
|
||||
} from './execution_context_container';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ExecutionContextServiceStart {
|
||||
/**
|
||||
* Creates a context container carrying the meta-data of a runtime operation.
|
||||
* Provided meta-data will be propagated to Kibana and Elasticsearch servers.
|
||||
* ```js
|
||||
* const context = executionContext.create(...);
|
||||
* http.fetch('/endpoint/', { context });
|
||||
* ```
|
||||
*/
|
||||
create: (context: KibanaExecutionContext) => IExecutionContextContainer;
|
||||
}
|
||||
|
||||
export class ExecutionContextService implements CoreService<void, ExecutionContextServiceStart> {
|
||||
setup() {}
|
||||
start(): ExecutionContextServiceStart {
|
||||
return {
|
||||
create(context: KibanaExecutionContext) {
|
||||
return new ExecutionContextContainer(context);
|
||||
},
|
||||
};
|
||||
}
|
||||
stop() {}
|
||||
}
|
12
src/core/public/execution_context/index.ts
Normal file
12
src/core/public/execution_context/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export type { KibanaExecutionContext } from '../../types';
|
||||
export { ExecutionContextService } from './execution_context_service';
|
||||
export type { ExecutionContextServiceStart } from './execution_context_service';
|
||||
export type { IExecutionContextContainer } from './execution_context_container';
|
|
@ -15,6 +15,7 @@ import { first } from 'rxjs/operators';
|
|||
import { Fetch } from './fetch';
|
||||
import { BasePath } from './base_path';
|
||||
import { HttpResponse, HttpFetchOptionsWithPath } from './types';
|
||||
import { executionContextServiceMock } from '../execution_context/execution_context_service.mock';
|
||||
|
||||
function delay<T>(duration: number) {
|
||||
return new Promise<T>((r) => setTimeout(r, duration));
|
||||
|
@ -227,6 +228,19 @@ describe('Fetch', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should inject context headers if provided', async () => {
|
||||
fetchMock.get('*', {});
|
||||
const executionContainerMock = executionContextServiceMock.createContainer();
|
||||
executionContainerMock.toHeader.mockReturnValueOnce({ 'x-kbn-context': 'value' });
|
||||
await fetchInstance.fetch('/my/path', {
|
||||
context: executionContainerMock,
|
||||
});
|
||||
|
||||
expect(fetchMock.lastOptions()!.headers).toMatchObject({
|
||||
'x-kbn-context': 'value',
|
||||
});
|
||||
});
|
||||
|
||||
// Deprecated header used by legacy platform pre-7.7. Remove in 8.x.
|
||||
it('should not allow overwriting of kbn-system-api when asSystemRequest: true', async () => {
|
||||
fetchMock.get('*', {});
|
||||
|
|
|
@ -124,6 +124,7 @@ export class Fetch {
|
|||
'Content-Type': 'application/json',
|
||||
...options.headers,
|
||||
'kbn-version': this.params.kibanaVersion,
|
||||
...options.context?.toHeader(),
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import { Observable } from 'rxjs';
|
||||
import { MaybePromise } from '@kbn/utility-types';
|
||||
import type { IExecutionContextContainer } from '../execution_context';
|
||||
|
||||
/** @public */
|
||||
export interface HttpSetup {
|
||||
|
@ -270,6 +271,8 @@ export interface HttpFetchOptions extends HttpRequestInit {
|
|||
* response information. When `false`, the return type will just be the parsed response body. Defaults to `false`.
|
||||
*/
|
||||
asResponse?: boolean;
|
||||
|
||||
context?: IExecutionContextContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -65,6 +65,7 @@ import { ApplicationSetup, Capabilities, ApplicationStart } from './application'
|
|||
import { DocLinksStart } from './doc_links';
|
||||
import { SavedObjectsStart } from './saved_objects';
|
||||
import { DeprecationsServiceStart } from './deprecations';
|
||||
import type { ExecutionContextServiceStart } from './execution_context';
|
||||
|
||||
export type {
|
||||
PackageInfo,
|
||||
|
@ -185,6 +186,12 @@ export type {
|
|||
|
||||
export type { DeprecationsServiceStart, ResolveDeprecationResponse } from './deprecations';
|
||||
|
||||
export type {
|
||||
IExecutionContextContainer,
|
||||
ExecutionContextServiceStart,
|
||||
KibanaExecutionContext,
|
||||
} from './execution_context';
|
||||
|
||||
export type { MountPoint, UnmountCallback, PublicUiSettingsParams } from './types';
|
||||
|
||||
export { URL_MAX_LENGTH } from './core_app';
|
||||
|
@ -271,6 +278,8 @@ export interface CoreStart {
|
|||
fatalErrors: FatalErrorsStart;
|
||||
/** {@link DeprecationsServiceStart} */
|
||||
deprecations: DeprecationsServiceStart;
|
||||
/** {@link ExecutionContextServiceStart} */
|
||||
executionContext: ExecutionContextServiceStart;
|
||||
/**
|
||||
* exposed temporarily until https://github.com/elastic/kibana/issues/41990 done
|
||||
* use *only* to retrieve config values. There is no way to set injected values
|
||||
|
|
|
@ -25,6 +25,7 @@ import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock';
|
|||
import { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock';
|
||||
import { injectedMetadataServiceMock } from './injected_metadata/injected_metadata_service.mock';
|
||||
import { deprecationsServiceMock } from './deprecations/deprecations_service.mock';
|
||||
import { executionContextServiceMock } from './execution_context/execution_context_service.mock';
|
||||
|
||||
export { chromeServiceMock } from './chrome/chrome_service.mock';
|
||||
export { docLinksServiceMock } from './doc_links/doc_links_service.mock';
|
||||
|
@ -39,6 +40,7 @@ export { savedObjectsServiceMock } from './saved_objects/saved_objects_service.m
|
|||
export { scopedHistoryMock } from './application/scoped_history.mock';
|
||||
export { applicationServiceMock } from './application/application_service.mock';
|
||||
export { deprecationsServiceMock } from './deprecations/deprecations_service.mock';
|
||||
export { executionContextServiceMock } from './execution_context/execution_context_service.mock';
|
||||
|
||||
function createCoreSetupMock({
|
||||
basePath = '',
|
||||
|
@ -84,6 +86,7 @@ function createCoreStartMock({ basePath = '' } = {}) {
|
|||
getInjectedVar: injectedMetadataServiceMock.createStartContract().getInjectedVar,
|
||||
},
|
||||
fatalErrors: fatalErrorsServiceMock.createStartContract(),
|
||||
executionContext: executionContextServiceMock.createStartContract(),
|
||||
};
|
||||
|
||||
return mock;
|
||||
|
|
|
@ -140,5 +140,6 @@ export function createPluginStartContext<
|
|||
},
|
||||
fatalErrors: deps.fatalErrors,
|
||||
deprecations: deps.deprecations,
|
||||
executionContext: deps.executionContext,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import { CoreSetup, CoreStart, PluginInitializerContext } from '..';
|
|||
import { docLinksServiceMock } from '../doc_links/doc_links_service.mock';
|
||||
import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock';
|
||||
import { deprecationsServiceMock } from '../deprecations/deprecations_service.mock';
|
||||
import { executionContextServiceMock } from '../execution_context/execution_context_service.mock';
|
||||
|
||||
export let mockPluginInitializers: Map<PluginName, MockedPluginInitializer>;
|
||||
|
||||
|
@ -103,6 +104,7 @@ describe('PluginsService', () => {
|
|||
savedObjects: savedObjectsServiceMock.createStartContract(),
|
||||
fatalErrors: fatalErrorsServiceMock.createStartContract(),
|
||||
deprecations: deprecationsServiceMock.createStartContract(),
|
||||
executionContext: executionContextServiceMock.createStartContract(),
|
||||
};
|
||||
mockStartContext = {
|
||||
...mockStartDeps,
|
||||
|
|
|
@ -433,6 +433,8 @@ export interface CoreStart {
|
|||
// (undocumented)
|
||||
docLinks: DocLinksStart;
|
||||
// (undocumented)
|
||||
executionContext: ExecutionContextServiceStart;
|
||||
// (undocumented)
|
||||
fatalErrors: FatalErrorsStart;
|
||||
// (undocumented)
|
||||
http: HttpStart;
|
||||
|
@ -714,6 +716,11 @@ export interface ErrorToastOptions extends ToastOptions {
|
|||
toastMessage?: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ExecutionContextServiceStart {
|
||||
create: (context: KibanaExecutionContext) => IExecutionContextContainer;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface FatalErrorInfo {
|
||||
// (undocumented)
|
||||
|
@ -752,6 +759,8 @@ export class HttpFetchError extends Error implements IHttpFetchError {
|
|||
export interface HttpFetchOptions extends HttpRequestInit {
|
||||
asResponse?: boolean;
|
||||
asSystemRequest?: boolean;
|
||||
// (undocumented)
|
||||
context?: IExecutionContextContainer;
|
||||
headers?: HttpHeadersInit;
|
||||
prependBasePath?: boolean;
|
||||
query?: HttpFetchQuery;
|
||||
|
@ -887,6 +896,12 @@ export interface IBasePath {
|
|||
readonly serverBasePath: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface IExecutionContextContainer {
|
||||
// (undocumented)
|
||||
toHeader: () => Record<string, string>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface IExternalUrl {
|
||||
validateUrl(relativeOrAbsoluteUrl: string): URL | null;
|
||||
|
@ -949,6 +964,15 @@ export interface IUiSettingsClient {
|
|||
set: (key: string, value: any) => Promise<boolean>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface KibanaExecutionContext {
|
||||
readonly description: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly type: string;
|
||||
readonly url?: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type MountPoint<T extends HTMLElement = HTMLElement> = (element: T) => UnmountCallback;
|
||||
|
||||
|
@ -1663,6 +1687,6 @@ export interface UserProvidedValues<T = any> {
|
|||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/core/public/core_system.ts:168:21 - (ae-forgotten-export) The symbol "InternalApplicationStart" needs to be exported by the entry point index.d.ts
|
||||
// src/core/public/core_system.ts:172:21 - (ae-forgotten-export) The symbol "InternalApplicationStart" needs to be exported by the entry point index.d.ts
|
||||
|
||||
```
|
||||
|
|
|
@ -10,6 +10,7 @@ import supertest from 'supertest';
|
|||
import { REPO_ROOT } from '@kbn/dev-utils';
|
||||
import { HttpService, InternalHttpServiceSetup } from '../../http';
|
||||
import { contextServiceMock } from '../../context/context_service.mock';
|
||||
import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock';
|
||||
import { loggingSystemMock } from '../../logging/logging_system.mock';
|
||||
import { Env } from '../../config';
|
||||
import { getEnvOptions } from '../../config/mocks';
|
||||
|
@ -31,6 +32,7 @@ describe('CapabilitiesService', () => {
|
|||
server = createHttpServer();
|
||||
httpSetup = await server.setup({
|
||||
context: contextServiceMock.createSetupContract(),
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
});
|
||||
service = new CapabilitiesService({
|
||||
coreId,
|
||||
|
|
|
@ -10,6 +10,7 @@ import { resolve } from 'path';
|
|||
import { readFile } from 'fs/promises';
|
||||
import supertest from 'supertest';
|
||||
import { contextServiceMock } from '../../context/context_service.mock';
|
||||
import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock';
|
||||
import { loggingSystemMock } from '../../logging/logging_system.mock';
|
||||
import { HttpService, IRouter } from '../../http';
|
||||
import { createHttpServer } from '../../http/test_utils';
|
||||
|
@ -53,6 +54,7 @@ describe('bundle routes', () => {
|
|||
it('serves images inside from the bundle path', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup({
|
||||
context: contextSetup,
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
});
|
||||
|
||||
registerFooPluginRoute(createRouter(''));
|
||||
|
@ -70,6 +72,7 @@ describe('bundle routes', () => {
|
|||
it('serves uncompressed js files', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup({
|
||||
context: contextSetup,
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
});
|
||||
|
||||
registerFooPluginRoute(createRouter(''));
|
||||
|
@ -87,6 +90,7 @@ describe('bundle routes', () => {
|
|||
it('returns 404 for files outside of the bundlePath', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup({
|
||||
context: contextSetup,
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
});
|
||||
|
||||
registerFooPluginRoute(createRouter(''));
|
||||
|
@ -100,6 +104,7 @@ describe('bundle routes', () => {
|
|||
it('returns 404 for non-existing files', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup({
|
||||
context: contextSetup,
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
});
|
||||
|
||||
registerFooPluginRoute(createRouter(''));
|
||||
|
@ -113,6 +118,7 @@ describe('bundle routes', () => {
|
|||
it('returns gzip version if present', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup({
|
||||
context: contextSetup,
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
});
|
||||
|
||||
registerFooPluginRoute(createRouter(''));
|
||||
|
@ -137,6 +143,7 @@ describe('bundle routes', () => {
|
|||
it('uses max-age cache-control', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup({
|
||||
context: contextSetup,
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
});
|
||||
|
||||
registerFooPluginRoute(createRouter(''), { isDist: true });
|
||||
|
@ -155,6 +162,7 @@ describe('bundle routes', () => {
|
|||
it('uses etag cache-control', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup({
|
||||
context: contextSetup,
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
});
|
||||
|
||||
registerFooPluginRoute(createRouter(''), { isDist: false });
|
||||
|
|
|
@ -55,14 +55,19 @@ describe('ClusterClient', () => {
|
|||
|
||||
it('creates a single internal and scoped client during initialization', () => {
|
||||
const config = createConfig();
|
||||
|
||||
new ClusterClient(config, logger, 'custom-type', getAuthHeaders);
|
||||
const getExecutionContextMock = jest.fn();
|
||||
new ClusterClient(config, logger, 'custom-type', getAuthHeaders, getExecutionContextMock);
|
||||
|
||||
expect(configureClientMock).toHaveBeenCalledTimes(2);
|
||||
expect(configureClientMock).toHaveBeenCalledWith(config, { logger, type: 'custom-type' });
|
||||
expect(configureClientMock).toHaveBeenCalledWith(config, {
|
||||
logger,
|
||||
type: 'custom-type',
|
||||
getExecutionContext: getExecutionContextMock,
|
||||
});
|
||||
expect(configureClientMock).toHaveBeenCalledWith(config, {
|
||||
logger,
|
||||
type: 'custom-type',
|
||||
getExecutionContext: getExecutionContextMock,
|
||||
scoped: true,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ import { Client } from '@elastic/elasticsearch';
|
|||
import { Logger } from '../../logging';
|
||||
import { GetAuthHeaders, Headers, isKibanaRequest, isRealRequest } from '../../http';
|
||||
import { ensureRawRequest, filterHeaders } from '../../http/router';
|
||||
import type { IExecutionContextContainer } from '../../execution_context';
|
||||
import { ScopeableRequest } from '../types';
|
||||
import { ElasticsearchClient } from './types';
|
||||
import { configureClient } from './configure_client';
|
||||
|
@ -54,6 +55,7 @@ export interface ICustomClusterClient extends IClusterClient {
|
|||
export class ClusterClient implements ICustomClusterClient {
|
||||
public readonly asInternalUser: Client;
|
||||
private readonly rootScopedClient: Client;
|
||||
private readonly allowListHeaders: string[];
|
||||
|
||||
private isClosed = false;
|
||||
|
||||
|
@ -61,10 +63,18 @@ export class ClusterClient implements ICustomClusterClient {
|
|||
private readonly config: ElasticsearchClientConfig,
|
||||
logger: Logger,
|
||||
type: string,
|
||||
private readonly getAuthHeaders: GetAuthHeaders = noop
|
||||
private readonly getAuthHeaders: GetAuthHeaders = noop,
|
||||
getExecutionContext: () => IExecutionContextContainer | undefined = noop
|
||||
) {
|
||||
this.asInternalUser = configureClient(config, { logger, type });
|
||||
this.rootScopedClient = configureClient(config, { logger, type, scoped: true });
|
||||
this.asInternalUser = configureClient(config, { logger, type, getExecutionContext });
|
||||
this.rootScopedClient = configureClient(config, {
|
||||
logger,
|
||||
type,
|
||||
getExecutionContext,
|
||||
scoped: true,
|
||||
});
|
||||
|
||||
this.allowListHeaders = ['x-opaque-id', ...this.config.requestHeadersWhitelist];
|
||||
}
|
||||
|
||||
asScoped(request: ScopeableRequest) {
|
||||
|
@ -90,10 +100,10 @@ export class ClusterClient implements ICustomClusterClient {
|
|||
const requestIdHeaders = isKibanaRequest(request) ? { 'x-opaque-id': request.id } : {};
|
||||
const authHeaders = this.getAuthHeaders(request);
|
||||
|
||||
scopedHeaders = filterHeaders({ ...requestHeaders, ...requestIdHeaders, ...authHeaders }, [
|
||||
'x-opaque-id',
|
||||
...this.config.requestHeadersWhitelist,
|
||||
]);
|
||||
scopedHeaders = filterHeaders(
|
||||
{ ...requestHeaders, ...requestIdHeaders, ...authHeaders },
|
||||
this.allowListHeaders
|
||||
);
|
||||
} else {
|
||||
scopedHeaders = filterHeaders(request?.headers ?? {}, this.config.requestHeadersWhitelist);
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ describe('configureClient', () => {
|
|||
const client = configureClient(config, { logger, type: 'test', scoped: false });
|
||||
|
||||
expect(ClientMock).toHaveBeenCalledTimes(1);
|
||||
expect(ClientMock).toHaveBeenCalledWith(parsedOptions);
|
||||
expect(ClientMock).toHaveBeenCalledWith(expect.objectContaining(parsedOptions));
|
||||
expect(client).toBe(ClientMock.mock.results[0].value);
|
||||
});
|
||||
|
||||
|
|
|
@ -8,18 +8,46 @@
|
|||
|
||||
import { Buffer } from 'buffer';
|
||||
import { stringify } from 'querystring';
|
||||
import { ApiError, Client, RequestEvent, errors } from '@elastic/elasticsearch';
|
||||
import type { RequestBody } from '@elastic/elasticsearch/lib/Transport';
|
||||
import { ApiError, Client, RequestEvent, errors, Transport } from '@elastic/elasticsearch';
|
||||
import type {
|
||||
RequestBody,
|
||||
TransportRequestParams,
|
||||
TransportRequestOptions,
|
||||
} from '@elastic/elasticsearch/lib/Transport';
|
||||
import type { IExecutionContextContainer } from '../../execution_context';
|
||||
import { Logger } from '../../logging';
|
||||
import { parseClientOptions, ElasticsearchClientConfig } from './client_config';
|
||||
|
||||
const noop = () => undefined;
|
||||
|
||||
export const configureClient = (
|
||||
config: ElasticsearchClientConfig,
|
||||
{ logger, type, scoped = false }: { logger: Logger; type: string; scoped?: boolean }
|
||||
{
|
||||
logger,
|
||||
type,
|
||||
scoped = false,
|
||||
getExecutionContext = noop,
|
||||
}: {
|
||||
logger: Logger;
|
||||
type: string;
|
||||
scoped?: boolean;
|
||||
getExecutionContext?: () => IExecutionContextContainer | undefined;
|
||||
}
|
||||
): Client => {
|
||||
const clientOptions = parseClientOptions(config, scoped);
|
||||
class KibanaTransport extends Transport {
|
||||
request(params: TransportRequestParams, options?: TransportRequestOptions) {
|
||||
const opts = options || {};
|
||||
const opaqueId = getExecutionContext()?.toString();
|
||||
if (opaqueId && !opts.opaqueId) {
|
||||
// rewrites headers['x-opaque-id'] if it presents
|
||||
opts.opaqueId = opaqueId;
|
||||
}
|
||||
return super.request(params, opts);
|
||||
}
|
||||
}
|
||||
|
||||
const client = new Client(clientOptions);
|
||||
const client = new Client({ ...clientOptions, Transport: KibanaTransport });
|
||||
addLogging(client, logger.get('query', type));
|
||||
|
||||
return client;
|
||||
|
|
|
@ -15,6 +15,7 @@ import { configServiceMock, getEnvOptions } from '../config/mocks';
|
|||
import { CoreContext } from '../core_context';
|
||||
import { loggingSystemMock } from '../logging/logging_system.mock';
|
||||
import { httpServiceMock } from '../http/http_service.mock';
|
||||
import { executionContextServiceMock } from '../execution_context/execution_context_service.mock';
|
||||
import { ElasticsearchConfig } from './elasticsearch_config';
|
||||
import { ElasticsearchService } from './elasticsearch_service';
|
||||
import { elasticsearchServiceMock } from './elasticsearch_service.mock';
|
||||
|
@ -28,6 +29,7 @@ let elasticsearchService: ElasticsearchService;
|
|||
const configService = configServiceMock.create();
|
||||
const setupDeps = {
|
||||
http: httpServiceMock.createInternalSetupContract(),
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
};
|
||||
configService.atPath.mockReturnValue(
|
||||
new BehaviorSubject({
|
||||
|
@ -274,12 +276,7 @@ describe('#start', () => {
|
|||
expect(clusterClient).toBe(mockClusterClientInstance);
|
||||
|
||||
expect(MockClusterClient).toHaveBeenCalledTimes(1);
|
||||
expect(MockClusterClient).toHaveBeenCalledWith(
|
||||
expect.objectContaining(customConfig),
|
||||
expect.objectContaining({ context: ['elasticsearch'] }),
|
||||
'custom-type',
|
||||
expect.any(Function)
|
||||
);
|
||||
expect(MockClusterClient.mock.calls[0][0]).toEqual(expect.objectContaining(customConfig));
|
||||
});
|
||||
it('creates a new client on each call', async () => {
|
||||
await elasticsearchService.setup(setupDeps);
|
||||
|
|
|
@ -20,13 +20,15 @@ import {
|
|||
} from './legacy';
|
||||
import { ClusterClient, ICustomClusterClient, ElasticsearchClientConfig } from './client';
|
||||
import { ElasticsearchConfig, ElasticsearchConfigType } from './elasticsearch_config';
|
||||
import { InternalHttpServiceSetup, GetAuthHeaders } from '../http/';
|
||||
import type { InternalHttpServiceSetup, GetAuthHeaders } from '../http/';
|
||||
import type { InternalExecutionContextSetup, IExecutionContext } from '../execution_context';
|
||||
import { InternalElasticsearchServiceSetup, InternalElasticsearchServiceStart } from './types';
|
||||
import { pollEsNodesVersion } from './version_check/ensure_es_version';
|
||||
import { calculateStatus$ } from './status';
|
||||
|
||||
interface SetupDeps {
|
||||
http: InternalHttpServiceSetup;
|
||||
executionContext: InternalExecutionContextSetup;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -37,6 +39,7 @@ export class ElasticsearchService
|
|||
private stop$ = new Subject();
|
||||
private kibanaVersion: string;
|
||||
private getAuthHeaders?: GetAuthHeaders;
|
||||
private executionContextClient?: IExecutionContext;
|
||||
|
||||
private createLegacyCustomClient?: (
|
||||
type: string,
|
||||
|
@ -60,6 +63,7 @@ export class ElasticsearchService
|
|||
const config = await this.config$.pipe(first()).toPromise();
|
||||
|
||||
this.getAuthHeaders = deps.http.getAuthHeaders;
|
||||
this.executionContextClient = deps.executionContext;
|
||||
this.legacyClient = this.createLegacyClusterClient('data', config);
|
||||
this.client = this.createClusterClient('data', config);
|
||||
|
||||
|
@ -128,7 +132,8 @@ export class ElasticsearchService
|
|||
config,
|
||||
this.coreContext.logger.get('elasticsearch'),
|
||||
type,
|
||||
this.getAuthHeaders
|
||||
this.getAuthHeaders,
|
||||
() => this.executionContextClient?.get()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { TypeOf, schema } from '@kbn/config-schema';
|
||||
import { ServiceConfigDescriptor } from '../internal_types';
|
||||
|
||||
const configSchema = schema.object({
|
||||
enabled: schema.boolean({ defaultValue: true }),
|
||||
});
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type ExecutionContextConfigType = TypeOf<typeof configSchema>;
|
||||
|
||||
export const config: ServiceConfigDescriptor<ExecutionContextConfigType> = {
|
||||
path: 'execution_context',
|
||||
schema: configSchema,
|
||||
};
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { KibanaServerExecutionContext } from './execution_context_service';
|
||||
import {
|
||||
ExecutionContextContainer,
|
||||
getParentContextFrom,
|
||||
BAGGAGE_HEADER,
|
||||
BAGGAGE_MAX_PER_NAME_VALUE_PAIRS,
|
||||
} from './execution_context_container';
|
||||
|
||||
describe('KibanaExecutionContext', () => {
|
||||
describe('toString', () => {
|
||||
it('returns a string representation of provided execution context', () => {
|
||||
const context: KibanaServerExecutionContext = {
|
||||
type: 'test-type',
|
||||
name: 'test-name',
|
||||
id: '42',
|
||||
description: 'test-descripton',
|
||||
requestId: '1234-5678',
|
||||
};
|
||||
|
||||
const value = new ExecutionContextContainer(context).toString();
|
||||
expect(value).toMatchInlineSnapshot(`"1234-5678;kibana:test-type:42"`);
|
||||
});
|
||||
|
||||
it('returns a limited representation if optional properties are omitted', () => {
|
||||
const context: KibanaServerExecutionContext = {
|
||||
requestId: '1234-5678',
|
||||
};
|
||||
|
||||
const value = new ExecutionContextContainer(context).toString();
|
||||
expect(value).toMatchInlineSnapshot(`"1234-5678"`);
|
||||
});
|
||||
|
||||
it('trims a string representation of provided execution context if it is bigger max allowed size', () => {
|
||||
expect(
|
||||
new Blob([
|
||||
new ExecutionContextContainer({
|
||||
requestId: '1234-5678'.repeat(1000),
|
||||
}).toString(),
|
||||
]).size
|
||||
).toBeLessThanOrEqual(BAGGAGE_MAX_PER_NAME_VALUE_PAIRS);
|
||||
|
||||
expect(
|
||||
new Blob([
|
||||
new ExecutionContextContainer({
|
||||
type: 'test-type'.repeat(1000),
|
||||
name: 'test-name',
|
||||
id: '42'.repeat(1000),
|
||||
description: 'test-descripton',
|
||||
requestId: '1234-5678',
|
||||
}).toString(),
|
||||
]).size
|
||||
).toBeLessThanOrEqual(BAGGAGE_MAX_PER_NAME_VALUE_PAIRS);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toJSON', () => {
|
||||
it('returns a context object', () => {
|
||||
const context: KibanaServerExecutionContext = {
|
||||
type: 'test-type',
|
||||
name: 'test-name',
|
||||
id: '42',
|
||||
description: 'test-descripton',
|
||||
requestId: '1234-5678',
|
||||
};
|
||||
|
||||
const value = new ExecutionContextContainer(context).toJSON();
|
||||
expect(value).toBe(context);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getParentContextFrom', () => {
|
||||
it('decodes provided header', () => {
|
||||
const ctx = { id: '42' };
|
||||
const header = encodeURIComponent(JSON.stringify(ctx));
|
||||
expect(getParentContextFrom({ [BAGGAGE_HEADER]: header })).toEqual(ctx);
|
||||
});
|
||||
|
||||
it('does not throw an exception if given not a valid value', () => {
|
||||
expect(getParentContextFrom({ [BAGGAGE_HEADER]: 'value' })).toBeUndefined();
|
||||
expect(getParentContextFrom({ [BAGGAGE_HEADER]: '' })).toBeUndefined();
|
||||
expect(getParentContextFrom({})).toBeUndefined();
|
||||
|
||||
const ctx = { id: '42' };
|
||||
const header = encodeURIComponent(JSON.stringify(ctx));
|
||||
expect(getParentContextFrom({ [BAGGAGE_HEADER]: header.slice(0, -2) })).toBeUndefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import type { KibanaServerExecutionContext } from './execution_context_service';
|
||||
import type { KibanaExecutionContext } from '../../types';
|
||||
|
||||
// Switch to the standard Baggage header. blocked by
|
||||
// https://github.com/elastic/apm-agent-nodejs/issues/2102
|
||||
export const BAGGAGE_HEADER = 'x-kbn-context';
|
||||
|
||||
export function getParentContextFrom(
|
||||
headers: Record<string, string>
|
||||
): KibanaExecutionContext | undefined {
|
||||
const header = headers[BAGGAGE_HEADER];
|
||||
return parseHeader(header);
|
||||
}
|
||||
|
||||
function parseHeader(header?: string): KibanaExecutionContext | undefined {
|
||||
if (!header) return undefined;
|
||||
try {
|
||||
return JSON.parse(decodeURIComponent(header));
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Maximum number of bytes per a single name-value pair allowed by w3c spec
|
||||
// https://w3c.github.io/baggage/
|
||||
export const BAGGAGE_MAX_PER_NAME_VALUE_PAIRS = 4096;
|
||||
|
||||
// a single character can use up to 4 bytes
|
||||
const MAX_BAGGAGE_LENGTH = BAGGAGE_MAX_PER_NAME_VALUE_PAIRS / 4;
|
||||
|
||||
// Limits the header value to max allowed "baggage" header property name-value pair
|
||||
// It will help us switch to the "baggage" header when it becomes the standard.
|
||||
// The trimmed value in the logs is better than nothing.
|
||||
function enforceMaxLength(header: string): string {
|
||||
return header.slice(0, MAX_BAGGAGE_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface IExecutionContextContainer {
|
||||
toString(): string;
|
||||
toJSON(): Readonly<KibanaServerExecutionContext>;
|
||||
}
|
||||
|
||||
export class ExecutionContextContainer implements IExecutionContextContainer {
|
||||
readonly #context: Readonly<KibanaServerExecutionContext>;
|
||||
constructor(context: Readonly<KibanaServerExecutionContext>) {
|
||||
this.#context = context;
|
||||
}
|
||||
toString(): string {
|
||||
const ctx = this.#context;
|
||||
const contextStringified = ctx.type && ctx.id ? `kibana:${ctx.type}:${ctx.id}` : '';
|
||||
const result = contextStringified ? `${ctx.requestId};${contextStringified}` : ctx.requestId;
|
||||
return enforceMaxLength(result);
|
||||
}
|
||||
toJSON(): Readonly<KibanaServerExecutionContext> {
|
||||
return this.#context;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type {
|
||||
IExecutionContext,
|
||||
InternalExecutionContextSetup,
|
||||
ExecutionContextSetup,
|
||||
} from './execution_context_service';
|
||||
|
||||
const createExecutionContextMock = () => {
|
||||
const mock: jest.Mocked<IExecutionContext> = {
|
||||
set: jest.fn(),
|
||||
reset: jest.fn(),
|
||||
get: jest.fn(),
|
||||
getParentContextFrom: jest.fn(),
|
||||
};
|
||||
return mock;
|
||||
};
|
||||
const createInternalSetupContractMock = () => {
|
||||
const setupContract: jest.Mocked<InternalExecutionContextSetup> = createExecutionContextMock();
|
||||
return setupContract;
|
||||
};
|
||||
|
||||
const createSetupContractMock = () => {
|
||||
const mock: jest.Mocked<ExecutionContextSetup> = {
|
||||
set: jest.fn(),
|
||||
get: jest.fn(),
|
||||
};
|
||||
return mock;
|
||||
};
|
||||
|
||||
export const executionContextServiceMock = {
|
||||
createInternalSetupContract: createInternalSetupContractMock,
|
||||
createInternalStartContract: createInternalSetupContractMock,
|
||||
createSetupContract: createSetupContractMock,
|
||||
createStartContract: createSetupContractMock,
|
||||
};
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import {
|
||||
ExecutionContextService,
|
||||
InternalExecutionContextSetup,
|
||||
} from './execution_context_service';
|
||||
import { mockCoreContext } from '../core_context.mock';
|
||||
|
||||
const delay = (ms: number = 100) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
describe('ExecutionContextService', () => {
|
||||
describe('setup', () => {
|
||||
let service: InternalExecutionContextSetup;
|
||||
const core = mockCoreContext.create();
|
||||
core.configService.atPath.mockReturnValue(new BehaviorSubject({ enabled: true }));
|
||||
beforeEach(() => {
|
||||
service = new ExecutionContextService(core).setup();
|
||||
});
|
||||
|
||||
it('sets and gets a value in async context', async () => {
|
||||
const chainA = Promise.resolve().then(async () => {
|
||||
service.set({
|
||||
requestId: '0000',
|
||||
});
|
||||
await delay(500);
|
||||
return service.get();
|
||||
});
|
||||
|
||||
const chainB = Promise.resolve().then(async () => {
|
||||
service.set({
|
||||
requestId: '1111',
|
||||
});
|
||||
await delay(100);
|
||||
return service.get();
|
||||
});
|
||||
|
||||
expect(
|
||||
await Promise.all([chainA, chainB]).then((results) =>
|
||||
results.map((result) => result?.toJSON())
|
||||
)
|
||||
).toEqual([
|
||||
{
|
||||
requestId: '0000',
|
||||
},
|
||||
{
|
||||
requestId: '1111',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('sets and resets a value in async context', async () => {
|
||||
const chainA = Promise.resolve().then(async () => {
|
||||
service.set({
|
||||
requestId: '0000',
|
||||
});
|
||||
await delay(500);
|
||||
service.reset();
|
||||
return service.get();
|
||||
});
|
||||
|
||||
const chainB = Promise.resolve().then(async () => {
|
||||
service.set({
|
||||
requestId: '1111',
|
||||
});
|
||||
await delay(100);
|
||||
return service.get();
|
||||
});
|
||||
|
||||
expect(
|
||||
await Promise.all([chainA, chainB]).then((results) =>
|
||||
results.map((result) => result?.toJSON())
|
||||
)
|
||||
).toEqual([
|
||||
undefined,
|
||||
{
|
||||
requestId: '1111',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('config', () => {
|
||||
it('can be disabled', async () => {
|
||||
const core = mockCoreContext.create();
|
||||
core.configService.atPath.mockReturnValue(new BehaviorSubject({ enabled: false }));
|
||||
const service = new ExecutionContextService(core).setup();
|
||||
const chainA = await Promise.resolve().then(async () => {
|
||||
service.set({
|
||||
requestId: '0000',
|
||||
});
|
||||
await delay(100);
|
||||
return service.get();
|
||||
});
|
||||
|
||||
expect(chainA).toBeUndefined();
|
||||
});
|
||||
|
||||
it('reacts to config changes', async () => {
|
||||
const core = mockCoreContext.create();
|
||||
const config$ = new BehaviorSubject({ enabled: false });
|
||||
core.configService.atPath.mockReturnValue(config$);
|
||||
const service = new ExecutionContextService(core).setup();
|
||||
function exec() {
|
||||
return Promise.resolve().then(async () => {
|
||||
service.set({
|
||||
requestId: '0000',
|
||||
});
|
||||
await delay(100);
|
||||
return service.get();
|
||||
});
|
||||
}
|
||||
expect(await exec()).toBeUndefined();
|
||||
|
||||
config$.next({
|
||||
enabled: true,
|
||||
});
|
||||
expect(await exec()).toBeDefined();
|
||||
|
||||
config$.next({
|
||||
enabled: false,
|
||||
});
|
||||
|
||||
expect(await exec()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
135
src/core/server/execution_context/execution_context_service.ts
Normal file
135
src/core/server/execution_context/execution_context_service.ts
Normal file
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { AsyncLocalStorage } from 'async_hooks';
|
||||
import type { Subscription } from 'rxjs';
|
||||
|
||||
import type { CoreService, KibanaExecutionContext } from '../../types';
|
||||
import type { CoreContext } from '../core_context';
|
||||
import type { Logger } from '../logging';
|
||||
import type { ExecutionContextConfigType } from './execution_context_config';
|
||||
|
||||
import {
|
||||
ExecutionContextContainer,
|
||||
IExecutionContextContainer,
|
||||
getParentContextFrom,
|
||||
} from './execution_context_container';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface KibanaServerExecutionContext extends Partial<KibanaExecutionContext> {
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface IExecutionContext {
|
||||
getParentContextFrom(headers: Record<string, string>): KibanaExecutionContext | undefined;
|
||||
set(context: Partial<KibanaServerExecutionContext>): void;
|
||||
reset(): void;
|
||||
get(): IExecutionContextContainer | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type InternalExecutionContextSetup = IExecutionContext;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export type InternalExecutionContextStart = IExecutionContext;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ExecutionContextSetup {
|
||||
/**
|
||||
* Stores the meta-data of a runtime operation.
|
||||
* Data are carried over all async operations automatically.
|
||||
* The sequential calls merge provided "context" object shallowly.
|
||||
**/
|
||||
set(context: Partial<KibanaServerExecutionContext>): void;
|
||||
/**
|
||||
* Retrieves an opearation meta-data for the current async context.
|
||||
**/
|
||||
get(): IExecutionContextContainer | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type ExecutionContextStart = ExecutionContextSetup;
|
||||
|
||||
export class ExecutionContextService
|
||||
implements CoreService<InternalExecutionContextSetup, InternalExecutionContextStart> {
|
||||
private readonly log: Logger;
|
||||
private readonly asyncLocalStorage: AsyncLocalStorage<IExecutionContextContainer>;
|
||||
private enabled = false;
|
||||
private configSubscription?: Subscription;
|
||||
|
||||
constructor(private readonly coreContext: CoreContext) {
|
||||
this.log = coreContext.logger.get('execution_context');
|
||||
this.asyncLocalStorage = new AsyncLocalStorage<IExecutionContextContainer>();
|
||||
}
|
||||
|
||||
setup(): InternalExecutionContextSetup {
|
||||
this.configSubscription = this.coreContext.configService
|
||||
.atPath<ExecutionContextConfigType>('execution_context')
|
||||
.subscribe((config) => {
|
||||
this.enabled = config.enabled;
|
||||
});
|
||||
|
||||
return {
|
||||
getParentContextFrom,
|
||||
set: this.set.bind(this),
|
||||
reset: this.reset.bind(this),
|
||||
get: this.get.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
start(): InternalExecutionContextStart {
|
||||
return {
|
||||
getParentContextFrom,
|
||||
set: this.set.bind(this),
|
||||
reset: this.reset.bind(this),
|
||||
get: this.get.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.enabled = false;
|
||||
if (this.configSubscription) {
|
||||
this.configSubscription.unsubscribe();
|
||||
this.configSubscription = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private set(context: KibanaServerExecutionContext) {
|
||||
if (!this.enabled) return;
|
||||
const prevValue = this.asyncLocalStorage.getStore();
|
||||
// merges context objects shallowly. repeats the deafult logic of apm.setCustomContext(ctx)
|
||||
const contextContainer = new ExecutionContextContainer({ ...prevValue?.toJSON(), ...context });
|
||||
// we have to use enterWith since Hapi lifecycle model is built on event emitters.
|
||||
// therefore if we wrapped request handler in asyncLocalStorage.run(), we would lose context in other lifecycles.
|
||||
this.asyncLocalStorage.enterWith(contextContainer);
|
||||
this.log.trace(`stored the execution context: ${contextContainer.toJSON()}`);
|
||||
}
|
||||
|
||||
private reset() {
|
||||
if (!this.enabled) return;
|
||||
// @ts-expect-error "undefined" is not supported in type definitions, which is wrong
|
||||
this.asyncLocalStorage.enterWith(undefined);
|
||||
}
|
||||
|
||||
private get(): IExecutionContextContainer | undefined {
|
||||
if (!this.enabled) return;
|
||||
return this.asyncLocalStorage.getStore();
|
||||
}
|
||||
}
|
20
src/core/server/execution_context/index.ts
Normal file
20
src/core/server/execution_context/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export type { KibanaExecutionContext } from '../../types';
|
||||
export { ExecutionContextService } from './execution_context_service';
|
||||
export type {
|
||||
InternalExecutionContextSetup,
|
||||
InternalExecutionContextStart,
|
||||
ExecutionContextSetup,
|
||||
ExecutionContextStart,
|
||||
IExecutionContext,
|
||||
KibanaServerExecutionContext,
|
||||
} from './execution_context_service';
|
||||
export type { IExecutionContextContainer } from './execution_context_container';
|
||||
export { config } from './execution_context_config';
|
|
@ -0,0 +1,542 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { ExecutionContextContainer } from '../../../public/execution_context/execution_context_container';
|
||||
import * as kbnTestServer from '../../../test_helpers/kbn_server';
|
||||
|
||||
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
const parentContext = {
|
||||
type: 'test-type',
|
||||
name: 'test-name',
|
||||
id: '42',
|
||||
description: 'test-description',
|
||||
};
|
||||
|
||||
describe('trace', () => {
|
||||
let esServer: kbnTestServer.TestElasticsearchUtils;
|
||||
let root: ReturnType<typeof kbnTestServer.createRoot>;
|
||||
beforeAll(async () => {
|
||||
const { startES } = kbnTestServer.createTestServers({
|
||||
adjustTimeout: jest.setTimeout,
|
||||
});
|
||||
esServer = await startES();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await esServer.stop();
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
root = kbnTestServer.createRootWithCorePlugins({
|
||||
plugins: { initialize: false },
|
||||
server: {
|
||||
requestId: {
|
||||
allowFromAnyIp: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}, 30000);
|
||||
|
||||
afterEach(async () => {
|
||||
await root.shutdown();
|
||||
});
|
||||
|
||||
describe('x-opaque-id', () => {
|
||||
it('passed to Elasticsearch unscoped client calls', async () => {
|
||||
const { http } = await root.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
const { headers } = await context.core.elasticsearch.client.asInternalUser.ping();
|
||||
return res.ok({ body: headers || {} });
|
||||
});
|
||||
|
||||
await root.start();
|
||||
|
||||
const myOpaqueId = 'my-opaque-id';
|
||||
const response = await kbnTestServer.request
|
||||
.get(root, '/execution-context')
|
||||
.set('x-opaque-id', myOpaqueId)
|
||||
.expect(200);
|
||||
|
||||
const header = response.body['x-opaque-id'];
|
||||
expect(header).toBe(myOpaqueId);
|
||||
});
|
||||
|
||||
it('passed to Elasticsearch scoped client calls', async () => {
|
||||
const { http } = await root.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
|
||||
return res.ok({ body: headers || {} });
|
||||
});
|
||||
|
||||
await root.start();
|
||||
|
||||
const myOpaqueId = 'my-opaque-id';
|
||||
const response = await kbnTestServer.request
|
||||
.get(root, '/execution-context')
|
||||
.set('x-opaque-id', myOpaqueId)
|
||||
.expect(200);
|
||||
|
||||
const header = response.body['x-opaque-id'];
|
||||
expect(header).toBe(myOpaqueId);
|
||||
});
|
||||
|
||||
it('generated and attached to Elasticsearch unscoped client calls if not specifed', async () => {
|
||||
const { http } = await root.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
const { headers } = await context.core.elasticsearch.client.asInternalUser.ping();
|
||||
return res.ok({ body: headers || {} });
|
||||
});
|
||||
|
||||
await root.start();
|
||||
|
||||
const response = await kbnTestServer.request.get(root, '/execution-context').expect(200);
|
||||
|
||||
const header = response.body['x-opaque-id'];
|
||||
expect(header).toEqual(expect.any(String));
|
||||
});
|
||||
|
||||
it('generated and attached to Elasticsearch scoped client calls if not specifed', async () => {
|
||||
const { http } = await root.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
|
||||
return res.ok({ body: headers || {} });
|
||||
});
|
||||
|
||||
await root.start();
|
||||
|
||||
const response = await kbnTestServer.request.get(root, '/execution-context').expect(200);
|
||||
|
||||
const header = response.body['x-opaque-id'];
|
||||
expect(header).toEqual(expect.any(String));
|
||||
});
|
||||
|
||||
it('can be overriden during Elasticsearch client call', async () => {
|
||||
const { http } = await root.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
const { headers } = await context.core.elasticsearch.client.asInternalUser.ping(
|
||||
{},
|
||||
{
|
||||
opaqueId: 'new-opaque-id',
|
||||
}
|
||||
);
|
||||
return res.ok({ body: headers || {} });
|
||||
});
|
||||
|
||||
await root.start();
|
||||
|
||||
const myOpaqueId = 'my-opaque-id';
|
||||
const response = await kbnTestServer.request
|
||||
.get(root, '/execution-context')
|
||||
.set('x-opaque-id', myOpaqueId)
|
||||
.expect(200);
|
||||
|
||||
const header = response.body['x-opaque-id'];
|
||||
expect(header).toBe('new-opaque-id');
|
||||
});
|
||||
|
||||
describe('ExecutionContext Service is disabled', () => {
|
||||
let rootExecutionContextDisabled: ReturnType<typeof kbnTestServer.createRoot>;
|
||||
beforeEach(async () => {
|
||||
rootExecutionContextDisabled = kbnTestServer.createRootWithCorePlugins({
|
||||
execution_context: { enabled: false },
|
||||
plugins: { initialize: false },
|
||||
server: {
|
||||
requestId: {
|
||||
allowFromAnyIp: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}, 30000);
|
||||
|
||||
afterEach(async () => {
|
||||
await rootExecutionContextDisabled.shutdown();
|
||||
});
|
||||
it('passed to Elasticsearch scoped client calls even if ExecutionContext Service is disabled', async () => {
|
||||
const { http } = await rootExecutionContextDisabled.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
|
||||
return res.ok({ body: headers || {} });
|
||||
});
|
||||
|
||||
await rootExecutionContextDisabled.start();
|
||||
|
||||
const myOpaqueId = 'my-opaque-id';
|
||||
const response = await kbnTestServer.request
|
||||
.get(rootExecutionContextDisabled, '/execution-context')
|
||||
.set('x-opaque-id', myOpaqueId)
|
||||
.expect(200);
|
||||
|
||||
const header = response.body['x-opaque-id'];
|
||||
expect(header).toBe(myOpaqueId);
|
||||
});
|
||||
|
||||
it('does not pass context if ExecutionContext Service is disabled', async () => {
|
||||
const { http, executionContext } = await rootExecutionContextDisabled.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
executionContext.set(parentContext);
|
||||
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
|
||||
return res.ok({
|
||||
body: { context: executionContext.get()?.toJSON(), header: headers?.['x-opaque-id'] },
|
||||
});
|
||||
});
|
||||
|
||||
await rootExecutionContextDisabled.start();
|
||||
|
||||
const myOpaqueId = 'my-opaque-id';
|
||||
const response = await kbnTestServer.request
|
||||
.get(rootExecutionContextDisabled, '/execution-context')
|
||||
.set('x-opaque-id', myOpaqueId)
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).toEqual({
|
||||
header: 'my-opaque-id',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('execution context', () => {
|
||||
it('sets execution context for a sync request handler', async () => {
|
||||
const { executionContext, http } = await root.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
executionContext.set(parentContext);
|
||||
return res.ok({ body: executionContext.get() });
|
||||
});
|
||||
|
||||
await root.start();
|
||||
const response = await kbnTestServer.request.get(root, '/execution-context').expect(200);
|
||||
expect(response.body).toEqual({ ...parentContext, requestId: expect.any(String) });
|
||||
});
|
||||
|
||||
it('sets execution context for an async request handler', async () => {
|
||||
const { executionContext, http } = await root.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
executionContext.set(parentContext);
|
||||
await delay(100);
|
||||
return res.ok({ body: executionContext.get() });
|
||||
});
|
||||
|
||||
await root.start();
|
||||
const response = await kbnTestServer.request.get(root, '/execution-context').expect(200);
|
||||
expect(response.body).toEqual({ ...parentContext, requestId: expect.any(String) });
|
||||
});
|
||||
|
||||
it('execution context is uniq for sequential requests', async () => {
|
||||
const { executionContext, http } = await root.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
executionContext.set(parentContext);
|
||||
await delay(100);
|
||||
return res.ok({ body: executionContext.get() });
|
||||
});
|
||||
|
||||
await root.start();
|
||||
const responseA = await kbnTestServer.request.get(root, '/execution-context').expect(200);
|
||||
const responseB = await kbnTestServer.request.get(root, '/execution-context').expect(200);
|
||||
|
||||
expect(responseA.body).toEqual({ ...parentContext, requestId: expect.any(String) });
|
||||
expect(responseB.body).toEqual({ ...parentContext, requestId: expect.any(String) });
|
||||
expect(responseA.body.requestId).not.toBe(responseB.body.requestId);
|
||||
});
|
||||
|
||||
it('execution context is uniq for concurrent requests', async () => {
|
||||
const { executionContext, http } = await root.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
let id = 2;
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
executionContext.set(parentContext);
|
||||
await delay(id-- * 100);
|
||||
return res.ok({ body: executionContext.get() });
|
||||
});
|
||||
|
||||
await root.start();
|
||||
const responseA = kbnTestServer.request.get(root, '/execution-context');
|
||||
const responseB = kbnTestServer.request.get(root, '/execution-context');
|
||||
const responseC = kbnTestServer.request.get(root, '/execution-context');
|
||||
|
||||
const [{ body: bodyA }, { body: bodyB }, { body: bodyC }] = await Promise.all([
|
||||
responseA,
|
||||
responseB,
|
||||
responseC,
|
||||
]);
|
||||
expect(bodyA.requestId).toBeDefined();
|
||||
expect(bodyB.requestId).toBeDefined();
|
||||
expect(bodyC.requestId).toBeDefined();
|
||||
|
||||
expect(bodyA.requestId).not.toBe(bodyB.requestId);
|
||||
expect(bodyB.requestId).not.toBe(bodyC.requestId);
|
||||
expect(bodyA.requestId).not.toBe(bodyC.requestId);
|
||||
});
|
||||
|
||||
it('execution context is uniq for concurrent requests when "x-opaque-id" provided', async () => {
|
||||
const { executionContext, http } = await root.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
let id = 2;
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
executionContext.set(parentContext);
|
||||
await delay(id-- * 100);
|
||||
return res.ok({ body: executionContext.get() });
|
||||
});
|
||||
|
||||
await root.start();
|
||||
const responseA = kbnTestServer.request
|
||||
.get(root, '/execution-context')
|
||||
.set('x-opaque-id', 'req-1');
|
||||
const responseB = kbnTestServer.request
|
||||
.get(root, '/execution-context')
|
||||
.set('x-opaque-id', 'req-2');
|
||||
const responseC = kbnTestServer.request
|
||||
.get(root, '/execution-context')
|
||||
.set('x-opaque-id', 'req-3');
|
||||
|
||||
const [{ body: bodyA }, { body: bodyB }, { body: bodyC }] = await Promise.all([
|
||||
responseA,
|
||||
responseB,
|
||||
responseC,
|
||||
]);
|
||||
expect(bodyA.requestId).toBe('req-1');
|
||||
expect(bodyB.requestId).toBe('req-2');
|
||||
expect(bodyC.requestId).toBe('req-3');
|
||||
});
|
||||
|
||||
it('parses the parent context if present', async () => {
|
||||
const { executionContext, http } = await root.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
router.get({ path: '/execution-context', validate: false }, (context, req, res) =>
|
||||
res.ok({ body: executionContext.get() })
|
||||
);
|
||||
|
||||
await root.start();
|
||||
const response = await kbnTestServer.request
|
||||
.get(root, '/execution-context')
|
||||
.set(new ExecutionContextContainer(parentContext).toHeader())
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).toEqual({ ...parentContext, requestId: expect.any(String) });
|
||||
});
|
||||
|
||||
it('execution context is the same for all the lifecycle events', async () => {
|
||||
const { executionContext, http } = await root.setup();
|
||||
const {
|
||||
createRouter,
|
||||
registerOnPreRouting,
|
||||
registerOnPreAuth,
|
||||
registerAuth,
|
||||
registerOnPostAuth,
|
||||
registerOnPreResponse,
|
||||
} = http;
|
||||
|
||||
const router = createRouter('');
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
return res.ok({ body: executionContext.get()?.toJSON() });
|
||||
});
|
||||
|
||||
let onPreRoutingContext;
|
||||
registerOnPreRouting((request, response, t) => {
|
||||
onPreRoutingContext = executionContext.get()?.toJSON();
|
||||
return t.next();
|
||||
});
|
||||
|
||||
let onPreAuthContext;
|
||||
registerOnPreAuth((request, response, t) => {
|
||||
onPreAuthContext = executionContext.get()?.toJSON();
|
||||
return t.next();
|
||||
});
|
||||
|
||||
let authContext;
|
||||
registerAuth((request, response, t) => {
|
||||
authContext = executionContext.get()?.toJSON();
|
||||
return t.authenticated();
|
||||
});
|
||||
|
||||
let onPostAuthContext;
|
||||
registerOnPostAuth((request, response, t) => {
|
||||
onPostAuthContext = executionContext.get()?.toJSON();
|
||||
return t.next();
|
||||
});
|
||||
|
||||
let onPreResponseContext;
|
||||
registerOnPreResponse((request, response, t) => {
|
||||
onPreResponseContext = executionContext.get()?.toJSON();
|
||||
return t.next();
|
||||
});
|
||||
|
||||
await root.start();
|
||||
const response = await kbnTestServer.request
|
||||
.get(root, '/execution-context')
|
||||
.set(new ExecutionContextContainer(parentContext).toHeader())
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).toEqual({ ...parentContext, requestId: expect.any(String) });
|
||||
|
||||
expect(response.body).toEqual(onPreRoutingContext);
|
||||
expect(response.body).toEqual(onPreAuthContext);
|
||||
expect(response.body).toEqual(authContext);
|
||||
expect(response.body).toEqual(onPostAuthContext);
|
||||
expect(response.body).toEqual(onPreResponseContext);
|
||||
});
|
||||
|
||||
it('propagates context to Elasticsearch scoped client', async () => {
|
||||
const { http } = await root.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
|
||||
return res.ok({ body: headers || {} });
|
||||
});
|
||||
|
||||
await root.start();
|
||||
|
||||
const response = await kbnTestServer.request
|
||||
.get(root, '/execution-context')
|
||||
.set(new ExecutionContextContainer(parentContext).toHeader())
|
||||
.expect(200);
|
||||
|
||||
const header = response.body['x-opaque-id'];
|
||||
expect(header).toContain('kibana:test-type:42');
|
||||
});
|
||||
|
||||
it('propagates context to Elasticsearch unscoped client', async () => {
|
||||
const { http } = await root.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
const { headers } = await context.core.elasticsearch.client.asInternalUser.ping();
|
||||
return res.ok({ body: headers || {} });
|
||||
});
|
||||
|
||||
await root.start();
|
||||
|
||||
const response = await kbnTestServer.request
|
||||
.get(root, '/execution-context')
|
||||
.set(new ExecutionContextContainer(parentContext).toHeader())
|
||||
.expect(200);
|
||||
|
||||
const header = response.body['x-opaque-id'];
|
||||
expect(header).toContain('kibana:test-type:42');
|
||||
});
|
||||
|
||||
it('a repeat call overwrites the old context', async () => {
|
||||
const { http, executionContext } = await root.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
const newContext = {
|
||||
type: 'new-type',
|
||||
name: 'new-name',
|
||||
id: '41',
|
||||
description: 'new-description',
|
||||
};
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
executionContext.set(newContext);
|
||||
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
|
||||
return res.ok({ body: headers || {} });
|
||||
});
|
||||
|
||||
await root.start();
|
||||
|
||||
const response = await kbnTestServer.request
|
||||
.get(root, '/execution-context')
|
||||
.set(new ExecutionContextContainer(parentContext).toHeader())
|
||||
.expect(200);
|
||||
|
||||
const header = response.body['x-opaque-id'];
|
||||
expect(header).toContain('kibana:new-type:41');
|
||||
});
|
||||
|
||||
it('does not affect "x-opaque-id" set by user', async () => {
|
||||
const { http, executionContext } = await root.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
executionContext.set(parentContext);
|
||||
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
|
||||
return res.ok({ body: headers || {} });
|
||||
});
|
||||
|
||||
await root.start();
|
||||
|
||||
const myOpaqueId = 'my-opaque-id';
|
||||
const response = await kbnTestServer.request
|
||||
.get(root, '/execution-context')
|
||||
.set('x-opaque-id', myOpaqueId)
|
||||
.expect(200);
|
||||
|
||||
const header = response.body['x-opaque-id'];
|
||||
expect(header).toBe('my-opaque-id;kibana:test-type:42');
|
||||
});
|
||||
|
||||
it('does not break on non-ASCII characters within execution context', async () => {
|
||||
const { http, executionContext } = await root.setup();
|
||||
const { createRouter } = http;
|
||||
|
||||
const router = createRouter('');
|
||||
const ctx = {
|
||||
type: 'test-type',
|
||||
name: 'test-name',
|
||||
id: '42',
|
||||
description: 'какое-то описание',
|
||||
};
|
||||
router.get({ path: '/execution-context', validate: false }, async (context, req, res) => {
|
||||
executionContext.set(ctx);
|
||||
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
|
||||
return res.ok({ body: headers || {} });
|
||||
});
|
||||
|
||||
await root.start();
|
||||
|
||||
const myOpaqueId = 'my-opaque-id';
|
||||
const response = await kbnTestServer.request
|
||||
.get(root, '/execution-context')
|
||||
.set('x-opaque-id', myOpaqueId)
|
||||
.expect(200);
|
||||
|
||||
const header = response.body['x-opaque-id'];
|
||||
expect(header).toBe('my-opaque-id;kibana:test-type:42');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -18,6 +18,7 @@ import { KibanaRequest } from './router';
|
|||
import { Env } from '../config';
|
||||
|
||||
import { contextServiceMock } from '../context/context_service.mock';
|
||||
import { executionContextServiceMock } from '../execution_context/execution_context_service.mock';
|
||||
import { loggingSystemMock } from '../logging/logging_system.mock';
|
||||
import { getEnvOptions, configServiceMock } from '../config/mocks';
|
||||
import { httpServerMock } from './http_server.mocks';
|
||||
|
@ -34,6 +35,7 @@ const contextSetup = contextServiceMock.createSetupContract();
|
|||
|
||||
const setupDeps = {
|
||||
context: contextSetup,
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
};
|
||||
|
||||
configService.atPath.mockImplementation((path) => {
|
||||
|
|
|
@ -22,6 +22,7 @@ import { Observable } from 'rxjs';
|
|||
import { take } from 'rxjs/operators';
|
||||
import { Logger, LoggerFactory } from '../logging';
|
||||
import { HttpConfig } from './http_config';
|
||||
import type { InternalExecutionContextSetup } from '../execution_context';
|
||||
import { adoptToHapiAuthFormat, AuthenticationHandler } from './lifecycle/auth';
|
||||
import { adoptToHapiOnPreAuth, OnPreAuthHandler } from './lifecycle/on_pre_auth';
|
||||
import { adoptToHapiOnPostAuthFormat, OnPostAuthHandler } from './lifecycle/on_post_auth';
|
||||
|
@ -133,18 +134,23 @@ export class HttpServer {
|
|||
}
|
||||
}
|
||||
|
||||
public async setup(config: HttpConfig): Promise<HttpServerSetup> {
|
||||
public async setup(
|
||||
config: HttpConfig,
|
||||
executionContext?: InternalExecutionContextSetup
|
||||
): Promise<HttpServerSetup> {
|
||||
const serverOptions = getServerOptions(config);
|
||||
const listenerOptions = getListenerOptions(config);
|
||||
this.server = createServer(serverOptions, listenerOptions);
|
||||
await this.server.register([HapiStaticFiles]);
|
||||
this.config = config;
|
||||
|
||||
// It's important to have setupRequestStateAssignment call the very first, otherwise context passing will be broken.
|
||||
// That's the only reason why context initialization exists in this method.
|
||||
this.setupRequestStateAssignment(config, executionContext);
|
||||
const basePathService = new BasePath(config.basePath, config.publicBaseUrl);
|
||||
this.setupBasePathRewrite(config, basePathService);
|
||||
this.setupConditionalCompression(config);
|
||||
this.setupResponseLogging();
|
||||
this.setupRequestStateAssignment(config);
|
||||
this.setupGracefulShutdownHandlers();
|
||||
|
||||
return {
|
||||
|
@ -323,11 +329,22 @@ export class HttpServer {
|
|||
this.server.events.on('response', this.handleServerResponseEvent);
|
||||
}
|
||||
|
||||
private setupRequestStateAssignment(config: HttpConfig) {
|
||||
private setupRequestStateAssignment(
|
||||
config: HttpConfig,
|
||||
executionContext?: InternalExecutionContextSetup
|
||||
) {
|
||||
this.server!.ext('onRequest', (request, responseToolkit) => {
|
||||
const requestId = getRequestId(request, config.requestId);
|
||||
|
||||
const parentContext = executionContext?.getParentContextFrom(request.headers);
|
||||
executionContext?.set({
|
||||
...parentContext,
|
||||
requestId,
|
||||
});
|
||||
|
||||
request.app = {
|
||||
...(request.app ?? {}),
|
||||
requestId: getRequestId(request, config.requestId),
|
||||
requestId,
|
||||
requestUuid: uuid.v4(),
|
||||
} as KibanaRequestState;
|
||||
return responseToolkit.continue;
|
||||
|
|
|
@ -18,6 +18,7 @@ import { httpServerMock } from './http_server.mocks';
|
|||
import { ConfigService, Env } from '../config';
|
||||
import { loggingSystemMock } from '../logging/logging_system.mock';
|
||||
import { contextServiceMock } from '../context/context_service.mock';
|
||||
import { executionContextServiceMock } from '../execution_context/execution_context_service.mock';
|
||||
import { config as cspConfig } from '../csp';
|
||||
import { config as externalUrlConfig } from '../external_url';
|
||||
|
||||
|
@ -45,6 +46,7 @@ const contextSetup = contextServiceMock.createSetupContract();
|
|||
|
||||
const setupDeps = {
|
||||
context: contextSetup,
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
};
|
||||
const fakeHapiServer = {
|
||||
start: noop,
|
||||
|
|
|
@ -11,6 +11,7 @@ import { first, map } from 'rxjs/operators';
|
|||
import { pick } from '@kbn/std';
|
||||
|
||||
import type { RequestHandlerContext } from 'src/core/server';
|
||||
import type { InternalExecutionContextSetup } from '../execution_context';
|
||||
import { CoreService } from '../../types';
|
||||
import { Logger, LoggerFactory } from '../logging';
|
||||
import { ContextSetup } from '../context';
|
||||
|
@ -41,6 +42,7 @@ import {
|
|||
|
||||
interface SetupDeps {
|
||||
context: ContextSetup;
|
||||
executionContext: InternalExecutionContextSetup;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
|
@ -90,7 +92,10 @@ export class HttpService
|
|||
|
||||
const notReadyServer = await this.setupNotReadyService({ config, context: deps.context });
|
||||
|
||||
const { registerRouter, ...serverContract } = await this.httpServer.setup(config);
|
||||
const { registerRouter, ...serverContract } = await this.httpServer.setup(
|
||||
config,
|
||||
deps.executionContext
|
||||
);
|
||||
|
||||
registerCoreHandlers(serverContract, config, this.env);
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import { ensureRawRequest } from '../router';
|
|||
import { HttpService } from '../http_service';
|
||||
|
||||
import { contextServiceMock } from '../../context/context_service.mock';
|
||||
import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock';
|
||||
import { loggingSystemMock } from '../../logging/logging_system.mock';
|
||||
import { createHttpServer } from '../test_utils';
|
||||
|
||||
|
@ -25,6 +26,7 @@ const contextSetup = contextServiceMock.createSetupContract();
|
|||
|
||||
const setupDeps = {
|
||||
context: contextSetup,
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -18,6 +18,7 @@ import { IRouter, RouteRegistrar } from '../router';
|
|||
|
||||
import { configServiceMock } from '../../config/mocks';
|
||||
import { contextServiceMock } from '../../context/context_service.mock';
|
||||
import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const pkg = require('../../../../../package.json');
|
||||
|
@ -31,6 +32,7 @@ const xsrfDisabledTestPath = '/xsrf/test/route/disabled';
|
|||
const kibanaName = 'my-kibana-name';
|
||||
const setupDeps = {
|
||||
context: contextServiceMock.createSetupContract(),
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
};
|
||||
|
||||
describe('core lifecycle handlers', () => {
|
||||
|
|
|
@ -15,6 +15,7 @@ import supertest from 'supertest';
|
|||
import { HttpService } from '../http_service';
|
||||
|
||||
import { contextServiceMock } from '../../context/context_service.mock';
|
||||
import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock';
|
||||
import { loggingSystemMock } from '../../logging/logging_system.mock';
|
||||
import { createHttpServer } from '../test_utils';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
@ -26,6 +27,7 @@ const contextSetup = contextServiceMock.createSetupContract();
|
|||
|
||||
const setupDeps = {
|
||||
context: contextSetup,
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -12,6 +12,7 @@ import supertest from 'supertest';
|
|||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { contextServiceMock } from '../../context/context_service.mock';
|
||||
import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock';
|
||||
import { loggingSystemMock } from '../../logging/logging_system.mock';
|
||||
import { createHttpServer } from '../test_utils';
|
||||
import { HttpService } from '../http_service';
|
||||
|
@ -24,6 +25,7 @@ const contextSetup = contextServiceMock.createSetupContract();
|
|||
|
||||
const setupDeps = {
|
||||
context: contextSetup,
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -78,6 +78,16 @@ export type {
|
|||
ConfigUsageData,
|
||||
};
|
||||
|
||||
import type { ExecutionContextSetup, ExecutionContextStart } from './execution_context';
|
||||
|
||||
export type {
|
||||
ExecutionContextSetup,
|
||||
ExecutionContextStart,
|
||||
IExecutionContextContainer,
|
||||
KibanaServerExecutionContext,
|
||||
KibanaExecutionContext,
|
||||
} from './execution_context';
|
||||
|
||||
export { bootstrap } from './bootstrap';
|
||||
export type {
|
||||
Capabilities,
|
||||
|
@ -475,6 +485,8 @@ export interface CoreSetup<TPluginsStart extends object = object, TStart = unkno
|
|||
context: ContextSetup;
|
||||
/** {@link ElasticsearchServiceSetup} */
|
||||
elasticsearch: ElasticsearchServiceSetup;
|
||||
/** {@link ExecutionContextSetup} */
|
||||
executionContext: ExecutionContextSetup;
|
||||
/** {@link HttpServiceSetup} */
|
||||
http: HttpServiceSetup & {
|
||||
/** {@link HttpResources} */
|
||||
|
@ -521,6 +533,8 @@ export interface CoreStart {
|
|||
capabilities: CapabilitiesStart;
|
||||
/** {@link ElasticsearchServiceStart} */
|
||||
elasticsearch: ElasticsearchServiceStart;
|
||||
/** {@link ExecutionContextStart} */
|
||||
executionContext: ExecutionContextStart;
|
||||
/** {@link HttpServiceStart} */
|
||||
http: HttpServiceStart;
|
||||
/** {@link MetricsServiceStart} */
|
||||
|
|
|
@ -30,6 +30,10 @@ import { InternalLoggingServiceSetup } from './logging';
|
|||
import { CoreUsageDataStart } from './core_usage_data';
|
||||
import { I18nServiceSetup } from './i18n';
|
||||
import { InternalDeprecationsServiceSetup } from './deprecations';
|
||||
import type {
|
||||
InternalExecutionContextSetup,
|
||||
InternalExecutionContextStart,
|
||||
} from './execution_context';
|
||||
|
||||
/** @internal */
|
||||
export interface InternalCoreSetup {
|
||||
|
@ -37,6 +41,7 @@ export interface InternalCoreSetup {
|
|||
context: ContextSetup;
|
||||
http: InternalHttpServiceSetup;
|
||||
elasticsearch: InternalElasticsearchServiceSetup;
|
||||
executionContext: InternalExecutionContextSetup;
|
||||
i18n: I18nServiceSetup;
|
||||
savedObjects: InternalSavedObjectsServiceSetup;
|
||||
status: InternalStatusServiceSetup;
|
||||
|
@ -60,6 +65,7 @@ export interface InternalCoreStart {
|
|||
savedObjects: InternalSavedObjectsServiceStart;
|
||||
uiSettings: InternalUiSettingsServiceStart;
|
||||
coreUsageData: CoreUsageDataStart;
|
||||
executionContext: InternalExecutionContextStart;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,6 +13,7 @@ import { Server as HapiServer } from '@hapi/hapi';
|
|||
import { createHttpServer } from '../../http/test_utils';
|
||||
import { HttpService, IRouter } from '../../http';
|
||||
import { contextServiceMock } from '../../context/context_service.mock';
|
||||
import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock';
|
||||
import { ServerMetricsCollector } from '../collectors/server';
|
||||
|
||||
const requestWaitDelay = 25;
|
||||
|
@ -29,7 +30,10 @@ describe('ServerMetricsCollector', () => {
|
|||
beforeEach(async () => {
|
||||
server = createHttpServer();
|
||||
const contextSetup = contextServiceMock.createSetupContract();
|
||||
const httpSetup = await server.setup({ context: contextSetup });
|
||||
const httpSetup = await server.setup({
|
||||
context: contextSetup,
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
});
|
||||
hapiServer = httpSetup.server;
|
||||
router = httpSetup.createRouter('/');
|
||||
collector = new ServerMetricsCollector(hapiServer);
|
||||
|
|
|
@ -30,6 +30,7 @@ import { statusServiceMock } from './status/status_service.mock';
|
|||
import { coreUsageDataServiceMock } from './core_usage_data/core_usage_data_service.mock';
|
||||
import { i18nServiceMock } from './i18n/i18n_service.mock';
|
||||
import { deprecationsServiceMock } from './deprecations/deprecations_service.mock';
|
||||
import { executionContextServiceMock } from './execution_context/execution_context_service.mock';
|
||||
|
||||
export { configServiceMock } from './config/mocks';
|
||||
export { httpServerMock } from './http/http_server.mocks';
|
||||
|
@ -51,6 +52,7 @@ export { capabilitiesServiceMock } from './capabilities/capabilities_service.moc
|
|||
export { coreUsageDataServiceMock } from './core_usage_data/core_usage_data_service.mock';
|
||||
export { i18nServiceMock } from './i18n/i18n_service.mock';
|
||||
export { deprecationsServiceMock } from './deprecations/deprecations_service.mock';
|
||||
export { executionContextServiceMock } from './execution_context/execution_context_service.mock';
|
||||
|
||||
type MockedPluginInitializerConfig<T> = jest.Mocked<PluginInitializerContext<T>['config']>;
|
||||
|
||||
|
@ -144,6 +146,7 @@ function createCoreSetupMock({
|
|||
logging: loggingServiceMock.createSetupContract(),
|
||||
metrics: metricsServiceMock.createSetupContract(),
|
||||
deprecations: deprecationsServiceMock.createSetupContract(),
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
getStartServices: jest
|
||||
.fn<Promise<[ReturnType<typeof createCoreStartMock>, object, any]>, []>()
|
||||
.mockResolvedValue([createCoreStartMock(), pluginStartDeps, pluginStartContract]),
|
||||
|
@ -161,6 +164,7 @@ function createCoreStartMock() {
|
|||
savedObjects: savedObjectsServiceMock.createStartContract(),
|
||||
uiSettings: uiSettingsServiceMock.createStartContract(),
|
||||
coreUsageData: coreUsageDataServiceMock.createStartContract(),
|
||||
executionContext: executionContextServiceMock.createInternalStartContract(),
|
||||
};
|
||||
|
||||
return mock;
|
||||
|
@ -182,6 +186,7 @@ function createInternalCoreSetupMock() {
|
|||
logging: loggingServiceMock.createInternalSetupContract(),
|
||||
metrics: metricsServiceMock.createInternalSetupContract(),
|
||||
deprecations: deprecationsServiceMock.createInternalSetupContract(),
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
};
|
||||
return setupDeps;
|
||||
}
|
||||
|
@ -195,6 +200,7 @@ function createInternalCoreStartMock() {
|
|||
savedObjects: savedObjectsServiceMock.createInternalStartContract(),
|
||||
uiSettings: uiSettingsServiceMock.createStartContract(),
|
||||
coreUsageData: coreUsageDataServiceMock.createStartContract(),
|
||||
executionContext: executionContextServiceMock.createInternalStartContract(),
|
||||
};
|
||||
return startDeps;
|
||||
}
|
||||
|
|
|
@ -115,6 +115,7 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
|
|||
elasticsearch: {
|
||||
legacy: deps.elasticsearch.legacy,
|
||||
},
|
||||
executionContext: deps.executionContext,
|
||||
http: {
|
||||
createCookieSessionStorageFactory: deps.http.createCookieSessionStorageFactory,
|
||||
registerRouteHandlerContext: <
|
||||
|
@ -195,6 +196,7 @@ export function createPluginStartContext<TPlugin, TPluginDependencies>(
|
|||
createClient: deps.elasticsearch.createClient,
|
||||
legacy: deps.elasticsearch.legacy,
|
||||
},
|
||||
executionContext: deps.executionContext,
|
||||
http: {
|
||||
auth: deps.http.auth,
|
||||
basePath: deps.http.basePath,
|
||||
|
|
|
@ -11,6 +11,7 @@ import { registerGetRoute } from '../get';
|
|||
import { ContextService } from '../../../context';
|
||||
import { savedObjectsClientMock } from '../../service/saved_objects_client.mock';
|
||||
import { CoreUsageStatsClient } from '../../../core_usage_data';
|
||||
import { executionContextServiceMock } from '../../../execution_context/execution_context_service.mock';
|
||||
import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock';
|
||||
import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock';
|
||||
import { HttpService, InternalHttpServiceSetup } from '../../../http';
|
||||
|
@ -33,6 +34,7 @@ describe('GET /api/saved_objects/{type}/{id}', () => {
|
|||
const contextService = new ContextService(coreContext);
|
||||
httpSetup = await server.setup({
|
||||
context: contextService.setup({ pluginDependencies: new Map() }),
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
});
|
||||
|
||||
handlerContext = coreMock.createRequestHandlerContext();
|
||||
|
|
|
@ -13,6 +13,7 @@ import { savedObjectsClientMock } from '../../service/saved_objects_client.mock'
|
|||
import { CoreUsageStatsClient } from '../../../core_usage_data';
|
||||
import { coreUsageStatsClientMock } from '../../../core_usage_data/core_usage_stats_client.mock';
|
||||
import { coreUsageDataServiceMock } from '../../../core_usage_data/core_usage_data_service.mock';
|
||||
import { executionContextServiceMock } from '../../../execution_context/execution_context_service.mock';
|
||||
import { HttpService, InternalHttpServiceSetup } from '../../../http';
|
||||
import { createHttpServer, createCoreContext } from '../../../http/test_utils';
|
||||
import { coreMock } from '../../../mocks';
|
||||
|
@ -33,6 +34,7 @@ describe('GET /api/saved_objects/resolve/{type}/{id}', () => {
|
|||
const contextService = new ContextService(coreContext);
|
||||
httpSetup = await server.setup({
|
||||
context: contextService.setup({ pluginDependencies: new Map() }),
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
});
|
||||
|
||||
handlerContext = coreMock.createRequestHandlerContext();
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import { ContextService } from '../../context';
|
||||
import { createHttpServer, createCoreContext } from '../../http/test_utils';
|
||||
import { coreMock } from '../../mocks';
|
||||
import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock';
|
||||
import { SavedObjectsType } from '../types';
|
||||
|
||||
const defaultCoreId = Symbol('core');
|
||||
|
@ -20,6 +21,7 @@ export const setupServer = async (coreId: symbol = defaultCoreId) => {
|
|||
const server = createHttpServer(coreContext);
|
||||
const httpSetup = await server.setup({
|
||||
context: contextService.setup({ pluginDependencies: new Map() }),
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
});
|
||||
const handlerContext = coreMock.createRequestHandlerContext();
|
||||
|
||||
|
|
|
@ -523,6 +523,8 @@ export interface CoreSetup<TPluginsStart extends object = object, TStart = unkno
|
|||
// (undocumented)
|
||||
elasticsearch: ElasticsearchServiceSetup;
|
||||
// (undocumented)
|
||||
executionContext: ExecutionContextSetup;
|
||||
// (undocumented)
|
||||
getStartServices: StartServicesAccessor<TPluginsStart, TStart>;
|
||||
// (undocumented)
|
||||
http: HttpServiceSetup & {
|
||||
|
@ -551,6 +553,8 @@ export interface CoreStart {
|
|||
// (undocumented)
|
||||
elasticsearch: ElasticsearchServiceStart;
|
||||
// (undocumented)
|
||||
executionContext: ExecutionContextStart;
|
||||
// (undocumented)
|
||||
http: HttpServiceStart;
|
||||
// (undocumented)
|
||||
metrics: MetricsServiceStart;
|
||||
|
@ -1015,6 +1019,15 @@ export interface ErrorHttpResponseOptions {
|
|||
headers?: ResponseHeaders;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ExecutionContextSetup {
|
||||
get(): IExecutionContextContainer | undefined;
|
||||
set(context: Partial<KibanaServerExecutionContext>): void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type ExecutionContextStart = ExecutionContextSetup;
|
||||
|
||||
// @public
|
||||
export interface FakeRequest {
|
||||
headers: Headers;
|
||||
|
@ -1187,6 +1200,14 @@ export interface ICustomClusterClient extends IClusterClient {
|
|||
close: () => Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface IExecutionContextContainer {
|
||||
// (undocumented)
|
||||
toJSON(): Readonly<KibanaServerExecutionContext>;
|
||||
// (undocumented)
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface IExternalUrlConfig {
|
||||
readonly policy: IExternalUrlPolicy[];
|
||||
|
@ -1303,6 +1324,15 @@ export interface IUiSettingsClient {
|
|||
setMany: (changes: Record<string, any>) => Promise<void>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface KibanaExecutionContext {
|
||||
readonly description: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly type: string;
|
||||
readonly url?: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class KibanaRequest<Params = unknown, Query = unknown, Body = unknown, Method extends RouteMethod = any> {
|
||||
// @internal (undocumented)
|
||||
|
@ -1374,6 +1404,12 @@ export const kibanaResponseFactory: {
|
|||
noContent: (options?: HttpResponseOptions) => KibanaResponse<undefined>;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface KibanaServerExecutionContext extends Partial<KibanaExecutionContext> {
|
||||
// (undocumented)
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "KnownKeys" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public
|
||||
|
|
|
@ -31,6 +31,7 @@ import { CapabilitiesService } from './capabilities';
|
|||
import { EnvironmentService, config as pidConfig } from './environment';
|
||||
// do not try to shorten the import to `./status`, it will break server test mocking
|
||||
import { StatusService } from './status/status_service';
|
||||
import { ExecutionContextService } from './execution_context';
|
||||
|
||||
import { config as cspConfig } from './csp';
|
||||
import { config as elasticsearchConfig } from './elasticsearch';
|
||||
|
@ -48,6 +49,7 @@ import { CoreUsageDataService } from './core_usage_data';
|
|||
import { DeprecationsService } from './deprecations';
|
||||
import { CoreRouteHandlerContext } from './core_route_handler_context';
|
||||
import { config as externalUrlConfig } from './external_url';
|
||||
import { config as executionContextConfig } from './execution_context';
|
||||
|
||||
const coreId = Symbol('core');
|
||||
const rootConfigPath = '';
|
||||
|
@ -73,6 +75,7 @@ export class Server {
|
|||
private readonly coreUsageData: CoreUsageDataService;
|
||||
private readonly i18n: I18nService;
|
||||
private readonly deprecations: DeprecationsService;
|
||||
private readonly executionContext: ExecutionContextService;
|
||||
|
||||
private readonly savedObjectsStartPromise: Promise<SavedObjectsServiceStart>;
|
||||
private resolveSavedObjectsStartPromise?: (value: SavedObjectsServiceStart) => void;
|
||||
|
@ -109,6 +112,7 @@ export class Server {
|
|||
this.coreUsageData = new CoreUsageDataService(core);
|
||||
this.i18n = new I18nService(core);
|
||||
this.deprecations = new DeprecationsService(core);
|
||||
this.executionContext = new ExecutionContextService(core);
|
||||
|
||||
this.savedObjectsStartPromise = new Promise((resolve) => {
|
||||
this.resolveSavedObjectsStartPromise = resolve;
|
||||
|
@ -133,9 +137,11 @@ export class Server {
|
|||
const contextServiceSetup = this.context.setup({
|
||||
pluginDependencies: new Map([...pluginTree.asOpaqueIds]),
|
||||
});
|
||||
const executionContextSetup = this.executionContext.setup();
|
||||
|
||||
const httpSetup = await this.http.setup({
|
||||
context: contextServiceSetup,
|
||||
executionContext: executionContextSetup,
|
||||
});
|
||||
|
||||
// setup i18n prior to any other service, to have translations ready
|
||||
|
@ -145,6 +151,7 @@ export class Server {
|
|||
|
||||
const elasticsearchServiceSetup = await this.elasticsearch.setup({
|
||||
http: httpSetup,
|
||||
executionContext: executionContextSetup,
|
||||
});
|
||||
|
||||
const metricsSetup = await this.metrics.setup({ http: httpSetup });
|
||||
|
@ -200,6 +207,7 @@ export class Server {
|
|||
context: contextServiceSetup,
|
||||
elasticsearch: elasticsearchServiceSetup,
|
||||
environment: environmentSetup,
|
||||
executionContext: executionContextSetup,
|
||||
http: httpSetup,
|
||||
i18n: i18nServiceSetup,
|
||||
savedObjects: savedObjectsSetup,
|
||||
|
@ -230,6 +238,7 @@ export class Server {
|
|||
this.log.debug('starting server');
|
||||
const startTransaction = apm.startTransaction('server_start', 'kibana_platform');
|
||||
|
||||
const executionContextStart = this.executionContext.start();
|
||||
const elasticsearchStart = await this.elasticsearch.start();
|
||||
const soStartSpan = startTransaction?.startSpan('saved_objects.migration', 'migration');
|
||||
const savedObjectsStart = await this.savedObjects.start({
|
||||
|
@ -253,6 +262,7 @@ export class Server {
|
|||
this.coreStart = {
|
||||
capabilities: capabilitiesStart,
|
||||
elasticsearch: elasticsearchStart,
|
||||
executionContext: executionContextStart,
|
||||
http: httpStart,
|
||||
metrics: metricsStart,
|
||||
savedObjects: savedObjectsStart,
|
||||
|
@ -297,6 +307,7 @@ export class Server {
|
|||
|
||||
public setupCoreConfig() {
|
||||
const configDescriptors: Array<ServiceConfigDescriptor<unknown>> = [
|
||||
executionContextConfig,
|
||||
pathConfig,
|
||||
cspConfig,
|
||||
elasticsearchConfig,
|
||||
|
|
|
@ -20,6 +20,7 @@ import { HttpService, InternalHttpServiceSetup } from '../../../http';
|
|||
import { registerStatusRoute } from '../status';
|
||||
import { ServiceStatus, ServiceStatusLevels } from '../../types';
|
||||
import { statusServiceMock } from '../../status_service.mock';
|
||||
import { executionContextServiceMock } from '../../../execution_context/execution_context_service.mock';
|
||||
|
||||
const coreId = Symbol('core');
|
||||
|
||||
|
@ -35,6 +36,7 @@ describe('GET /api/status', () => {
|
|||
server = createHttpServer(coreContext);
|
||||
httpSetup = await server.setup({
|
||||
context: contextService.setup({ pluginDependencies: new Map() }),
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
});
|
||||
|
||||
metrics = metricsServiceMock.createSetupContract();
|
||||
|
|
24
src/core/types/execution_context.ts
Normal file
24
src/core/types/execution_context.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
/** @public */
|
||||
export interface KibanaExecutionContext {
|
||||
/**
|
||||
* Kibana application initated an operation.
|
||||
* Can be narrowed to an enum later.
|
||||
* */
|
||||
readonly type: string; // 'visualization' | 'actions' | 'server' | ..;
|
||||
/** public name of a user-facing feature */
|
||||
readonly name: string; // 'TSVB' | 'Lens' | 'action_execution' | ..;
|
||||
/** unique value to identify the source */
|
||||
readonly id: string;
|
||||
/** human readable description. For example, a vis title, action name */
|
||||
readonly description: string;
|
||||
/** in browser - url to navigate to a current page, on server - endpoint path, for task: task SO url */
|
||||
readonly url?: string;
|
||||
}
|
|
@ -16,3 +16,4 @@ export * from './app_category';
|
|||
export * from './ui_settings';
|
||||
export * from './saved_objects';
|
||||
export * from './serializable';
|
||||
export type { KibanaExecutionContext } from './execution_context';
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
contextServiceMock,
|
||||
loggingSystemMock,
|
||||
metricsServiceMock,
|
||||
executionContextServiceMock,
|
||||
} from '../../../../../core/server/mocks';
|
||||
import { createHttpServer } from '../../../../../core/server/test_utils';
|
||||
import { registerStatsRoute } from '../stats';
|
||||
|
@ -37,6 +38,7 @@ describe('/api/stats', () => {
|
|||
server = createHttpServer();
|
||||
httpSetup = await server.setup({
|
||||
context: contextServiceMock.createSetupContract(),
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
});
|
||||
overallStatus$ = new BehaviorSubject<ServiceStatus>({
|
||||
level: ServiceStatusLevels.available,
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"id": "corePluginExecutionContext",
|
||||
"version": "0.0.1",
|
||||
"kibanaVersion": "kibana",
|
||||
"configPath": ["core_plugin_execution_context"],
|
||||
"server": true,
|
||||
"ui": false
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "core_plugin_execution_context",
|
||||
"version": "1.0.0",
|
||||
"main": "target/test/plugin_functional/plugins/core_plugin_execution_context",
|
||||
"kibana": {
|
||||
"version": "kibana"
|
||||
},
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0",
|
||||
"scripts": {
|
||||
"kbn": "node ../../../../scripts/kbn.js",
|
||||
"build": "rm -rf './target' && ../../../../node_modules/.bin/tsc"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { CorePluginExecutionContext } from './plugin';
|
||||
|
||||
export const plugin = () => new CorePluginExecutionContext();
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { Plugin, CoreSetup } from 'kibana/server';
|
||||
|
||||
export class CorePluginExecutionContext implements Plugin {
|
||||
public setup(core: CoreSetup, deps: {}) {
|
||||
const router = core.http.createRouter();
|
||||
router.get(
|
||||
{
|
||||
path: '/execution_context/pass',
|
||||
validate: false,
|
||||
options: {
|
||||
authRequired: false,
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const { headers } = await context.core.elasticsearch.client.asCurrentUser.ping();
|
||||
return response.ok({ body: headers || {} });
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public start() {}
|
||||
public stop() {}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./target",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"index.ts",
|
||||
"server/**/*.ts",
|
||||
],
|
||||
"exclude": [],
|
||||
"references": [
|
||||
{ "path": "../../../../src/core/tsconfig.json" }
|
||||
]
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { PluginFunctionalProviderContext } from '../../services';
|
||||
import '../../../../test/plugin_functional/plugins/core_provider_plugin/types';
|
||||
|
||||
export default function ({ getService, getPageObjects }: PluginFunctionalProviderContext) {
|
||||
describe('execution context', function () {
|
||||
describe('passed for a client-side operation', () => {
|
||||
const PageObjects = getPageObjects(['common']);
|
||||
const browser = getService('browser');
|
||||
|
||||
before(async () => {
|
||||
await PageObjects.common.navigateToApp('home');
|
||||
});
|
||||
|
||||
it('passes plugin-specific execution context to Elasticsearch server', async () => {
|
||||
expect(
|
||||
await browser.execute(async () => {
|
||||
const coreStart = window._coreProvider.start.core;
|
||||
|
||||
const context = coreStart.executionContext.create({
|
||||
type: 'execution_context_app',
|
||||
name: 'Execution context app',
|
||||
id: '42',
|
||||
// add a non-ASCII symbols to make sure it doesn't break the context propagation mechanism
|
||||
description: 'какое-то странное описание',
|
||||
});
|
||||
|
||||
const result = await coreStart.http.get('/execution_context/pass', {
|
||||
context,
|
||||
});
|
||||
|
||||
return result['x-opaque-id'];
|
||||
})
|
||||
).to.contain('kibana:execution_context_app:42');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -12,6 +12,7 @@ export default function ({ loadTestFile }: PluginFunctionalProviderContext) {
|
|||
describe('core plugins', () => {
|
||||
loadTestFile(require.resolve('./applications'));
|
||||
loadTestFile(require.resolve('./elasticsearch_client'));
|
||||
loadTestFile(require.resolve('./execution_context'));
|
||||
loadTestFile(require.resolve('./server_plugins'));
|
||||
loadTestFile(require.resolve('./ui_plugins'));
|
||||
loadTestFile(require.resolve('./ui_settings'));
|
||||
|
|
|
@ -5,7 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { contextServiceMock } from 'src/core/server/mocks';
|
||||
import {
|
||||
contextServiceMock,
|
||||
executionContextServiceMock,
|
||||
} from '../../../../../../../../src/core/server/mocks';
|
||||
import { createHttpServer } from 'src/core/server/test_utils';
|
||||
import supertest from 'supertest';
|
||||
import { createApmEventClient } from '.';
|
||||
|
@ -23,6 +26,7 @@ describe('createApmEventClient', () => {
|
|||
it('cancels a search when a request is aborted', async () => {
|
||||
const { server: innerServer, createRouter } = await server.setup({
|
||||
context: contextServiceMock.createSetupContract(),
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
});
|
||||
const router = createRouter('/');
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
contextServiceMock,
|
||||
elasticsearchServiceMock,
|
||||
savedObjectsServiceMock,
|
||||
executionContextServiceMock,
|
||||
} from '../../../../../src/core/server/mocks';
|
||||
import { createHttpServer } from '../../../../../src/core/server/test_utils';
|
||||
import { registerSettingsRoute } from './settings';
|
||||
|
@ -48,6 +49,7 @@ describe('/api/settings', () => {
|
|||
},
|
||||
},
|
||||
}),
|
||||
executionContext: executionContextServiceMock.createInternalSetupContract(),
|
||||
});
|
||||
|
||||
overallStatus$ = new BehaviorSubject<ServiceStatus>({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue