[ES] Upgrade client to v8.0 (#113950)

* bump to a pre-8.0 version

* export KibanaClient from /lib sub-folder

* workaround the problem of the absence of estypes

* update es client usage in pacakges

* export estypes from another path

* import errors from root

* import errors from root 2

* update transport import

* update import path for /api/types

* update import path for /api/types

* import errors from top export

* use TransportResult instead if ApiResponse

* fix errors in client_config

* fix src/core/server/saved_objects/migrationsv2/actions/integration_tests/actions.test.ts

* use KibanaClient in mock. we dont export the original Client

* fix client mocks

* fix errors on SO

* fix remaining core errors

* update estype import path

* fix errors in data plugin

* fix data_views

* fix es_ui_shared

* fix errors in interactive_setup

* fix errors in ./test folder

* add @elastic/transport to the runtime deps

* fix errors in packages

* fix erros in src/core

* fix errors in test/

* fix an error in actions plugin

* woraround and fix errors in APM plugin

* fix errors in canvas

* fix errors in event_log

* fix errors in fleet

* fix errors in ILM

* fix errors in infra

* fix errors in ingest_pipeline

* fix errors in lens

* fix errors in license_management

* fix errors in licensing

* fix errors in logstash

* fix errors in ml

* fix errors in monitoring

* fix errors in observability

* fix errors in rule_registry

* fix errors in reporting

* fix errors in rule_registry

* fix errors in security

* fix errors in security_solution

* fix errors in snapshot_restore

* fix errors in transform

* fix errors in UA

* fix errors in uptime

* fix errors in x-pack/test

* fix eslint errors

* fix new errors

* use default HTTP Connection. Undici does not support agent config options keepAlive and maxSockets

* create does not accept require_alias option

* update deps

* use transport types exported from ES client package

* fix ErrorCause | string errors

* do not use enum

* fix errors in data plugin

* update x-pack code

* fix transport

* fix apm search request

* do not crash on reporting

* fix kbn-test build

* mute reporting error to start

* fix ftr build

* another attempt

* update import path

* address or mute new errors

* REMOVE me. pin transport version temporarily.

* remove deep imports from transport package

* fix jest crash

* fix product check tests

* remove unnecessary ts-expect-error

* fix a few failed unit tests

* bump to canary 24

* remove unnecessary ts-expect-error

* remove dependency on transport

* fix types in tests

* mute errors in xpack tests

* product check doesn;t  spam in logs anymore

* filterPath --> filter_path

* ignoreUnavailable --> ignore_unavailable

* ignoreUnavailable --> ignore_unavailable

* trackScores --> track_scores

* trackTotalHits --> track_total_hits

* fix es-arcives

* fix data plugin crashes

* fix watcher test utils

* rollback unnecessary changes

* fix another problem in es-archiver

* fix scroll. for whatever reason scroll fails when request scroll_id in body

* add meta: true in kbn-securitysolution-es-utils

* bump client to canary 25

* fix errors in accordance with the es client spec

* update securityscolution-es-utils

* unify scroll api in reporting and fix tests

* fix unit tests in watcher

* refactor APM to abort request with AbortController API

* fix missing es client calls in tests

* fix missing meta in detection engine FTR tests

* fix another bunch of errors in js tests

* fix wrong coercion

* remove test-grep pattern

* fix apm unit test

* rename terminateAfter to terminate_after in infra plugin

* rename terminateAfter to terminate_after in uptime plugin

* rename terminateAfter to terminate_after in apm plugin

* fix security roles FTR tests

* fix reference

* fix post_privilidges test

* fix post_privilidges

* bump client to 26

* add meta for index_management test helpers

* remove ts-expect-error caused by bad type in reason

* bump client to 27

* REMOVE me. workaround until fixed in the es client

* fix incorrect type casting

* swtich from camelCase params

* use `HttpConnection` for FTR-related clients

* bump client to 29

* Revert "REMOVE me. workaround until fixed in the es client"

This reverts commit c038850c09.

* fix new util

* revert repository changes

* do not crash if cannot store event_loop data

* fix new estypes imports

* fix more types

* fix security test types and add ts-ignore for custom ES client

* fix more estypes imports

* yet more ts violations

* line by line fixing is hard

* adapt `evaluateAlert` from infra as it's also used from FTR tests

* use convertToKibanaClient in FTR test instead of meta:true in plugin code

* migrate from deprecated API in fleet

* fix intergration tests

* fix fleet tests

* fix another fleet test

* fix more tests

* let's call it a day

* Removes custom header check on 404 responses, includes es client ProductNotSupportedError in EsUnavailableError conditional (#116029)

* Removes custom header check on 404 responses, includes es client ProductNotSupportedError in EsUnavailableError conditional

* Updates proxy response integration test

* disable APM until compatible with client v8

* skip async_search FTR test

* use kbnClient in integration tests

* bump version to 29

* bump to 30

* have configureClient return a KibanaClient instead of Client, remove resolved violations.

* bump to 31

* bump to 31

* Revert "bump to 31"

This reverts commit 5ac713e640.

* trigger stop to unusubscribe

* update generated docs

* remove obsolete test

* put "as" back

* cleanup

* skip test

* remove new type errors in apm package

* remove ErrorCause casting

* update a comment

* bump version to 32

* remove unnecessary ts-expect-error in apm code

* update comments

* update to client v33

* remove outdated type definition

* bump to 34 without params mutation

* unskip the test that should not fail anymore

* remove unnecessary ts-expect-error comments

* update to v35. body can be string

* move `sort` to body and use body friendly syntax

* fix a failing test. maps register the same SO that has been already registered by home

Co-authored-by: pgayvallet <pierre.gayvallet@gmail.com>
Co-authored-by: Christiane (Tina) Heiligers <christiane.heiligers@elastic.co>
This commit is contained in:
Mikhail Shustov 2021-10-26 14:08:22 +02:00 committed by GitHub
parent 252aef80dc
commit 3c8fa527a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
814 changed files with 3045 additions and 3350 deletions

View file

@ -9,9 +9,9 @@ Client used to query the elasticsearch cluster.
<b>Signature:</b>
```typescript
export declare type ElasticsearchClient = Omit<KibanaClient, 'connectionPool' | 'transport' | 'serializer' | 'extend' | 'child' | 'close'> & {
export declare type ElasticsearchClient = Omit<KibanaClient, 'connectionPool' | 'transport' | 'serializer' | 'extend' | 'child' | 'close' | 'diagnostic'> & {
transport: {
request(params: TransportRequestParams, options?: TransportRequestOptions): TransportRequestPromise<ApiResponse>;
request<TResponse = unknown>(params: TransportRequestParams, options?: TransportRequestOptions): Promise<TransportResult<TResponse>>;
};
};
```

View file

@ -0,0 +1,14 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [ElasticsearchErrorDetails](./kibana-plugin-core-server.elasticsearcherrordetails.md) &gt; [error](./kibana-plugin-core-server.elasticsearcherrordetails.error.md)
## ElasticsearchErrorDetails.error property
<b>Signature:</b>
```typescript
error?: {
type: string;
reason?: string;
};
```

View file

@ -0,0 +1,19 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [ElasticsearchErrorDetails](./kibana-plugin-core-server.elasticsearcherrordetails.md)
## ElasticsearchErrorDetails interface
<b>Signature:</b>
```typescript
export interface ElasticsearchErrorDetails
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [error](./kibana-plugin-core-server.elasticsearcherrordetails.error.md) | <code>{</code><br/><code> type: string;</code><br/><code> reason?: string;</code><br/><code> }</code> | |

View file

@ -71,6 +71,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [DeprecationsServiceSetup](./kibana-plugin-core-server.deprecationsservicesetup.md) | The deprecations service provides a way for the Kibana platform to communicate deprecated features and configs with its users. These deprecations are only communicated if the deployment is using these features. Allowing for a user tailored experience for upgrading the stack version.<!-- -->The Deprecation service is consumed by the upgrade assistant to assist with the upgrade experience.<!-- -->If a deprecated feature can be resolved without manual user intervention. Using correctiveActions.api allows the Upgrade Assistant to use this api to correct the deprecation upon a user trigger. |
| [DiscoveredPlugin](./kibana-plugin-core-server.discoveredplugin.md) | Small container object used to expose information about discovered plugins that may or may not have been started. |
| [ElasticsearchConfigPreboot](./kibana-plugin-core-server.elasticsearchconfigpreboot.md) | A limited set of Elasticsearch configuration entries exposed to the <code>preboot</code> plugins at <code>setup</code>. |
| [ElasticsearchErrorDetails](./kibana-plugin-core-server.elasticsearcherrordetails.md) | |
| [ElasticsearchServicePreboot](./kibana-plugin-core-server.elasticsearchservicepreboot.md) | |
| [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) | |
| [ElasticsearchServiceStart](./kibana-plugin-core-server.elasticsearchservicestart.md) | |

View file

@ -1,23 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) &gt; [createGenericNotFoundEsUnavailableError](./kibana-plugin-core-server.savedobjectserrorhelpers.creategenericnotfoundesunavailableerror.md)
## SavedObjectsErrorHelpers.createGenericNotFoundEsUnavailableError() method
<b>Signature:</b>
```typescript
static createGenericNotFoundEsUnavailableError(type?: string | null, id?: string | null): DecoratedError;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| type | <code>string &#124; null</code> | |
| id | <code>string &#124; null</code> | |
<b>Returns:</b>
`DecoratedError`

View file

@ -18,7 +18,6 @@ export declare class SavedObjectsErrorHelpers
| [createBadRequestError(reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.createbadrequesterror.md) | <code>static</code> | |
| [createConflictError(type, id, reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.createconflicterror.md) | <code>static</code> | |
| [createGenericNotFoundError(type, id)](./kibana-plugin-core-server.savedobjectserrorhelpers.creategenericnotfounderror.md) | <code>static</code> | |
| [createGenericNotFoundEsUnavailableError(type, id)](./kibana-plugin-core-server.savedobjectserrorhelpers.creategenericnotfoundesunavailableerror.md) | <code>static</code> | |
| [createIndexAliasNotFoundError(alias)](./kibana-plugin-core-server.savedobjectserrorhelpers.createindexaliasnotfounderror.md) | <code>static</code> | |
| [createInvalidVersionError(versionInput)](./kibana-plugin-core-server.savedobjectserrorhelpers.createinvalidversionerror.md) | <code>static</code> | |
| [createTooManyRequestsError(type, id)](./kibana-plugin-core-server.savedobjectserrorhelpers.createtoomanyrequestserror.md) | <code>static</code> | |

View file

@ -100,7 +100,7 @@
"@elastic/apm-rum-react": "^1.3.1",
"@elastic/charts": "38.0.1",
"@elastic/datemath": "link:bazel-bin/packages/elastic-datemath",
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.21",
"@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.35",
"@elastic/ems-client": "7.16.0",
"@elastic/eui": "40.0.0",
"@elastic/filesaver": "1.1.2",

View file

@ -35,7 +35,7 @@ export async function cleanWriteTargets({
wait_for_completion: false,
});
const task = response.body.task;
const task = response.task;
if (task) {
await new Promise<void>((resolve, reject) => {
@ -45,13 +45,13 @@ export async function cleanWriteTargets({
});
logger.debug(
`Polled for task:\n${JSON.stringify(taskResponse.body, ['completed', 'error'], 2)}`
`Polled for task:\n${JSON.stringify(taskResponse, ['completed', 'error'], 2)}`
);
if (taskResponse.body.completed) {
if (taskResponse.completed) {
resolve();
} else if (taskResponse.body.error) {
reject(taskResponse.body.error);
} else if (taskResponse.error) {
reject(taskResponse.error);
} else {
setTimeout(pollForTaskCompletion, 2500);
}

View file

@ -24,16 +24,15 @@ export async function getWriteTargets({
]);
function getDataStreamName(filter: string) {
return datastreamsResponse.body.data_streams.find((stream) => stream.name.includes(filter))
?.name;
return datastreamsResponse.data_streams.find((stream) => stream.name.includes(filter))?.name;
}
function getAlias(filter: string) {
return Object.keys(indicesResponse.body)
return Object.keys(indicesResponse)
.map((key) => {
return {
key,
writeIndexAlias: Object.entries(indicesResponse.body[key].aliases).find(
writeIndexAlias: Object.entries(indicesResponse[key].aliases).find(
([_, alias]) => alias.is_write_index
)?.[0],
};

View file

@ -59,7 +59,7 @@ export function uploadEvents({
)
.then((results) => {
const errors = results
.flatMap((result) => result.body.items)
.flatMap((result) => result.items)
.filter((item) => !!item.index?.error)
.map((item) => item.index?.error);

View file

@ -11,7 +11,8 @@ import { mockLoadConfiguration } from './init_apm.test.mocks';
import { initApm } from './init_apm';
import apm from 'elastic-apm-node';
describe('initApm', () => {
// TODO: unskip when https://github.com/elastic/kibana/issues/116109 is fixed
describe.skip('initApm', () => {
let apmAddFilterSpy: jest.SpyInstance;
let apmStartSpy: jest.SpyInstance;
let getConfig: jest.Mock;

View file

@ -14,6 +14,9 @@ export const initApm = (
isDistributable: boolean,
serviceName: string
) => {
// TODO: re-enabled when https://github.com/elastic/kibana/issues/116109 is fixed
return;
const apmConfigLoader = loadConfiguration(argv, rootDir, isDistributable);
const apmConfig = apmConfigLoader.getConfig(serviceName);

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import type { Client } from '@elastic/elasticsearch';
import { ToolingLog } from '@kbn/dev-utils';
import { KbnClient } from '@kbn/test';
@ -17,7 +17,7 @@ export async function emptyKibanaIndexAction({
log,
kbnClient,
}: {
client: KibanaClient;
client: Client;
log: ToolingLog;
kbnClient: KbnClient;
}) {

View file

@ -11,7 +11,7 @@ import { createReadStream } from 'fs';
import { Readable } from 'stream';
import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils';
import { KbnClient } from '@kbn/test';
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import type { Client } from '@elastic/elasticsearch';
import { createPromiseFromStreams, concatStreamProviders } from '@kbn/utils';
import { ES_CLIENT_HEADERS } from '../client_headers';
@ -47,7 +47,7 @@ export async function loadAction({
inputDir: string;
skipExisting: boolean;
useCreate: boolean;
client: KibanaClient;
client: Client;
log: ToolingLog;
kbnClient: KbnClient;
}) {

View file

@ -9,7 +9,7 @@
import { resolve, relative } from 'path';
import { createWriteStream, mkdirSync } from 'fs';
import { Readable, Writable } from 'stream';
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import type { Client } from '@elastic/elasticsearch';
import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils';
import { createListStream, createPromiseFromStreams } from '@kbn/utils';
@ -31,7 +31,7 @@ export async function saveAction({
}: {
outputDir: string;
indices: string | string[];
client: KibanaClient;
client: Client;
log: ToolingLog;
raw: boolean;
query?: Record<string, any>;

View file

@ -9,7 +9,7 @@
import { resolve, relative } from 'path';
import { createReadStream } from 'fs';
import { Readable, Writable } from 'stream';
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import type { Client } from '@elastic/elasticsearch';
import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils';
import { KbnClient } from '@kbn/test';
import { createPromiseFromStreams } from '@kbn/utils';
@ -31,7 +31,7 @@ export async function unloadAction({
kbnClient,
}: {
inputDir: string;
client: KibanaClient;
client: Client;
log: ToolingLog;
kbnClient: KbnClient;
}) {

View file

@ -19,7 +19,7 @@ import Fs from 'fs';
import { RunWithCommands, createFlagError, CA_CERT_PATH } from '@kbn/dev-utils';
import { readConfigFile, KbnClient } from '@kbn/test';
import { Client } from '@elastic/elasticsearch';
import { Client, HttpConnection } from '@elastic/elasticsearch';
import { EsArchiver } from './es_archiver';
@ -106,7 +106,8 @@ export function runCli() {
const client = new Client({
node: esUrl,
ssl: esCa ? { ca: esCa } : undefined,
tls: esCa ? { ca: esCa } : undefined,
Connection: HttpConnection,
});
addCleanupTask(() => client.close());

View file

@ -9,7 +9,7 @@
import Fs from 'fs';
import Path from 'path';
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import type { Client } from '@elastic/elasticsearch';
import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils';
import { KbnClient } from '@kbn/test';
@ -23,14 +23,14 @@ import {
} from './actions';
interface Options {
client: KibanaClient;
client: Client;
baseDir?: string;
log: ToolingLog;
kbnClient: KbnClient;
}
export class EsArchiver {
private readonly client: KibanaClient;
private readonly client: Client;
private readonly baseDir: string;
private readonly log: ToolingLog;
private readonly kbnClient: KbnClient;

View file

@ -99,10 +99,8 @@ it('transforms each input index to a stream of docs using scrollSearch helper',
Array [
Object {
"_source": "true",
"body": Object {
"query": undefined,
},
"index": "bar",
"query": undefined,
"rest_total_hits_as_int": true,
"scroll": "1m",
"size": 1000,
@ -116,10 +114,8 @@ it('transforms each input index to a stream of docs using scrollSearch helper',
Array [
Object {
"_source": "true",
"body": Object {
"query": undefined,
},
"index": "foo",
"query": undefined,
"rest_total_hits_as_int": true,
"scroll": "1m",
"size": 1000,

View file

@ -7,7 +7,7 @@
*/
import { Transform } from 'stream';
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import type { Client } from '@elastic/elasticsearch';
import { Stats } from '../stats';
import { Progress } from '../progress';
import { ES_CLIENT_HEADERS } from '../../client_headers';
@ -21,7 +21,7 @@ export function createGenerateDocRecordsStream({
progress,
query,
}: {
client: KibanaClient;
client: Client;
stats: Stats;
progress: Progress;
query?: Record<string, any>;
@ -37,9 +37,7 @@ export function createGenerateDocRecordsStream({
scroll: SCROLL_TIMEOUT,
size: SCROLL_SIZE,
_source: 'true',
body: {
query,
},
query,
rest_total_hits_as_int: true,
},
{

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import type { Client } from '@elastic/elasticsearch';
import AggregateError from 'aggregate-error';
import { Writable } from 'stream';
import { Stats } from '../stats';
@ -14,7 +14,7 @@ import { Progress } from '../progress';
import { ES_CLIENT_HEADERS } from '../../client_headers';
export function createIndexDocRecordsStream(
client: KibanaClient,
client: Client,
stats: Stats,
progress: Progress,
useCreate: boolean = false

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import type { Client } from '@elastic/elasticsearch';
import sinon from 'sinon';
import { ToolingLog } from '@kbn/dev-utils';
import { Stats } from '../../stats';
@ -67,7 +67,7 @@ const createEsClientError = (errorType: string) => {
const indexAlias = (aliases: Record<string, any>, index: string) =>
Object.keys(aliases).find((k) => aliases[k] === index);
type StubClient = KibanaClient;
type StubClient = Client;
export const createStubClient = (
existingIndices: string[] = [],

View file

@ -71,6 +71,7 @@ describe('esArchiver: createCreateIndexStream()', () => {
"ignore": Array [
404,
],
"meta": true,
},
],
]

View file

@ -9,8 +9,8 @@
import { Transform, Readable } from 'stream';
import { inspect } from 'util';
import { estypes } from '@elastic/elasticsearch';
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { Client } from '@elastic/elasticsearch';
import { ToolingLog } from '@kbn/dev-utils';
import { Stats } from '../stats';
@ -31,7 +31,7 @@ export function createCreateIndexStream({
skipExisting = false,
log,
}: {
client: KibanaClient;
client: Client;
stats: Stats;
skipExisting?: boolean;
log: ToolingLog;

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import type { Client } from '@elastic/elasticsearch';
import { ToolingLog } from '@kbn/dev-utils';
import { Stats } from '../stats';
import { ES_CLIENT_HEADERS } from '../../client_headers';
@ -15,7 +15,7 @@ import { ES_CLIENT_HEADERS } from '../../client_headers';
const PENDING_SNAPSHOT_STATUSES = ['INIT', 'STARTED', 'WAITING'];
export async function deleteIndex(options: {
client: KibanaClient;
client: Client;
stats: Stats;
index: string | string[];
log: ToolingLog;
@ -32,6 +32,7 @@ export async function deleteIndex(options: {
{
ignore: [404],
headers: ES_CLIENT_HEADERS,
meta: true,
}
);
@ -84,15 +85,13 @@ export function isDeleteWhileSnapshotInProgressError(error: any) {
* snapshotting this index to complete.
*/
export async function waitForSnapshotCompletion(
client: KibanaClient,
client: Client,
index: string | string[],
log: ToolingLog
) {
const isSnapshotPending = async (repository: string, snapshot: string) => {
const {
body: {
snapshots: [status],
},
snapshots: [status],
} = await client.snapshot.status(
{
repository,
@ -108,9 +107,7 @@ export async function waitForSnapshotCompletion(
};
const getInProgressSnapshots = async (repository: string) => {
const {
body: { snapshots: inProgressSnapshots },
} = await client.snapshot.get(
const { snapshots: inProgressSnapshots } = await client.snapshot.get(
{
repository,
snapshot: '_current',
@ -123,7 +120,7 @@ export async function waitForSnapshotCompletion(
return inProgressSnapshots;
};
const { body: repositoryMap } = await client.snapshot.getRepository({} as any);
const repositoryMap = await client.snapshot.getRepository({});
for (const repository of Object.keys(repositoryMap)) {
const allInProgress = await getInProgressSnapshots(repository);
const found = allInProgress?.find((s: any) => s.indices.includes(index));

View file

@ -7,14 +7,14 @@
*/
import { Transform } from 'stream';
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import type { Client } from '@elastic/elasticsearch';
import { ToolingLog } from '@kbn/dev-utils';
import { Stats } from '../stats';
import { deleteIndex } from './delete_index';
import { cleanKibanaIndices } from './kibana_index';
export function createDeleteIndexStream(client: KibanaClient, stats: Stats, log: ToolingLog) {
export function createDeleteIndexStream(client: Client, stats: Stats, log: ToolingLog) {
return new Transform({
readableObjectMode: true,
writableObjectMode: true,

View file

@ -6,12 +6,12 @@
* Side Public License, v 1.
*/
import type { Client } from '@elastic/elasticsearch';
import { Transform } from 'stream';
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import { Stats } from '../stats';
import { ES_CLIENT_HEADERS } from '../../client_headers';
export function createGenerateIndexRecordsStream(client: KibanaClient, stats: Stats) {
export function createGenerateIndexRecordsStream(client: Client, stats: Stats) {
return new Transform({
writableObjectMode: true,
readableObjectMode: true,
@ -37,9 +37,10 @@ export function createGenerateIndexRecordsStream(client: KibanaClient, stats: St
},
{
headers: ES_CLIENT_HEADERS,
meta: true,
}
)
).body as Record<string, any>;
).body;
for (const [index, { settings, mappings }] of Object.entries(resp)) {
const {
@ -50,6 +51,7 @@ export function createGenerateIndexRecordsStream(client: KibanaClient, stats: St
{ index },
{
headers: ES_CLIENT_HEADERS,
meta: true,
}
);

View file

@ -8,7 +8,7 @@
import { inspect } from 'util';
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import type { Client } from '@elastic/elasticsearch';
import { ToolingLog } from '@kbn/dev-utils';
import { KbnClient } from '@kbn/test';
import { Stats } from '../stats';
@ -23,7 +23,7 @@ export async function deleteKibanaIndices({
stats,
log,
}: {
client: KibanaClient;
client: Client;
stats: Stats;
log: ToolingLog;
}) {
@ -35,7 +35,7 @@ export async function deleteKibanaIndices({
await client.indices.putSettings(
{
index: indexNames,
body: { settings: { blocks: { read_only: false } } },
body: { blocks: { read_only: false } },
},
{
headers: ES_CLIENT_HEADERS,
@ -75,7 +75,7 @@ function isKibanaIndex(index?: string): index is string {
);
}
async function fetchKibanaIndices(client: KibanaClient) {
async function fetchKibanaIndices(client: Client) {
const resp = await client.cat.indices(
{ index: '.kibana*', format: 'json' },
{
@ -83,11 +83,11 @@ async function fetchKibanaIndices(client: KibanaClient) {
}
);
if (!Array.isArray(resp.body)) {
throw new Error(`expected response to be an array ${inspect(resp.body)}`);
if (!Array.isArray(resp)) {
throw new Error(`expected response to be an array ${inspect(resp)}`);
}
return resp.body.map((x: { index?: string }) => x.index).filter(isKibanaIndex);
return resp.map((x: { index?: string }) => x.index).filter(isKibanaIndex);
}
const delay = (delayInMs: number) => new Promise((resolve) => setTimeout(resolve, delayInMs));
@ -97,7 +97,7 @@ export async function cleanKibanaIndices({
stats,
log,
}: {
client: KibanaClient;
client: Client;
stats: Stats;
log: ToolingLog;
}) {
@ -123,11 +123,11 @@ export async function cleanKibanaIndices({
}
);
if (resp.body.total !== resp.body.deleted) {
if (resp.total !== resp.deleted) {
log.warning(
'delete by query deleted %d of %d total documents, trying again',
resp.body.deleted,
resp.body.total
resp.deleted,
resp.total
);
await delay(200);
continue;
@ -144,13 +144,7 @@ export async function cleanKibanaIndices({
stats.deletedIndex('.kibana');
}
export async function createDefaultSpace({
index,
client,
}: {
index: string;
client: KibanaClient;
}) {
export async function createDefaultSpace({ index, client }: { index: string; client: Client }) {
await client.create(
{
index,

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { SerializableRecord } from '@kbn/utility-types';
import { extend, defaults } from 'lodash';
import { getTimeZoneFromSettings } from '../utils';

View file

@ -7,7 +7,7 @@
*/
import { isUndefined } from 'lodash';
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { migrateFilter } from './migrate_filter';
import { filterMatchesIndex } from './filter_matches_index';
import { Filter, cleanFilter, isFilterDisabled } from '../filters';

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { isString } from 'lodash';
/**

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import type { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
/**
* A field's sub type

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { Filter, FilterMeta, FILTERS, FilterStateStore } from './types';
/** @public */

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { has } from 'lodash';
import type { Filter, FilterMeta } from './types';

View file

@ -14,7 +14,7 @@ import {
} from './phrase_filter';
import { fields, getField } from '../stubs';
import { DataViewBase } from '../../es_query';
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
describe('Phrase filter builder', () => {
let indexPattern: DataViewBase;

View file

@ -5,7 +5,7 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { get, has, isPlainObject } from 'lodash';
import type { Filter, FilterMeta } from './types';
import type { IndexPatternFieldBase, IndexPatternBase } from '../../es_query';

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { Filter, FilterMeta, FILTERS } from './types';
import { getPhraseScript, PhraseFilterValue } from './phrase_filter';
import type { IndexPatternFieldBase, IndexPatternBase } from '../../es_query';

View file

@ -5,7 +5,7 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { map, reduce, mapValues, has, get, keys, pickBy } from 'lodash';
import type { Filter, FilterMeta } from './types';
import type { IndexPatternBase, IndexPatternFieldBase } from '../../es_query';

View file

@ -7,7 +7,7 @@
*/
import { JsonObject } from '@kbn/utility-types';
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { nodeTypes } from '../node_types/index';
import { KQLSyntaxError } from '../kuery_syntax_error';
import { KueryNode, KueryParseOptions, KueryQueryOptions } from '../types';

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { IndexPatternFieldBase, IndexPatternBase, KueryNode, KueryQueryOptions } from '../..';
import * as literal from '../node_types/literal';

View file

@ -109,7 +109,6 @@ describe('kuery functions', () => {
const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params);
const result = geoBoundingBox.toElasticsearchQuery(node, indexPattern);
// @ts-expect-error @elastic/elasticsearch doesn't support ignore_unmapped in QueryDslGeoBoundingBoxQuery
expect(result.geo_bounding_box!.ignore_unmapped).toBe(true);
});

View file

@ -7,7 +7,7 @@
*/
import _ from 'lodash';
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { nodeTypes } from '../node_types';
import * as ast from '../ast';
import { IndexPatternBase, KueryNode, KueryQueryOptions, LatLon } from '../..';
@ -53,7 +53,6 @@ export function toElasticsearchQuery(
}
return {
// @ts-expect-error @elastic/elasticsearch doesn't support ignore_unmapped in QueryDslGeoBoundingBoxQuery
geo_bounding_box: {
[fieldName]: queryParams,
ignore_unmapped: true,

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { nodeTypes } from '../node_types';
import * as ast from '../ast';
import { IndexPatternBase, KueryNode, KueryQueryOptions, LatLon } from '../..';
@ -49,7 +49,6 @@ export function toElasticsearchQuery(
}
return {
// @ts-expect-error @elastic/elasticsearch doesn't support ignore_unmapped in QueryDslGeoPolygonQuery
geo_polygon: {
[fieldName]: queryParams,
ignore_unmapped: true,

View file

@ -11,7 +11,7 @@ import { fields } from '../../filters/stubs';
import * as is from './is';
import { DataViewBase } from '../..';
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
jest.mock('../grammar');

View file

@ -7,7 +7,7 @@
*/
import { isUndefined } from 'lodash';
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { getPhraseScript } from '../../filters';
import { getFields } from './utils/get_fields';
import { getTimeZoneFromSettings, getDataViewFieldSubtypeNested } from '../../utils';

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import * as ast from '../ast';
import * as literal from '../node_types/literal';
import { IndexPatternBase, KueryNode, KueryQueryOptions } from '../..';

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import * as ast from '../ast';
import { IndexPatternBase, KueryNode, KueryQueryOptions } from '../..';

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import * as ast from '../ast';
import { IndexPatternBase, KueryNode, KueryQueryOptions } from '../..';

View file

@ -13,7 +13,7 @@ import { DataViewBase } from '../..';
import { RangeFilterParams } from '../../filters';
import * as range from './range';
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
jest.mock('../grammar');
describe('kuery functions', () => {

View file

@ -7,7 +7,7 @@
*/
import { pick, map, mapValues } from 'lodash';
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { nodeTypes } from '../node_types';
import * as ast from '../ast';
import { getRangeScript, RangeFilterParams } from '../../filters';

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { toElasticsearchQuery as astToElasticsearchQuery } from './ast';
/**

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { SerializableRecord } from '@kbn/utility-types';
import { NodeTypes } from './node_types';

View file

@ -16,7 +16,7 @@ exports.NativeRealm = class NativeRealm {
const auth = { username: 'elastic', password: elasticPassword };
this._client = new Client(
ssl
? { node: `https://localhost:${port}`, ssl: { ca: caCert, rejectUnauthorized: true }, auth }
? { node: `https://localhost:${port}`, tls: { ca: caCert, rejectUnauthorized: true }, auth }
: { node: `http://localhost:${port}`, auth }
);
this._elasticPassword = elasticPassword;
@ -67,9 +67,7 @@ exports.NativeRealm = class NativeRealm {
async getReservedUsers(retryOpts = {}) {
return await this._autoRetry(retryOpts, async () => {
const resp = await this._client.security.getUser();
const usernames = Object.keys(resp.body).filter(
(user) => resp.body[user].metadata._reserved === true
);
const usernames = Object.keys(resp).filter((user) => resp[user].metadata._reserved === true);
if (!usernames?.length) {
throw new Error('no reserved users found, unable to set native realm passwords');
@ -82,9 +80,7 @@ exports.NativeRealm = class NativeRealm {
async isSecurityEnabled(retryOpts = {}) {
try {
return await this._autoRetry(retryOpts, async () => {
const {
body: { features },
} = await this._client.xpack.info({ categories: 'features' });
const { features } = await this._client.xpack.info({ categories: 'features' });
return features.security && features.security.enabled && features.security.available;
});
} catch (error) {

View file

@ -38,12 +38,10 @@ afterAll(() => {
function mockXPackInfo(available, enabled) {
mockClient.xpack.info.mockImplementation(() => ({
body: {
features: {
security: {
available,
enabled,
},
features: {
security: {
available,
enabled,
},
},
}));
@ -97,31 +95,29 @@ describe('setPasswords', () => {
mockXPackInfo(true, true);
mockClient.security.getUser.mockImplementation(() => ({
body: {
kibana_system: {
metadata: {
_reserved: true,
},
kibana_system: {
metadata: {
_reserved: true,
},
non_native: {
metadata: {
_reserved: false,
},
},
non_native: {
metadata: {
_reserved: false,
},
logstash_system: {
metadata: {
_reserved: true,
},
},
logstash_system: {
metadata: {
_reserved: true,
},
elastic: {
metadata: {
_reserved: true,
},
},
elastic: {
metadata: {
_reserved: true,
},
beats_system: {
metadata: {
_reserved: true,
},
},
beats_system: {
metadata: {
_reserved: true,
},
},
}));
@ -176,21 +172,19 @@ Array [
describe('getReservedUsers', () => {
it('returns array of reserved usernames', async () => {
mockClient.security.getUser.mockImplementation(() => ({
body: {
kibana_system: {
metadata: {
_reserved: true,
},
kibana_system: {
metadata: {
_reserved: true,
},
non_native: {
metadata: {
_reserved: false,
},
},
non_native: {
metadata: {
_reserved: false,
},
logstash_system: {
metadata: {
_reserved: true,
},
},
logstash_system: {
metadata: {
_reserved: true,
},
},
}));

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import type { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { EsQueryConfig } from '@kbn/es-query';
/**

View file

@ -16,15 +16,18 @@ export const createBootstrapIndex = async (
index: string
): Promise<unknown> => {
return (
await esClient.indices.create({
index: `${index}-000001`,
body: {
aliases: {
[index]: {
is_write_index: true,
await esClient.indices.create(
{
index: `${index}-000001`,
body: {
aliases: {
[index]: {
is_write_index: true,
},
},
},
},
})
{ meta: true }
)
).body;
};

View file

@ -23,8 +23,8 @@ export const decodeVersion = (
const parsed = JSON.parse(decoded);
if (Array.isArray(parsed) && Number.isInteger(parsed[0]) && Number.isInteger(parsed[1])) {
return {
ifPrimaryTerm: parsed[1],
ifSeqNo: parsed[0],
if_primary_term: parsed[1],
if_seq_no: parsed[0],
};
} else {
return {};

View file

@ -25,7 +25,7 @@ export const deleteAllIndex = async (
{
index: pattern,
},
{ ignore: [404] }
{ ignore: [404], meta: true }
);
// @ts-expect-error status doesn't exist on response

View file

@ -10,10 +10,7 @@ import type { ElasticsearchClient } from '../elasticsearch_client';
export const deletePolicy = async (
esClient: ElasticsearchClient,
policy: string
name: string
): Promise<unknown> => {
return (
// @ts-expect-error policy_id is required by mistake. fixed in the v8.0
(await esClient.ilm.deleteLifecycle({ policy })).body
);
return (await esClient.ilm.deleteLifecycle({ name }, { meta: true })).body;
};

View file

@ -13,8 +13,11 @@ export const deleteTemplate = async (
name: string
): Promise<unknown> => {
return (
await esClient.indices.deleteTemplate({
name,
})
await esClient.indices.deleteTemplate(
{
name,
},
{ meta: true }
)
).body;
};

View file

@ -9,7 +9,7 @@
// Copied from src/core/server/elasticsearch/client/types.ts
// as these types aren't part of any package yet. Once they are, remove this completely
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import type { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
/**
* Client used to query the elasticsearch cluster.
@ -18,5 +18,5 @@ import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
*/
export type ElasticsearchClient = Omit<
KibanaClient,
'connectionPool' | 'transport' | 'serializer' | 'extend' | 'child' | 'close'
'connectionPool' | 'transport' | 'serializer' | 'extend' | 'child' | 'close' | 'diagnostic'
>;

View file

@ -39,9 +39,12 @@ export const getIndexAliases = async ({
esClient: ElasticsearchClient;
alias: string;
}): Promise<IndexAlias[]> => {
const response = await esClient.indices.getAlias<AliasesResponse>({
name: alias,
});
const response = await esClient.indices.getAlias<AliasesResponse>(
{
name: alias,
},
{ meta: true }
);
return Object.keys(response.body).map((index) => ({
alias,

View file

@ -23,9 +23,12 @@ export const getIndexCount = async ({
esClient: ElasticsearchClient;
index: string;
}): Promise<number> => {
const response = await esClient.count<{ count: number }>({
index,
});
const response = await esClient.count<{ count: number }>(
{
index,
},
{ meta: true }
);
return response.body.count;
};

View file

@ -13,14 +13,17 @@ export const getIndexExists = async (
index: string
): Promise<boolean> => {
try {
const { body: response } = await esClient.search({
index,
size: 0,
allow_no_indices: true,
body: {
terminate_after: 1,
const { body: response } = await esClient.search(
{
index,
size: 0,
allow_no_indices: true,
body: {
terminate_after: 1,
},
},
});
{ meta: true }
);
return response._shards.total > 0;
} catch (err) {
if (err.body != null && err.body.status === 404) {

View file

@ -9,11 +9,11 @@ import type { ElasticsearchClient } from '../elasticsearch_client';
export const getPolicyExists = async (
esClient: ElasticsearchClient,
policy: string
name: string
): Promise<boolean> => {
try {
await esClient.ilm.getLifecycle({
policy,
name,
});
// Return true that there exists a policy which is not 404 or some error
// Since there is not a policy exists API, this is how we create one by calling

View file

@ -13,8 +13,11 @@ export const getTemplateExists = async (
template: string
): Promise<boolean> => {
return (
await esClient.indices.existsTemplate({
name: template,
})
await esClient.indices.existsTemplate(
{
name: template,
},
{ meta: true }
)
).body;
};

View file

@ -9,7 +9,10 @@
import type { ElasticsearchClient } from '../elasticsearch_client';
export const readIndex = async (esClient: ElasticsearchClient, index: string): Promise<unknown> => {
return esClient.indices.get({
index,
});
return esClient.indices.get(
{
index,
},
{ meta: true }
);
};

View file

@ -13,60 +13,63 @@ export const readPrivileges = async (
index: string
): Promise<unknown> => {
return (
await esClient.security.hasPrivileges({
body: {
cluster: [
'all',
'create_snapshot',
'manage',
'manage_api_key',
'manage_ccr',
'manage_transform',
'manage_ilm',
'manage_index_templates',
'manage_ingest_pipelines',
'manage_ml',
'manage_own_api_key',
'manage_pipeline',
'manage_rollup',
'manage_saml',
'manage_security',
'manage_token',
'manage_watcher',
'monitor',
'monitor_transform',
'monitor_ml',
'monitor_rollup',
'monitor_watcher',
'read_ccr',
'read_ilm',
'transport_client',
],
index: [
{
names: [index],
privileges: [
'all',
'create',
'create_doc',
'create_index',
'delete',
'delete_index',
'index',
'manage',
'maintenance',
'manage_follow_index',
'manage_ilm',
'manage_leader_index',
'monitor',
'read',
'read_cross_cluster',
'view_index_metadata',
'write',
],
},
],
await esClient.security.hasPrivileges(
{
body: {
cluster: [
'all',
'create_snapshot',
'manage',
'manage_api_key',
'manage_ccr',
'manage_transform',
'manage_ilm',
'manage_index_templates',
'manage_ingest_pipelines',
'manage_ml',
'manage_own_api_key',
'manage_pipeline',
'manage_rollup',
'manage_saml',
'manage_security',
'manage_token',
'manage_watcher',
'monitor',
'monitor_transform',
'monitor_ml',
'monitor_rollup',
'monitor_watcher',
'read_ccr',
'read_ilm',
'transport_client',
],
index: [
{
names: [index],
privileges: [
'all',
'create',
'create_doc',
'create_index',
'delete',
'delete_index',
'index',
'manage',
'maintenance',
'manage_follow_index',
'manage_ilm',
'manage_leader_index',
'monitor',
'read',
'read_cross_cluster',
'view_index_metadata',
'write',
],
},
],
},
},
})
{ meta: true }
)
).body;
};

View file

@ -9,13 +9,16 @@ import type { ElasticsearchClient } from '../elasticsearch_client';
export const setPolicy = async (
esClient: ElasticsearchClient,
policy: string,
name: string,
body: Record<string, unknown>
): Promise<unknown> => {
return (
await esClient.ilm.putLifecycle({
policy,
body,
})
await esClient.ilm.putLifecycle(
{
name,
body,
},
{ meta: true }
)
).body;
};

View file

@ -14,9 +14,12 @@ export const setTemplate = async (
body: Record<string, unknown>
): Promise<unknown> => {
return (
await esClient.indices.putTemplate({
name,
body,
})
await esClient.indices.putTemplate(
{
name,
body,
},
{ meta: true }
)
).body;
};

View file

@ -23,7 +23,7 @@ import {
} from '@kbn/securitysolution-io-ts-list-types';
import { Filter } from '@kbn/es-query';
import { QueryDslBoolQuery, QueryDslNestedQuery } from '@elastic/elasticsearch/api/types';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { hasLargeValueList } from '../has_large_value_list';
type NonListEntry = EntryMatch | EntryMatchAny | EntryNested | EntryExists;
@ -40,11 +40,11 @@ export type ExceptionItemSansLargeValueLists =
| CreateExceptionListItemNonLargeList;
export interface BooleanFilter {
bool: QueryDslBoolQuery;
bool: estypes.QueryDslBoolQuery;
}
export interface NestedFilter {
nested: QueryDslNestedQuery;
nested: estypes.QueryDslNestedQuery;
}
export const chunkExceptions = (

View file

@ -0,0 +1,35 @@
/*
* 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 { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
import type {
Client,
TransportRequestParams,
TransportRequestOptions,
TransportResult,
} from '@elastic/elasticsearch';
import { Transport } from '@elastic/elasticsearch';
// remove once https://github.com/elastic/kibana/issues/116095 is addressed
class KibanaTransport extends Transport {
request(params: TransportRequestParams, options?: TransportRequestOptions) {
const opts: TransportRequestOptions = options || {};
// Enforce the client to return TransportResult.
// It's required for bwc with responses in 7.x version.
if (opts?.meta === undefined) {
opts.meta = true;
}
return super.request(params, opts) as Promise<TransportResult<any, any>>;
}
}
export function convertToKibanaClient(esClient: Client): KibanaClient {
// @ts-expect-error @elastic/elasticsearch fix discrepancy between clients
return esClient.child({
Transport: KibanaTransport,
});
}

View file

@ -9,3 +9,4 @@
export { createTestEsCluster } from './test_es_cluster';
export type { CreateTestEsClusterOptions, EsTestCluster, ICluster } from './test_es_cluster';
export { esTestConfig } from './es_test_config';
export { convertToKibanaClient } from './client_to_kibana_client';

View file

@ -11,11 +11,12 @@ import { format } from 'url';
import del from 'del';
// @ts-expect-error in js
import { Cluster } from '@kbn/es';
import { Client } from '@elastic/elasticsearch';
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import { Client, HttpConnection } from '@elastic/elasticsearch';
import type { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
import type { ToolingLog } from '@kbn/dev-utils';
import { CI_PARALLEL_PROCESS_PREFIX } from '../ci_parallel_process_prefix';
import { esTestConfig } from './es_test_config';
import { convertToKibanaClient } from './client_to_kibana_client';
import { KIBANA_ROOT } from '../';
@ -51,7 +52,8 @@ export interface ICluster {
start: () => Promise<void>;
stop: () => Promise<void>;
cleanup: () => Promise<void>;
getClient: () => KibanaClient;
getClient: () => Client;
getKibanaEsClient: () => KibanaClient;
getHostUrls: () => string[];
}
@ -280,12 +282,20 @@ export function createTestEsCluster<
/**
* Returns an ES Client to the configured cluster
*/
getClient(): KibanaClient {
getClient(): Client {
return new Client({
node: this.getHostUrls()[0],
Connection: HttpConnection,
});
}
/**
* Returns an ES Client to the configured cluster
*/
getKibanaEsClient(): KibanaClient {
return convertToKibanaClient(this.getClient());
}
getUrl() {
if (this.nodes.length > 1) {
throw new Error(

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { Client } from '@elastic/elasticsearch';
import { Client, HttpConnection } from '@elastic/elasticsearch';
import { createFailError, ToolingLog } from '@kbn/dev-utils';
import { TestFailure } from './get_failures';
@ -34,6 +34,7 @@ export async function reportFailuresToEs(log: ToolingLog, failures: TestFailure[
username: process.env.TEST_FAILURES_ES_USERNAME,
password: process.env.TEST_FAILURES_ES_PASSWORD,
},
Connection: HttpConnection,
});
const body = failures.flatMap((failure) => [
@ -59,7 +60,7 @@ export async function reportFailuresToEs(log: ToolingLog, failures: TestFailure[
},
]);
const resp = await client.bulk({ body });
const resp = await client.bulk({ body }, { meta: true });
if (resp?.body?.errors) {
log.error(JSON.stringify(resp.body.items, null, 2));
}

View file

@ -31,6 +31,7 @@ export {
CreateTestEsClusterOptions,
EsTestCluster,
ICluster,
convertToKibanaClient,
} from './es';
export { kbnTestConfig, kibanaServerTestUser, kibanaTestUser, adminTestUser } from './kbn';

View file

@ -5,12 +5,11 @@
```ts
import { Action } from 'history';
import { ApiResponse } from '@elastic/elasticsearch/lib/Transport';
import Boom from '@hapi/boom';
import { ConfigPath } from '@kbn/config';
import { DetailedPeerCertificate } from 'tls';
import { EnvironmentMode } from '@kbn/config';
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { EuiBreadcrumb } from '@elastic/eui';
import { EuiButtonEmptyProps } from '@elastic/eui';
import { EuiConfirmModalProps } from '@elastic/eui';
@ -20,7 +19,7 @@ import { History } from 'history';
import { Href } from 'history';
import { IconType } from '@elastic/eui';
import { IncomingHttpHeaders } from 'http';
import { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
import { Location } from 'history';
import { LocationDescriptorObject } from 'history';
import { Logger } from '@kbn/logging';
@ -38,9 +37,9 @@ import { RecursiveReadonly } from '@kbn/utility-types';
import { Request } from '@hapi/hapi';
import * as Rx from 'rxjs';
import { SchemaTypeError } from '@kbn/config-schema';
import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport';
import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport';
import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport';
import { TransportRequestOptions } from '@elastic/elasticsearch';
import { TransportRequestParams } from '@elastic/elasticsearch';
import { TransportResult } from '@elastic/elasticsearch';
import { Type } from '@kbn/config-schema';
import { TypeOf } from '@kbn/config-schema';
import { UiCounterMetricType } from '@kbn/analytics';

View file

@ -17,7 +17,7 @@ import {
AggregationsFiltersAggregate,
AggregationsFiltersBucketItem,
SearchTotalHits,
} from '@elastic/elasticsearch/api/types';
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { CoreContext } from '../core_context';
import { ElasticsearchConfigType } from '../elasticsearch/elasticsearch_config';
import { HttpConfigType, InternalHttpServiceSetup } from '../http';

View file

@ -328,10 +328,10 @@ describe('parseClientOptions', () => {
});
});
describe('ssl config', () => {
it('does not generate ssl option is ssl config is not set', () => {
expect(parseClientOptions(createConfig({}), false).ssl).toBeUndefined();
expect(parseClientOptions(createConfig({}), true).ssl).toBeUndefined();
describe('tls config', () => {
it('does not generate tls option is ssl config is not set', () => {
expect(parseClientOptions(createConfig({}), false).tls).toBeUndefined();
expect(parseClientOptions(createConfig({}), true).tls).toBeUndefined();
});
it('handles the `certificateAuthorities` option', () => {
@ -341,7 +341,7 @@ describe('parseClientOptions', () => {
ssl: { verificationMode: 'full', certificateAuthorities: ['content-of-ca-path'] },
}),
false
).ssl!.ca
).tls!.ca
).toEqual(['content-of-ca-path']);
expect(
parseClientOptions(
@ -349,7 +349,7 @@ describe('parseClientOptions', () => {
ssl: { verificationMode: 'full', certificateAuthorities: ['content-of-ca-path'] },
}),
true
).ssl!.ca
).tls!.ca
).toEqual(['content-of-ca-path']);
});
@ -363,7 +363,7 @@ describe('parseClientOptions', () => {
},
}),
false
).ssl
).tls
).toMatchInlineSnapshot(`
Object {
"ca": undefined,
@ -380,7 +380,7 @@ describe('parseClientOptions', () => {
},
}),
false
).ssl
).tls
).toMatchInlineSnapshot(`
Object {
"ca": undefined,
@ -398,7 +398,7 @@ describe('parseClientOptions', () => {
},
}),
false
).ssl
).tls
).toMatchInlineSnapshot(`
Object {
"ca": undefined,
@ -416,7 +416,7 @@ describe('parseClientOptions', () => {
},
}),
false
).ssl
).tls
).toThrowErrorMatchingInlineSnapshot(`"Unknown ssl verificationMode: unknown"`);
});
it('throws for undefined values', () => {
@ -429,7 +429,7 @@ describe('parseClientOptions', () => {
},
}),
false
).ssl
).tls
).toThrowErrorMatchingInlineSnapshot(`"Unknown ssl verificationMode: undefined"`);
});
});
@ -446,7 +446,7 @@ describe('parseClientOptions', () => {
},
}),
false
).ssl
).tls
).toMatchInlineSnapshot(`
Object {
"ca": undefined,
@ -466,7 +466,7 @@ describe('parseClientOptions', () => {
},
}),
false
).ssl
).tls
).toMatchInlineSnapshot(`
Object {
"ca": undefined,
@ -487,7 +487,7 @@ describe('parseClientOptions', () => {
},
}),
false
).ssl
).tls
).toMatchInlineSnapshot(`
Object {
"ca": undefined,
@ -511,7 +511,7 @@ describe('parseClientOptions', () => {
},
}),
true
).ssl
).tls
).toMatchInlineSnapshot(`
Object {
"ca": undefined,
@ -531,7 +531,7 @@ describe('parseClientOptions', () => {
},
}),
true
).ssl
).tls
).toMatchInlineSnapshot(`
Object {
"ca": undefined,

View file

@ -9,7 +9,7 @@
import { ConnectionOptions as TlsConnectionOptions } from 'tls';
import { URL } from 'url';
import { Duration } from 'moment';
import { ClientOptions, NodeOptions } from '@elastic/elasticsearch';
import type { ClientOptions } from '@elastic/elasticsearch/lib/client';
import { ElasticsearchConfig } from '../elasticsearch_config';
import { DEFAULT_HEADERS } from '../default_headers';
@ -93,7 +93,7 @@ export function parseClientOptions(
clientOptions.nodes = config.hosts.map((host) => convertHost(host));
if (config.ssl) {
clientOptions.ssl = generateSslConfig(
clientOptions.tls = generateSslConfig(
config.ssl,
scoped && !config.ssl.alwaysPresentCertificate
);
@ -141,7 +141,7 @@ const generateSslConfig = (
return ssl;
};
const convertHost = (host: string): NodeOptions => {
const convertHost = (host: string): { url: URL } => {
const url = new URL(host);
const isHTTPS = url.protocol === 'https:';
url.port = url.port || (isHTTPS ? '443' : '80');

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { Client } from '@elastic/elasticsearch';
import type { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
import { Logger } from '../../logging';
import { GetAuthHeaders, Headers, isKibanaRequest, isRealRequest } from '../../http';
import { ensureRawRequest, filterHeaders } from '../../http/router';
@ -52,8 +52,8 @@ export interface ICustomClusterClient extends IClusterClient {
/** @internal **/
export class ClusterClient implements ICustomClusterClient {
public readonly asInternalUser: Client;
private readonly rootScopedClient: Client;
public readonly asInternalUser: KibanaClient;
private readonly rootScopedClient: KibanaClient;
private readonly allowListHeaders: string[];
private isClosed = false;

View file

@ -9,13 +9,13 @@
import { Buffer } from 'buffer';
import { Readable } from 'stream';
import { RequestEvent, errors } from '@elastic/elasticsearch';
import type { Client } from '@elastic/elasticsearch';
import { errors } from '@elastic/elasticsearch';
import type {
TransportRequestOptions,
TransportRequestParams,
DiagnosticResult,
RequestBody,
} from '@elastic/elasticsearch/lib/Transport';
} from '@elastic/elasticsearch';
import { parseClientOptionsMock, ClientMock } from './configure_client.test.mocks';
import { loggingSystemMock } from '../../logging/logging_system.mock';
@ -36,7 +36,7 @@ const createFakeClient = () => {
const client = new actualEs.Client({
nodes: ['http://localhost'], // Enforcing `nodes` because it's mandatory
});
jest.spyOn(client, 'on');
jest.spyOn(client.diagnostic, 'on');
return client;
};
@ -54,7 +54,7 @@ const createApiResponse = <T>({
warnings?: string[];
params?: TransportRequestParams;
requestOptions?: TransportRequestOptions;
}): RequestEvent<T> => {
}): DiagnosticResult<T> => {
return {
body,
statusCode,
@ -70,14 +70,6 @@ const createApiResponse = <T>({
};
};
function getProductCheckValue(client: Client) {
const tSymbol = Object.getOwnPropertySymbols(client.transport || client).filter(
(symbol) => symbol.description === 'product check'
)[0];
// @ts-expect-error `tSymbol` is missing in the index signature of Transport
return (client.transport || client)[tSymbol];
}
describe('configureClient', () => {
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
let config: ElasticsearchClientConfig;
@ -124,26 +116,8 @@ describe('configureClient', () => {
it('listens to client on `response` events', () => {
const client = configureClient(config, { logger, type: 'test', scoped: false });
expect(client.on).toHaveBeenCalledTimes(1);
expect(client.on).toHaveBeenCalledWith('response', expect.any(Function));
});
describe('Product check', () => {
it('should not skip the product check for the unscoped client', () => {
const client = configureClient(config, { logger, type: 'test', scoped: false });
expect(getProductCheckValue(client)).toBe(0);
});
it('should skip the product check for the scoped client', () => {
const client = configureClient(config, { logger, type: 'test', scoped: true });
expect(getProductCheckValue(client)).toBe(2);
});
it('should skip the product check for the children of the scoped client', () => {
const client = configureClient(config, { logger, type: 'test', scoped: true });
const asScoped = client.child({ headers: { 'x-custom-header': 'Custom value' } });
expect(getProductCheckValue(asScoped)).toBe(2);
});
expect(client.diagnostic.on).toHaveBeenCalledTimes(1);
expect(client.diagnostic.on).toHaveBeenCalledWith('response', expect.any(Function));
});
describe('Client logging', () => {
@ -176,7 +150,7 @@ describe('configureClient', () => {
},
});
client.emit('response', null, response);
client.diagnostic.emit('response', null, response);
expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(`
Array [
Array [
@ -201,7 +175,7 @@ describe('configureClient', () => {
})
);
client.emit('response', null, response);
client.diagnostic.emit('response', null, response);
expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(`
Array [
Array [
@ -228,7 +202,7 @@ describe('configureClient', () => {
)
);
client.emit('response', null, response);
client.diagnostic.emit('response', null, response);
expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(`
Array [
Array [
@ -255,7 +229,7 @@ describe('configureClient', () => {
)
);
client.emit('response', null, response);
client.diagnostic.emit('response', null, response);
expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(`
Array [
Array [
@ -273,7 +247,7 @@ describe('configureClient', () => {
const response = createResponseWithBody();
client.emit('response', null, response);
client.diagnostic.emit('response', null, response);
expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(`
Array [
Array [
@ -298,7 +272,7 @@ describe('configureClient', () => {
},
});
client.emit('response', null, response);
client.diagnostic.emit('response', null, response);
expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(`
Array [
@ -333,7 +307,7 @@ describe('configureClient', () => {
},
},
});
client.emit('response', new errors.ResponseError(response), response);
client.diagnostic.emit('response', new errors.ResponseError(response), response);
expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(`
Array [
@ -351,7 +325,7 @@ describe('configureClient', () => {
const client = configureClient(createFakeConfig(), { logger, type: 'test', scoped: false });
const response = createApiResponse({ body: {} });
client.emit('response', new errors.TimeoutError('message', response), response);
client.diagnostic.emit('response', new errors.TimeoutError('message', response), response);
expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(`
Array [
@ -381,7 +355,7 @@ describe('configureClient', () => {
},
},
});
client.emit('response', new errors.ResponseError(response), response);
client.diagnostic.emit('response', new errors.ResponseError(response), response);
expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(`
Array [
@ -397,7 +371,7 @@ describe('configureClient', () => {
it('logs default error info when the error response body is empty', () => {
const client = configureClient(createFakeConfig(), { logger, type: 'test', scoped: false });
let response: RequestEvent<any, any> = createApiResponse({
let response: DiagnosticResult<any, any> = createApiResponse({
statusCode: 400,
headers: {},
params: {
@ -408,7 +382,7 @@ describe('configureClient', () => {
error: {},
},
});
client.emit('response', new errors.ResponseError(response), response);
client.diagnostic.emit('response', new errors.ResponseError(response), response);
expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(`
Array [
@ -431,7 +405,7 @@ describe('configureClient', () => {
},
body: undefined,
});
client.emit('response', new errors.ResponseError(response), response);
client.diagnostic.emit('response', new errors.ResponseError(response), response);
expect(loggingSystemMock.collect(logger).debug).toMatchInlineSnapshot(`
Array [
@ -461,7 +435,7 @@ describe('configureClient', () => {
error: {},
},
});
client.emit('response', null, response);
client.diagnostic.emit('response', null, response);
expect(loggingSystemMock.collect(logger).debug[0][1]).toMatchInlineSnapshot(`
Object {
@ -487,7 +461,7 @@ describe('configureClient', () => {
},
body: {} as any,
});
client.emit('response', new errors.ResponseError(response), response);
client.diagnostic.emit('response', new errors.ResponseError(response), response);
expect(loggingSystemMock.collect(logger).debug[0][1]).toMatchInlineSnapshot(`
Object {

View file

@ -8,14 +8,19 @@
import { Buffer } from 'buffer';
import { stringify } from 'querystring';
import { ApiError, Client, RequestEvent, errors, Transport } from '@elastic/elasticsearch';
import { Client, errors, Transport, HttpConnection } from '@elastic/elasticsearch';
import type { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
import type {
RequestBody,
TransportRequestParams,
TransportRequestOptions,
} from '@elastic/elasticsearch/lib/Transport';
TransportResult,
DiagnosticResult,
RequestBody,
} from '@elastic/elasticsearch';
import { Logger } from '../../logging';
import { parseClientOptions, ElasticsearchClientConfig } from './client_config';
import type { ElasticsearchErrorDetails } from './types';
const noop = () => undefined;
@ -32,30 +37,33 @@ export const configureClient = (
scoped?: boolean;
getExecutionContext?: () => string | undefined;
}
): Client => {
): KibanaClient => {
const clientOptions = parseClientOptions(config, scoped);
class KibanaTransport extends Transport {
request(params: TransportRequestParams, options?: TransportRequestOptions) {
const opts = options || {};
const opts: TransportRequestOptions = options || {};
const opaqueId = getExecutionContext();
if (opaqueId && !opts.opaqueId) {
// rewrites headers['x-opaque-id'] if it presents
opts.opaqueId = opaqueId;
}
return super.request(params, opts);
// Enforce the client to return TransportResult.
// It's required for bwc with responses in 7.x version.
if (opts.meta === undefined) {
opts.meta = true;
}
return super.request(params, opts) as Promise<TransportResult<any, any>>;
}
}
const client = new Client({ ...clientOptions, Transport: KibanaTransport });
const client = new Client({
...clientOptions,
Transport: KibanaTransport,
Connection: HttpConnection,
});
addLogging(client, logger.get('query', type));
// --------------------------------------------------------------------------------- //
// Hack to disable the "Product check" only in the scoped clients while we //
// come up with a better approach in https://github.com/elastic/kibana/issues/110675 //
if (scoped) skipProductCheck(client);
// --------------------------------------------------------------------------------- //
return client;
return client as KibanaClient;
};
const convertQueryString = (qs: string | Record<string, any> | undefined): string => {
@ -76,9 +84,10 @@ function ensureString(body: RequestBody): string {
* Returns a debug message from an Elasticsearch error in the following format:
* [error type] error reason
*/
export function getErrorMessage(error: ApiError): string {
export function getErrorMessage(error: errors.ElasticsearchClientError): string {
if (error instanceof errors.ResponseError) {
return `[${error.meta.body?.error?.type}]: ${error.meta.body?.error?.reason ?? error.message}`;
const errorBody = error.meta.body as ElasticsearchErrorDetails;
return `[${errorBody?.error?.type}]: ${errorBody?.error?.reason ?? error.message}`;
}
return `[${error.name}]: ${error.message}`;
}
@ -92,7 +101,7 @@ export function getErrorMessage(error: ApiError): string {
*
* so it could be copy-pasted into the Dev console
*/
function getResponseMessage(event: RequestEvent): string {
function getResponseMessage(event: DiagnosticResult): string {
const errorMeta = getRequestDebugMeta(event);
const body = errorMeta.body ? `\n${errorMeta.body}` : '';
return `${errorMeta.statusCode}\n${errorMeta.method} ${errorMeta.url}${body}`;
@ -102,7 +111,7 @@ function getResponseMessage(event: RequestEvent): string {
* Returns stringified debug information from an Elasticsearch request event
* useful for logging in case of an unexpected failure.
*/
export function getRequestDebugMeta(event: RequestEvent): {
export function getRequestDebugMeta(event: DiagnosticResult): {
url: string;
body: string;
statusCode: number | null;
@ -115,12 +124,12 @@ export function getRequestDebugMeta(event: RequestEvent): {
url: `${params.path}${querystring ? `?${querystring}` : ''}`,
body: params.body ? `${ensureString(params.body)}` : '',
method: params.method,
statusCode: event.statusCode,
statusCode: event.statusCode!,
};
}
const addLogging = (client: Client, logger: Logger) => {
client.on('response', (error, event) => {
client.diagnostic.on('response', (error, event) => {
if (event) {
const opaqueId = event.meta.request.options.opaqueId;
const meta = opaqueId
@ -140,21 +149,3 @@ const addLogging = (client: Client, logger: Logger) => {
}
});
};
/**
* Hack to skip the Product Check performed by the Elasticsearch-js client.
* We noticed that the scoped clients are always performing this check because
* of the way we initialize the clients. We'll discuss changing this in the issue
* https://github.com/elastic/kibana/issues/110675. In the meanwhile, let's skip
* it for the scoped clients.
*
* The hack is copied from the test/utils in the elasticsearch-js repo
* (https://github.com/elastic/elasticsearch-js/blob/master/test/utils/index.js#L45-L56)
*/
function skipProductCheck(client: Client) {
const tSymbol = Object.getOwnPropertySymbols(client.transport || client).filter(
(symbol) => symbol.description === 'product check'
)[0];
// @ts-expect-error `tSymbol` is missing in the index signature of Transport
(client.transport || client)[tSymbol] = 2;
}

View file

@ -6,12 +6,8 @@
* Side Public License, v 1.
*/
import {
ResponseError,
ConnectionError,
ConfigurationError,
} from '@elastic/elasticsearch/lib/errors';
import { ApiResponse } from '@elastic/elasticsearch';
import { errors } from '@elastic/elasticsearch';
import type { TransportResult } from '@elastic/elasticsearch';
import { isResponseError, isUnauthorizedError } from './errors';
const createApiResponseError = ({
@ -22,7 +18,7 @@ const createApiResponseError = ({
statusCode?: number;
headers?: Record<string, string>;
body?: Record<string, any>;
} = {}): ApiResponse => {
} = {}): TransportResult => {
return {
body,
statusCode,
@ -34,38 +30,42 @@ const createApiResponseError = ({
describe('isResponseError', () => {
it('returns `true` when the input is a `ResponseError`', () => {
expect(isResponseError(new ResponseError(createApiResponseError()))).toBe(true);
expect(isResponseError(new errors.ResponseError(createApiResponseError()))).toBe(true);
});
it('returns `false` when the input is not a `ResponseError`', () => {
expect(isResponseError(new Error('foo'))).toBe(false);
expect(isResponseError(new ConnectionError('error', createApiResponseError()))).toBe(false);
expect(isResponseError(new ConfigurationError('foo'))).toBe(false);
expect(isResponseError(new errors.ConnectionError('error', createApiResponseError()))).toBe(
false
);
expect(isResponseError(new errors.ConfigurationError('foo'))).toBe(false);
});
});
describe('isUnauthorizedError', () => {
it('returns true when the input is a `ResponseError` and statusCode === 401', () => {
expect(
isUnauthorizedError(new ResponseError(createApiResponseError({ statusCode: 401 })))
isUnauthorizedError(new errors.ResponseError(createApiResponseError({ statusCode: 401 })))
).toBe(true);
});
it('returns false when the input is a `ResponseError` and statusCode !== 401', () => {
expect(
isUnauthorizedError(new ResponseError(createApiResponseError({ statusCode: 200 })))
isUnauthorizedError(new errors.ResponseError(createApiResponseError({ statusCode: 200 })))
).toBe(false);
expect(
isUnauthorizedError(new ResponseError(createApiResponseError({ statusCode: 403 })))
isUnauthorizedError(new errors.ResponseError(createApiResponseError({ statusCode: 403 })))
).toBe(false);
expect(
isUnauthorizedError(new ResponseError(createApiResponseError({ statusCode: 500 })))
isUnauthorizedError(new errors.ResponseError(createApiResponseError({ statusCode: 500 })))
).toBe(false);
});
it('returns `false` when the input is not a `ResponseError`', () => {
expect(isUnauthorizedError(new Error('foo'))).toBe(false);
expect(isUnauthorizedError(new ConnectionError('error', createApiResponseError()))).toBe(false);
expect(isUnauthorizedError(new ConfigurationError('foo'))).toBe(false);
expect(isUnauthorizedError(new errors.ConnectionError('error', createApiResponseError()))).toBe(
false
);
expect(isUnauthorizedError(new errors.ConfigurationError('foo'))).toBe(false);
});
});

View file

@ -6,14 +6,14 @@
* Side Public License, v 1.
*/
import { ResponseError } from '@elastic/elasticsearch/lib/errors';
import { errors } from '@elastic/elasticsearch';
export type UnauthorizedError = ResponseError & {
export type UnauthorizedError = errors.ResponseError & {
statusCode: 401;
};
export function isResponseError(error: unknown): error is ResponseError {
return error instanceof ResponseError;
export function isResponseError(error: unknown): error is errors.ResponseError {
return error instanceof errors.ResponseError;
}
export function isUnauthorizedError(error: unknown): error is UnauthorizedError {

View file

@ -14,6 +14,7 @@ export type {
SearchResponse,
GetResponse,
DeleteDocumentResponse,
ElasticsearchErrorDetails,
} from './types';
export { ScopedClusterClient } from './scoped_cluster_client';
export type { IScopedClusterClient } from './scoped_cluster_client';

View file

@ -39,9 +39,9 @@ describe('Mocked client', () => {
});
it('used EventEmitter functions should be mocked', () => {
expectMocked(client.on);
expectMocked(client.off);
expectMocked(client.once);
expectMocked(client.diagnostic.on);
expectMocked(client.diagnostic.off);
expectMocked(client.diagnostic.once);
});
it('`child` should be mocked and return a mocked Client', () => {

View file

@ -6,36 +6,33 @@
* Side Public License, v 1.
*/
import type { Client, ApiResponse } from '@elastic/elasticsearch';
import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport';
import type { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
import type { TransportResult } from '@elastic/elasticsearch';
import type { DeeplyMockedKeys } from '@kbn/utility-types/jest';
import type { PublicKeys } from '@kbn/utility-types';
import { ElasticsearchClient } from './types';
import { ICustomClusterClient } from './cluster_client';
import { PRODUCT_RESPONSE_HEADER } from '../supported_server_response_check';
const omittedProps = [
'diagnostic',
'name',
'connectionPool',
'transport',
'serializer',
'helpers',
] as Array<PublicKeys<KibanaClient>>;
// the product header expected in every response from es
const PRODUCT_RESPONSE_HEADER = 'x-elastic-product';
// use jest.requireActual() to prevent weird errors when people mock @elastic/elasticsearch
const { Client: UnmockedClient } = jest.requireActual('@elastic/elasticsearch');
const createInternalClientMock = (
res?: MockedTransportRequestPromise<unknown>
): DeeplyMockedKeys<Client> => {
const createInternalClientMock = (res?: Promise<unknown>): DeeplyMockedKeys<KibanaClient> => {
// we mimic 'reflection' on a concrete instance of the client to generate the mocked functions.
const client = new UnmockedClient({
node: 'http://localhost',
node: 'http://127.0.0.1',
});
const omittedProps = [
'_events',
'_eventsCount',
'_maxListeners',
'constructor',
'name',
'serializer',
'connectionPool',
'transport',
'helpers',
];
const getAllPropertyDescriptors = (obj: Record<string, any>) => {
const descriptors = Object.entries(Object.getOwnPropertyDescriptors(obj));
let prototype = Object.getPrototypeOf(obj);
@ -77,21 +74,21 @@ const createInternalClientMock = (
};
// `on`, `off`, and `once` are properties without a setter.
// We can't `client.on = jest.fn()` because the following error will be thrown:
// We can't `client.diagnostic.on = jest.fn()` because the following error will be thrown:
// TypeError: Cannot set property on of #<Client> which has only a getter
mockGetter(client, 'on');
mockGetter(client, 'off');
mockGetter(client, 'once');
mockGetter(client.diagnostic, 'on');
mockGetter(client.diagnostic, 'off');
mockGetter(client.diagnostic, 'once');
client.transport = {
request: jest.fn(),
};
return client as DeeplyMockedKeys<Client>;
return client as DeeplyMockedKeys<KibanaClient>;
};
export type ElasticsearchClientMock = DeeplyMockedKeys<ElasticsearchClient>;
const createClientMock = (res?: MockedTransportRequestPromise<unknown>): ElasticsearchClientMock =>
const createClientMock = (res?: Promise<unknown>): ElasticsearchClientMock =>
createInternalClientMock(res) as unknown as ElasticsearchClientMock;
export interface ScopedClusterClientMock {
@ -139,31 +136,23 @@ const createCustomClusterClientMock = () => {
return mock;
};
export type MockedTransportRequestPromise<T> = TransportRequestPromise<T> & {
abort: jest.MockedFunction<() => undefined>;
};
const createSuccessTransportRequestPromise = <T>(
body: T,
{ statusCode = 200 }: { statusCode?: number } = {},
headers: Record<string, string | string[]> = { [PRODUCT_RESPONSE_HEADER]: 'Elasticsearch' }
): MockedTransportRequestPromise<ApiResponse<T>> => {
): Promise<TransportResult<T>> => {
const response = createApiResponse({ body, statusCode, headers });
const promise = Promise.resolve(response);
(promise as MockedTransportRequestPromise<ApiResponse<T>>).abort = jest.fn();
return promise as MockedTransportRequestPromise<ApiResponse<T>>;
return Promise.resolve(response) as Promise<TransportResult<T>>;
};
const createErrorTransportRequestPromise = (err: any): MockedTransportRequestPromise<never> => {
const promise = Promise.reject(err);
(promise as MockedTransportRequestPromise<never>).abort = jest.fn();
return promise as MockedTransportRequestPromise<never>;
const createErrorTransportRequestPromise = (err: any): Promise<TransportResult<never>> => {
return Promise.reject(err);
};
function createApiResponse<TResponse = Record<string, any>>(
opts: Partial<ApiResponse<TResponse>> = {}
): ApiResponse<TResponse> {
opts: Partial<TransportResult<TResponse>> = {}
): TransportResult<TResponse> {
return {
body: {} as any,
statusCode: 200,

View file

@ -6,13 +6,12 @@
* Side Public License, v 1.
*/
import type { KibanaClient } from '@elastic/elasticsearch/api/kibana';
import type { KibanaClient } from '@elastic/elasticsearch/lib/api/kibana';
import type {
ApiResponse,
TransportResult,
TransportRequestOptions,
TransportRequestParams,
TransportRequestPromise,
} from '@elastic/elasticsearch/lib/Transport';
} from '@elastic/elasticsearch';
/**
* Client used to query the elasticsearch cluster.
@ -21,13 +20,13 @@ import type {
*/
export type ElasticsearchClient = Omit<
KibanaClient,
'connectionPool' | 'transport' | 'serializer' | 'extend' | 'child' | 'close'
'connectionPool' | 'transport' | 'serializer' | 'extend' | 'child' | 'close' | 'diagnostic'
> & {
transport: {
request(
request<TResponse = unknown>(
params: TransportRequestParams,
options?: TransportRequestOptions
): TransportRequestPromise<ApiResponse>;
): Promise<TransportResult<TResponse>>;
};
};
@ -133,3 +132,10 @@ export interface DeleteDocumentResponse {
type: string;
};
}
/**
* @public
*/
export interface ElasticsearchErrorDetails {
error?: { type: string; reason?: string };
}

View file

@ -35,10 +35,6 @@ export type {
ShardsResponse,
GetResponse,
DeleteDocumentResponse,
ElasticsearchErrorDetails,
} from './client';
export { getRequestDebugMeta, getErrorMessage } from './client';
export {
isSupportedEsServer,
isNotFoundFromUnsupportedServer,
PRODUCT_RESPONSE_HEADER,
} from './supported_server_response_check';

View file

@ -52,17 +52,6 @@ describe('elasticsearch clients', () => {
);
expect(resp2.headers).not.toHaveProperty('warning');
});
it('returns deprecation warning when x-elastic-product-orign header is not set', async () => {
const resp =
await kibanaServer.coreStart.elasticsearch.client.asInternalUser.indices.getSettings(
{ index: '.kibana' },
{ headers: { 'x-elastic-product-origin': null } }
);
expect(resp.headers).toHaveProperty('warning');
expect(resp.headers!.warning).toMatch('system indices');
});
});
function createFakeElasticsearchServer() {

View file

@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
import { estypes } from '@elastic/elasticsearch';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { elasticsearchServiceMock } from './elasticsearch_service.mock';
import { isInlineScriptingEnabled } from './is_scripting_enabled';

View file

@ -1,41 +0,0 @@
/*
* 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 { isNotFoundFromUnsupportedServer } from './supported_server_response_check';
describe('#isNotFoundFromUnsupportedServer', () => {
it('returns true with not found response from unsupported server', () => {
const rawResponse = {
statusCode: 404,
headers: {},
};
const result = isNotFoundFromUnsupportedServer(rawResponse);
expect(result).toBe(true);
});
it('returns false with not found response from supported server', async () => {
const rawResponse = {
statusCode: 404,
headers: { 'x-elastic-product': 'Elasticsearch' },
};
const result = isNotFoundFromUnsupportedServer(rawResponse);
expect(result).toBe(false);
});
it('returns false when not a 404', async () => {
const rawResponse = {
statusCode: 200,
headers: { 'x-elastic-product': 'Elasticsearch' },
};
const result = isNotFoundFromUnsupportedServer(rawResponse);
expect(result).toBe(false);
});
});

View file

@ -1,33 +0,0 @@
/*
* 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 const PRODUCT_RESPONSE_HEADER = 'x-elastic-product';
/**
* Response headers check to determine if the response is from Elasticsearch
* @param headers Response headers
* @returns boolean
*/
// This check belongs to the elasticsearch service as a dedicated helper method.
export const isSupportedEsServer = (headers: Record<string, string | string[]> | null) => {
return !!headers && headers[PRODUCT_RESPONSE_HEADER] === 'Elasticsearch';
};
/**
* Check to ensure that a 404 response does not come from Elasticsearch
*
* WARNING: This is a hack to work around for 404 responses returned from a proxy.
* We're aiming to minimise the risk of data loss when consumers act on Not Found errors
*
* @param response response from elasticsearch client call
* @returns boolean 'true' if the status code is 404 and the Elasticsearch product header is missing/unexpected value
*/
export const isNotFoundFromUnsupportedServer = (args: {
statusCode: number | null;
headers: Record<string, string | string[]> | null;
}): boolean => {
return args.statusCode === 404 && !isSupportedEsServer(args.headers);
};

View file

@ -139,6 +139,7 @@ describe('pollEsNodesVersion', () => {
});
const nodeInfosSuccessOnce = (infos: NodesInfo) => {
// @ts-expect-error not full interface
internalClient.nodes.info.mockImplementationOnce(() => createEsSuccess(infos));
};
const nodeInfosErrorOnce = (error: any) => {

View file

@ -8,7 +8,7 @@
import { MockElasticsearchClient } from './core_service.test.mocks';
import { elasticsearchClientMock } from '../../elasticsearch/client/mocks';
import { ResponseError } from '@elastic/elasticsearch/lib/errors';
import { errors } from '@elastic/elasticsearch';
import * as kbnTestServer from '../../../test_helpers/kbn_server';
import { InternalElasticsearchServiceStart } from '../../elasticsearch';
@ -205,7 +205,7 @@ describe('http service', () => {
esClient.ping.mockImplementation(() =>
elasticsearchClientMock.createErrorTransportRequestPromise(
new ResponseError({
new errors.ResponseError({
statusCode: 401,
body: {
error: {
@ -243,7 +243,7 @@ describe('http service', () => {
esClient.ping.mockImplementation(() =>
elasticsearchClientMock.createErrorTransportRequestPromise(
new ResponseError({
new errors.ResponseError({
statusCode: 401,
body: {
error: {
@ -279,7 +279,7 @@ describe('http service', () => {
esClient.ping.mockImplementation(() =>
elasticsearchClientMock.createErrorTransportRequestPromise(
new ResponseError({
new errors.ResponseError({
statusCode: 404,
body: {
error: {

View file

@ -15,6 +15,7 @@ import Boom from '@hapi/boom';
import * as stream from 'stream';
import { isResponseError as isElasticsearchResponseError } from '../../elasticsearch/client/errors';
import { ElasticsearchErrorDetails } from '../../elasticsearch';
import {
HttpResponsePayload,
@ -154,7 +155,9 @@ function getErrorMessage(payload?: ResponseError): string {
if (typeof payload === 'string') return payload;
// for ES response errors include nested error reason message. it doesn't contain sensitive data.
if (isElasticsearchResponseError(payload)) {
return `[${payload.message}]: ${payload.meta.body?.error?.reason}`;
return `[${payload.message}]: ${
(payload.meta.body as ElasticsearchErrorDetails)?.error?.reason
}`;
}
return getErrorMessage(payload.message);

View file

@ -289,10 +289,10 @@ export class Router<Context extends RequestHandlerContext = RequestHandlerContex
const convertEsUnauthorized = (e: EsNotAuthorizedError): ErrorHttpResponseOptions => {
const getAuthenticateHeaderValue = () => {
const header = Object.entries(e.headers).find(
const header = Object.entries(e.headers || {}).find(
([key]) => key.toLowerCase() === 'www-authenticate'
);
return header ? header[1] : 'Basic realm="Authorization Required"';
return header ? (header[1] as string) : 'Basic realm="Authorization Required"';
};
return {
body: e.message,

View file

@ -136,6 +136,7 @@ export type {
GetResponse,
DeleteDocumentResponse,
ElasticsearchConfigPreboot,
ElasticsearchErrorDetails,
} from './elasticsearch';
export type { IExternalUrlConfig, IExternalUrlPolicy } from './external_url';

View file

@ -8,7 +8,7 @@
import { getIndexForTypeMock } from './unknown_object_types.test.mocks';
import { estypes } from '@elastic/elasticsearch';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { deleteUnknownTypeObjects, getUnknownTypesDeprecations } from './unknown_object_types';
import { typeRegistryMock } from '../saved_objects_type_registry.mock';
import { elasticsearchClientMock } from '../../elasticsearch/client/mocks';

Some files were not shown because too many files have changed in this diff Show more