Migrate Index Management and enrichers to the new ES JS client (#105863)

* Destructure index API request bodies consistently.
* Remove unnecessary calls to encodeURIComponent on the server.
* Migrate routes to handleEsError. Delete wrapEsError helpers. Remove unused isEsError and parseEsError dependencies. Remove isEsError from es_ui_shared.
* Update tests and migrate API integration tests.
* Clarify test details in CCR README. Update Index Management README with steps for testing Cloud-managed index templates and steps for testing indices and data streams that contain special characters.
This commit is contained in:
CJ Cenizal 2021-08-02 12:20:54 -05:00 committed by GitHub
parent 5e8b24230a
commit 3491f05e95
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 682 additions and 1135 deletions

View file

@ -457,7 +457,7 @@ Index Management by running this series of requests in Console:
|{kib-repo}blob/{branch}/x-pack/plugins/index_management/README.md[indexManagement] |{kib-repo}blob/{branch}/x-pack/plugins/index_management/README.md[indexManagement]
|Create a data stream using Console and you'll be able to view it in the UI: |Create an index with special characters and verify it renders correctly:
|{kib-repo}blob/{branch}/x-pack/plugins/infra/README.md[infra] |{kib-repo}blob/{branch}/x-pack/plugins/infra/README.md[infra]

View file

@ -6,6 +6,5 @@
* Side Public License, v 1. * Side Public License, v 1.
*/ */
export { isEsError } from './is_es_error';
export { handleEsError } from './handle_es_error'; export { handleEsError } from './handle_es_error';
export { parseEsError } from './es_error_parser'; export { parseEsError } from './es_error_parser';

View file

@ -1,26 +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 * as legacyElasticsearch from 'elasticsearch';
const esErrorsParent = legacyElasticsearch.errors._Abstract;
interface RequestError extends Error {
statusCode?: number;
}
/*
* @deprecated
* Only works with legacy elasticsearch js client errors and will be removed after 7.x last
*/
export function isEsError(err: RequestError) {
const isInstanceOfEsError = err instanceof esErrorsParent;
const hasStatusCode = Boolean(err.statusCode);
return isInstanceOfEsError && hasStatusCode;
}

View file

@ -6,4 +6,4 @@
* Side Public License, v 1. * Side Public License, v 1.
*/ */
export { isEsError, handleEsError, parseEsError } from '../../__packages_do_not_import__/errors'; export { handleEsError, parseEsError } from '../../__packages_do_not_import__/errors';

View file

@ -6,7 +6,7 @@
* Side Public License, v 1. * Side Public License, v 1.
*/ */
export { isEsError, handleEsError, parseEsError } from './errors'; export { handleEsError, parseEsError } from './errors';
/** dummy plugin*/ /** dummy plugin*/
export function plugin() { export function plugin() {

View file

@ -4,14 +4,15 @@
You can run a local cluster and simulate a remote cluster within a single Kibana directory. You can run a local cluster and simulate a remote cluster within a single Kibana directory.
1. Ensure Kibana isn't running so it doesn't load up any data into your cluster. Run `yarn es snapshot --license=trial` to install a fresh snapshot. Wait for ES to finish setting up. 1. Ensure Kibana isn't running so it doesn't load up any data into your cluster. Run `yarn es snapshot --license=trial` to install a fresh snapshot. Wait for ES to finish setting up and activate the license.
2. Create a "remote" copy of your ES snapshot by running: `cp -R .es/8.0.0 .es/8.0.0-2`. 2. Create a "remote" copy of your ES snapshot by running: `cp -R .es/8.0.0 .es/8.0.0-2`.
4. Start your "remote" cluster by running `.es/8.0.0-2/bin/elasticsearch -E cluster.name=europe -E transport.port=9400`. 4. Start your "local" cluster by running `.es/8.0.0/bin/elasticsearch`.
4. Run `yarn start` to start Kibana. 5. Run `yarn start` to start Kibana so that it connects to the "local" cluster.
5. Index a document into your remote cluster by running `curl -X PUT http://elastic:changeme@localhost:9201/my-leader-index --data '{"settings":{"number_of_shards":1,"soft_deletes.enabled":true}}' --header "Content-Type: application/json"`. Note that these settings are required for testing auto-follow pattern conflicts errors (see below). 6. Start your "remote" cluster by running `.es/8.0.0-2/bin/elasticsearch -E cluster.name=europe -E transport.port=9400`.
7. Index a document into your "remote" cluster by running `curl -X PUT http://elastic:changeme@localhost:9201/my-leader-index --data '{"settings":{"number_of_shards":1,"soft_deletes.enabled":true}}' --header "Content-Type: application/json"`. Note that these settings are required for testing auto-follow pattern conflicts errors (see below).
Now you can create follower indices and auto-follow patterns to replicate the `my-leader-index` Now you can create follower indices and auto-follow patterns to replicate the `my-leader-index`
index on the remote cluster that's available at `127.0.0.1:9400`. index on the "remote" cluster that's available at `127.0.0.1:9400`.
### Auto-follow pattern conflict errors ### Auto-follow pattern conflict errors

View file

@ -7,14 +7,8 @@
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { first } from 'rxjs/operators'; import { first } from 'rxjs/operators';
import { import { CoreSetup, CoreStart, Plugin, Logger, PluginInitializerContext } from 'src/core/server';
CoreSetup, import { IScopedClusterClient } from 'kibana/server';
CoreStart,
Plugin,
Logger,
PluginInitializerContext,
LegacyAPICaller,
} from 'src/core/server';
import { Index } from '../../index_management/server'; import { Index } from '../../index_management/server';
import { PLUGIN } from '../common/constants'; import { PLUGIN } from '../common/constants';
@ -23,20 +17,18 @@ import { registerApiRoutes } from './routes';
import { CrossClusterReplicationConfig } from './config'; import { CrossClusterReplicationConfig } from './config';
import { License, handleEsError } from './shared_imports'; import { License, handleEsError } from './shared_imports';
// TODO replace deprecated ES client after Index Management is updated const ccrDataEnricher = async (indicesList: Index[], client: IScopedClusterClient) => {
const ccrDataEnricher = async (indicesList: Index[], callWithRequest: LegacyAPICaller) => {
if (!indicesList?.length) { if (!indicesList?.length) {
return indicesList; return indicesList;
} }
const params = {
path: '/_all/_ccr/info',
method: 'GET',
};
try { try {
const { follower_indices: followerIndices } = await callWithRequest( const {
'transport.request', body: { follower_indices: followerIndices },
params } = await client.asCurrentUser.ccr.followInfo({
); index: '_all',
});
return indicesList.map((index) => { return indicesList.map((index) => {
const isFollowerIndex = !!followerIndices.find( const isFollowerIndex = !!followerIndices.find(
(followerIndex: { follower_index: string }) => { (followerIndex: { follower_index: string }) => {

View file

@ -6,44 +6,36 @@
*/ */
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import { import { CoreSetup, Plugin, Logger, PluginInitializerContext } from 'src/core/server';
CoreSetup, import { IScopedClusterClient } from 'kibana/server';
Plugin,
Logger,
PluginInitializerContext,
LegacyAPICaller,
} from 'src/core/server';
import { handleEsError } from './shared_imports';
import { Index as IndexWithoutIlm } from '../../index_management/common/types'; import { Index as IndexWithoutIlm } from '../../index_management/common/types';
import { PLUGIN } from '../common/constants'; import { PLUGIN } from '../common/constants';
import { Index, IndexLifecyclePolicy } from '../common/types'; import { Index } from '../common/types';
import { Dependencies } from './types'; import { Dependencies } from './types';
import { registerApiRoutes } from './routes'; import { registerApiRoutes } from './routes';
import { License } from './services'; import { License } from './services';
import { IndexLifecycleManagementConfig } from './config'; import { IndexLifecycleManagementConfig } from './config';
import { handleEsError } from './shared_imports';
const indexLifecycleDataEnricher = async ( const indexLifecycleDataEnricher = async (
indicesList: IndexWithoutIlm[], indicesList: IndexWithoutIlm[],
// TODO replace deprecated ES client after Index Management is updated client: IScopedClusterClient
callAsCurrentUser: LegacyAPICaller
): Promise<Index[]> => { ): Promise<Index[]> => {
if (!indicesList || !indicesList.length) { if (!indicesList || !indicesList.length) {
return []; return [];
} }
const params = { const {
path: '/*/_ilm/explain', body: { indices: ilmIndicesData },
method: 'GET', } = await client.asCurrentUser.ilm.explainLifecycle({
}; index: '*',
});
const { indices: ilmIndicesData } = await callAsCurrentUser<{
indices: { [indexName: string]: IndexLifecyclePolicy };
}>('transport.request', params);
return indicesList.map((index: IndexWithoutIlm) => { return indicesList.map((index: IndexWithoutIlm) => {
return { return {
...index, ...index,
// @ts-expect-error @elastic/elasticsearch Element implicitly has an 'any' type
ilm: { ...(ilmIndicesData[index.name] || {}) }, ilm: { ...(ilmIndicesData[index.name] || {}) },
}; };
}); });

View file

@ -1,5 +1,16 @@
# Index Management UI # Index Management UI
## Indices tab
### Quick steps for testing
Create an index with special characters and verify it renders correctly:
```
# Renders as %{[@metadata][beat]}-%{[@metadata][version]}-2020.08.23
PUT %25%7B%5B%40metadata%5D%5Bbeat%5D%7D-%25%7B%5B%40metadata%5D%5Bversion%5D%7D-2020.08.23
```
## Data streams tab ## Data streams tab
### Quick steps for testing ### Quick steps for testing
@ -20,3 +31,55 @@ POST ds/_doc
"@timestamp": "2020-01-27" "@timestamp": "2020-01-27"
} }
``` ```
Create a data stream with special characters and verify it renders correctly:
```
# Configure template for creating a data stream
PUT _index_template/special_ds
{
"index_patterns": ["%{[@metadata][beat]}-%{[@metadata][version]}-2020.08.23"],
"data_stream": {}
}
# Add a document to the data stream, which will render as %{[@metadata][beat]}-%{[@metadata][version]}-2020.08.23
POST %25%7B%5B%40metadata%5D%5Bbeat%5D%7D-%25%7B%5B%40metadata%5D%5Bversion%5D%7D-2020.08.23/_doc
{
"@timestamp": "2020-01-27"
}
```
## Index templates tab
### Quick steps for testing
By default, **legacy index templates** are not shown in the UI. Make them appear by creating one in Console:
```
PUT _template/template_1
{
"index_patterns": ["foo*"]
}
```
To test **Cloud-managed templates**:
1. Add `cluster.metadata.managed_index_templates` setting via Dev Tools:
```
PUT /_cluster/settings
{
"persistent": {
"cluster.metadata.managed_index_templates": ".cloud-"
}
}
```
2. Create a template with the format: `.cloud-<template_name>` via Dev Tools.
```
PUT _template/.cloud-example
{
"index_patterns": [ "foobar*"]
}
```
The UI will now prevent you from editing or deleting this template.

View file

@ -6,7 +6,6 @@
*/ */
import React, { useState, useCallback, useEffect } from 'react'; import React, { useState, useCallback, useEffect } from 'react';
import uuid from 'uuid';
import { FormattedMessage } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react';
import { EuiCodeBlock, EuiCallOut } from '@elastic/eui'; import { EuiCodeBlock, EuiCallOut } from '@elastic/eui';
@ -37,11 +36,6 @@ export const SimulateTemplate = React.memo(({ template, filters }: Props) => {
} }
const indexTemplate = serializeTemplate(stripEmptyFields(template) as TemplateDeserialized); const indexTemplate = serializeTemplate(stripEmptyFields(template) as TemplateDeserialized);
// Until ES fixes a bug on their side we will send a random index pattern to the simulate API.
// Issue: https://github.com/elastic/elasticsearch/issues/59152
indexTemplate.index_patterns = [uuid.v4()];
const { data, error } = await simulateIndexTemplate(indexTemplate); const { data, error } = await simulateIndexTemplate(indexTemplate);
let filteredTemplate = data; let filteredTemplate = data;

View file

@ -1,174 +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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const elasticsearchJsPlugin = (Client: any, config: any, components: any) => {
const ca = components.clientAction.factory;
Client.prototype.dataManagement = components.clientAction.namespaceFactory();
const dataManagement = Client.prototype.dataManagement.prototype;
// Data streams
// We don't allow the user to create a data stream in the UI or API. We're just adding this here
// to enable the API integration tests.
dataManagement.createDataStream = ca({
urls: [
{
fmt: '/_data_stream/<%=name%>',
req: {
name: {
type: 'string',
},
},
},
],
method: 'PUT',
});
dataManagement.deleteDataStream = ca({
urls: [
{
fmt: '/_data_stream/<%=name%>',
req: {
name: {
type: 'string',
},
},
},
],
method: 'DELETE',
});
// Component templates
dataManagement.getComponentTemplates = ca({
urls: [
{
fmt: '/_component_template',
},
],
method: 'GET',
});
dataManagement.getComponentTemplate = ca({
urls: [
{
fmt: '/_component_template/<%=name%>',
req: {
name: {
type: 'string',
},
},
},
],
method: 'GET',
});
dataManagement.saveComponentTemplate = ca({
urls: [
{
fmt: '/_component_template/<%=name%>',
req: {
name: {
type: 'string',
},
},
},
],
method: 'PUT',
});
dataManagement.deleteComponentTemplate = ca({
urls: [
{
fmt: '/_component_template/<%=name%>',
req: {
name: {
type: 'string',
},
},
},
],
method: 'DELETE',
});
// Composable index templates
dataManagement.getComposableIndexTemplates = ca({
urls: [
{
fmt: '/_index_template',
},
],
method: 'GET',
});
dataManagement.getComposableIndexTemplate = ca({
urls: [
{
fmt: '/_index_template/<%=name%>',
req: {
name: {
type: 'string',
},
},
},
],
method: 'GET',
});
dataManagement.saveComposableIndexTemplate = ca({
urls: [
{
fmt: '/_index_template/<%=name%>',
req: {
name: {
type: 'string',
},
},
},
],
needBody: true,
method: 'PUT',
});
dataManagement.deleteComposableIndexTemplate = ca({
urls: [
{
fmt: '/_index_template/<%=name%>',
req: {
name: {
type: 'string',
},
},
},
],
method: 'DELETE',
});
dataManagement.existsTemplate = ca({
urls: [
{
fmt: '/_index_template/<%=name%>',
req: {
name: {
type: 'string',
},
},
},
],
method: 'HEAD',
});
dataManagement.simulateTemplate = ca({
urls: [
{
fmt: '/_index_template/_simulate',
},
],
needBody: true,
method: 'POST',
});
};

View file

@ -10,7 +10,7 @@ import { PluginInitializerContext } from 'src/core/server';
import { IndexMgmtServerPlugin } from './plugin'; import { IndexMgmtServerPlugin } from './plugin';
import { configSchema } from './config'; import { configSchema } from './config';
export const plugin = (ctx: PluginInitializerContext) => new IndexMgmtServerPlugin(ctx); export const plugin = (context: PluginInitializerContext) => new IndexMgmtServerPlugin(context);
export const config = { export const config = {
schema: configSchema, schema: configSchema,

View file

@ -5,99 +5,57 @@
* 2.0. * 2.0.
*/ */
import { CatIndicesParams } from 'elasticsearch'; import { IScopedClusterClient } from 'kibana/server';
import { IndexDataEnricher } from '../services'; import { IndexDataEnricher } from '../services';
import { CallAsCurrentUser } from '../types';
import { Index } from '../index'; import { Index } from '../index';
interface Hit {
health: string;
status: string;
index: string;
uuid: string;
pri: string;
rep: string;
'docs.count': any;
'store.size': any;
sth: 'true' | 'false';
hidden: boolean;
}
interface IndexInfo {
aliases: { [aliasName: string]: unknown };
mappings: unknown;
data_stream?: string;
settings: {
index: {
hidden: 'true' | 'false';
};
};
}
interface GetIndicesResponse {
[indexName: string]: IndexInfo;
}
async function fetchIndicesCall( async function fetchIndicesCall(
callAsCurrentUser: CallAsCurrentUser, client: IScopedClusterClient,
indexNames?: string[] indexNames?: string[]
): Promise<Index[]> { ): Promise<Index[]> {
const indexNamesString = indexNames && indexNames.length ? indexNames.join(',') : '*'; const indexNamesString = indexNames && indexNames.length ? indexNames.join(',') : '*';
// This call retrieves alias and settings (incl. hidden status) information about indices // This call retrieves alias and settings (incl. hidden status) information about indices
const indices: GetIndicesResponse = await callAsCurrentUser('transport.request', { const { body: indices } = await client.asCurrentUser.indices.get({
method: 'GET', index: indexNamesString,
// transport.request doesn't do any URI encoding, unlike other JS client APIs. This enables
// working with Logstash indices with names like %{[@metadata][beat]}-%{[@metadata][version]}.
path: `/${encodeURIComponent(indexNamesString)}`,
query: {
expand_wildcards: 'hidden,all', expand_wildcards: 'hidden,all',
},
}); });
if (!Object.keys(indices).length) { if (!Object.keys(indices).length) {
return []; return [];
} }
const catQuery: Pick<CatIndicesParams, 'format' | 'h'> & { const { body: catHits } = await client.asCurrentUser.cat.indices({
expand_wildcards: string;
index?: string;
} = {
format: 'json', format: 'json',
h: 'health,status,index,uuid,pri,rep,docs.count,sth,store.size', h: 'health,status,index,uuid,pri,rep,docs.count,sth,store.size',
expand_wildcards: 'hidden,all', expand_wildcards: 'hidden,all',
index: indexNamesString, index: indexNamesString,
};
// This call retrieves health and other high-level information about indices.
const catHits: Hit[] = await callAsCurrentUser('transport.request', {
method: 'GET',
path: '/_cat/indices',
query: catQuery,
}); });
// System indices may show up in _cat APIs, as these APIs are primarily used for troubleshooting // System indices may show up in _cat APIs, as these APIs are primarily used for troubleshooting
// For now, we filter them out and only return index information for the indices we have // For now, we filter them out and only return index information for the indices we have
// In the future, we should migrate away from using cat APIs (https://github.com/elastic/kibana/issues/57286) // In the future, we should migrate away from using cat APIs (https://github.com/elastic/kibana/issues/57286)
return catHits.reduce((decoratedIndices, hit) => { return catHits.reduce((decoratedIndices, hit) => {
const index = indices[hit.index]; const index = indices[hit.index!];
if (typeof index !== 'undefined') { if (typeof index !== 'undefined') {
const aliases = Object.keys(index.aliases); const aliases = Object.keys(index.aliases!);
decoratedIndices.push({ decoratedIndices.push({
health: hit.health, health: hit.health!,
status: hit.status, status: hit.status!,
name: hit.index, name: hit.index!,
uuid: hit.uuid, uuid: hit.uuid!,
primary: hit.pri, primary: hit.pri!,
replica: hit.rep, replica: hit.rep!,
documents: hit['docs.count'], documents: hit['docs.count'],
size: hit['store.size'], size: hit['store.size'],
isFrozen: hit.sth === 'true', // sth value coming back as a string from ES isFrozen: hit.sth === 'true', // sth value coming back as a string from ES
aliases: aliases.length ? aliases : 'none', aliases: aliases.length ? aliases : 'none',
// @ts-expect-error @elastic/elasticsearch Property 'index' does not exist on type 'IndicesIndexSettings | IndicesIndexStatePrefixedSettings'.
hidden: index.settings.index.hidden === 'true', hidden: index.settings.index.hidden === 'true',
data_stream: index.data_stream, // @ts-expect-error @elastic/elasticsearch Property 'data_stream' does not exist on type 'IndicesIndexState'.
data_stream: index.data_stream!,
}); });
} }
@ -106,10 +64,10 @@ async function fetchIndicesCall(
} }
export const fetchIndices = async ( export const fetchIndices = async (
callAsCurrentUser: CallAsCurrentUser, client: IScopedClusterClient,
indexDataEnricher: IndexDataEnricher, indexDataEnricher: IndexDataEnricher,
indexNames?: string[] indexNames?: string[]
) => { ) => {
const indices = await fetchIndicesCall(callAsCurrentUser, indexNames); const indices = await fetchIndicesCall(client, indexNames);
return await indexDataEnricher.enrichIndices(indices, callAsCurrentUser); return await indexDataEnricher.enrichIndices(indices, client);
}; };

View file

@ -5,16 +5,20 @@
* 2.0. * 2.0.
*/ */
import { IScopedClusterClient } from 'kibana/server';
// Cloud has its own system for managing templates and we want to make // Cloud has its own system for managing templates and we want to make
// this clear in the UI when a template is used in a Cloud deployment. // this clear in the UI when a template is used in a Cloud deployment.
export const getCloudManagedTemplatePrefix = async ( export const getCloudManagedTemplatePrefix = async (
callAsCurrentUser: any client: IScopedClusterClient
): Promise<string | undefined> => { ): Promise<string | undefined> => {
try { try {
const { persistent, transient, defaults } = await callAsCurrentUser('cluster.getSettings', { const {
filterPath: '*.*managed_index_templates', body: { persistent, transient, defaults },
flatSettings: true, } = await client.asCurrentUser.cluster.getSettings({
includeDefaults: true, filter_path: '*.*managed_index_templates',
flat_settings: true,
include_defaults: true,
}); });
const { 'cluster.metadata.managed_index_templates': managedTemplatesPrefix = undefined } = { const { 'cluster.metadata.managed_index_templates': managedTemplatesPrefix = undefined } = {

View file

@ -5,20 +5,13 @@
* 2.0. * 2.0.
*/ */
import { import { CoreSetup, Plugin, PluginInitializerContext } from 'src/core/server';
CoreSetup,
Plugin,
PluginInitializerContext,
ILegacyCustomClusterClient,
} from 'src/core/server';
import { PLUGIN } from '../common/constants/plugin'; import { PLUGIN } from '../common/constants/plugin';
import { Dependencies } from './types'; import { Dependencies } from './types';
import { ApiRoutes } from './routes'; import { ApiRoutes } from './routes';
import { IndexDataEnricher } from './services'; import { IndexDataEnricher } from './services';
import { isEsError, handleEsError, parseEsError } from './shared_imports'; import { handleEsError } from './shared_imports';
import { elasticsearchJsPlugin } from './client/elasticsearch';
import type { IndexManagementRequestHandlerContext } from './types';
export interface IndexManagementPluginSetup { export interface IndexManagementPluginSetup {
indexDataEnricher: { indexDataEnricher: {
@ -26,16 +19,9 @@ export interface IndexManagementPluginSetup {
}; };
} }
async function getCustomEsClient(getStartServices: CoreSetup['getStartServices']) {
const [core] = await getStartServices();
const esClientConfig = { plugins: [elasticsearchJsPlugin] };
return core.elasticsearch.legacy.createClient('dataManagement', esClientConfig);
}
export class IndexMgmtServerPlugin implements Plugin<IndexManagementPluginSetup, void, any, any> { export class IndexMgmtServerPlugin implements Plugin<IndexManagementPluginSetup, void, any, any> {
private readonly apiRoutes: ApiRoutes; private readonly apiRoutes: ApiRoutes;
private readonly indexDataEnricher: IndexDataEnricher; private readonly indexDataEnricher: IndexDataEnricher;
private dataManagementESClient?: ILegacyCustomClusterClient;
constructor(initContext: PluginInitializerContext) { constructor(initContext: PluginInitializerContext) {
this.apiRoutes = new ApiRoutes(); this.apiRoutes = new ApiRoutes();
@ -46,8 +32,6 @@ export class IndexMgmtServerPlugin implements Plugin<IndexManagementPluginSetup,
{ http, getStartServices }: CoreSetup, { http, getStartServices }: CoreSetup,
{ features, security }: Dependencies { features, security }: Dependencies
): IndexManagementPluginSetup { ): IndexManagementPluginSetup {
const router = http.createRouter<IndexManagementRequestHandlerContext>();
features.registerElasticsearchFeature({ features.registerElasticsearchFeature({
id: PLUGIN.id, id: PLUGIN.id,
management: { management: {
@ -63,27 +47,13 @@ export class IndexMgmtServerPlugin implements Plugin<IndexManagementPluginSetup,
], ],
}); });
http.registerRouteHandlerContext<IndexManagementRequestHandlerContext, 'dataManagement'>(
'dataManagement',
async (ctx, request) => {
this.dataManagementESClient =
this.dataManagementESClient ?? (await getCustomEsClient(getStartServices));
return {
client: this.dataManagementESClient.asScoped(request),
};
}
);
this.apiRoutes.setup({ this.apiRoutes.setup({
router, router: http.createRouter(),
config: { config: {
isSecurityEnabled: () => security !== undefined && security.license.isEnabled(), isSecurityEnabled: () => security !== undefined && security.license.isEnabled(),
}, },
indexDataEnricher: this.indexDataEnricher, indexDataEnricher: this.indexDataEnricher,
lib: { lib: {
isEsError,
parseEsError,
handleEsError, handleEsError,
}, },
}); });
@ -97,9 +67,5 @@ export class IndexMgmtServerPlugin implements Plugin<IndexManagementPluginSetup,
start() {} start() {}
stop() { stop() {}
if (this.dataManagementESClient) {
this.dataManagementESClient.close();
}
}
} }

View file

@ -12,7 +12,10 @@ import { RouteDependencies } from '../../../types';
import { addBasePath } from '../index'; import { addBasePath } from '../index';
import { componentTemplateSchema } from './schema_validation'; import { componentTemplateSchema } from './schema_validation';
export const registerCreateRoute = ({ router, lib: { isEsError } }: RouteDependencies): void => { export const registerCreateRoute = ({
router,
lib: { handleEsError },
}: RouteDependencies): void => {
router.post( router.post(
{ {
path: addBasePath('/component_templates'), path: addBasePath('/component_templates'),
@ -20,24 +23,23 @@ export const registerCreateRoute = ({ router, lib: { isEsError } }: RouteDepende
body: componentTemplateSchema, body: componentTemplateSchema,
}, },
}, },
async (ctx, req, res) => { async (context, request, response) => {
const { callAsCurrentUser } = ctx.dataManagement!.client; const { client } = context.core.elasticsearch;
const serializedComponentTemplate = serializeComponentTemplate(req.body); const serializedComponentTemplate = serializeComponentTemplate(request.body);
const { name } = req.body; const { name } = request.body;
try { try {
// Check that a component template with the same name doesn't already exist // Check that a component template with the same name doesn't already exist
const componentTemplateResponse = await callAsCurrentUser( const {
'dataManagement.getComponentTemplate', body: { component_templates: componentTemplates },
{ name } } = await client.asCurrentUser.cluster.getComponentTemplate({
); name,
});
const { component_templates: componentTemplates } = componentTemplateResponse;
if (componentTemplates.length) { if (componentTemplates.length) {
return res.conflict({ return response.conflict({
body: new Error( body: new Error(
i18n.translate('xpack.idxMgmt.componentTemplates.createRoute.duplicateErrorMessage', { i18n.translate('xpack.idxMgmt.componentTemplates.createRoute.duplicateErrorMessage', {
defaultMessage: "There is already a component template with name '{name}'.", defaultMessage: "There is already a component template with name '{name}'.",
@ -53,21 +55,15 @@ export const registerCreateRoute = ({ router, lib: { isEsError } }: RouteDepende
} }
try { try {
const response = await callAsCurrentUser('dataManagement.saveComponentTemplate', { const { body: responseBody } = await client.asCurrentUser.cluster.putComponentTemplate({
name, name,
// @ts-expect-error @elastic/elasticsearch Type 'ComponentTemplateSerialized' is not assignable
body: serializedComponentTemplate, body: serializedComponentTemplate,
}); });
return res.ok({ body: response }); return response.ok({ body: responseBody });
} catch (error) { } catch (error) {
if (isEsError(error)) { return handleEsError({ error, response });
return res.customError({
statusCode: error.statusCode,
body: error,
});
}
throw error;
} }
} }
); );

View file

@ -14,7 +14,10 @@ const paramsSchema = schema.object({
names: schema.string(), names: schema.string(),
}); });
export const registerDeleteRoute = ({ router }: RouteDependencies): void => { export const registerDeleteRoute = ({
router,
lib: { handleEsError },
}: RouteDependencies): void => {
router.delete( router.delete(
{ {
path: addBasePath('/component_templates/{names}'), path: addBasePath('/component_templates/{names}'),
@ -22,32 +25,34 @@ export const registerDeleteRoute = ({ router }: RouteDependencies): void => {
params: paramsSchema, params: paramsSchema,
}, },
}, },
async (ctx, req, res) => { async (context, request, response) => {
const { callAsCurrentUser } = ctx.dataManagement!.client; const { client } = context.core.elasticsearch;
const { names } = req.params; const { names } = request.params;
const componentNames = names.split(','); const componentNames = names.split(',');
const response: { itemsDeleted: string[]; errors: any[] } = { const responseBody: { itemsDeleted: string[]; errors: any[] } = {
itemsDeleted: [], itemsDeleted: [],
errors: [], errors: [],
}; };
await Promise.all( await Promise.all(
componentNames.map((componentName) => { componentNames.map(async (componentName) => {
return callAsCurrentUser('dataManagement.deleteComponentTemplate', { try {
await client.asCurrentUser.cluster.deleteComponentTemplate({
name: componentName, name: componentName,
}) });
.then(() => response.itemsDeleted.push(componentName))
.catch((e) => return responseBody.itemsDeleted.push(componentName);
response.errors.push({ } catch (error) {
return responseBody.errors.push({
name: componentName, name: componentName,
error: e, error: handleEsError({ error, response }),
}) });
); }
}) })
); );
return res.ok({ body: response }); return response.ok({ body: responseBody });
} }
); );
}; };

View file

@ -19,25 +19,23 @@ const paramsSchema = schema.object({
name: schema.string(), name: schema.string(),
}); });
export function registerGetAllRoute({ router, lib: { isEsError } }: RouteDependencies) { export function registerGetAllRoute({ router, lib: { handleEsError } }: RouteDependencies) {
// Get all component templates // Get all component templates
router.get( router.get(
{ path: addBasePath('/component_templates'), validate: false }, { path: addBasePath('/component_templates'), validate: false },
async (ctx, req, res) => { async (context, request, response) => {
const { callAsCurrentUser } = ctx.dataManagement!.client; const { client } = context.core.elasticsearch;
try { try {
const { const {
component_templates: componentTemplates, body: { component_templates: componentTemplates },
}: { component_templates: ComponentTemplateFromEs[] } = await callAsCurrentUser( } = await client.asCurrentUser.cluster.getComponentTemplate();
'dataManagement.getComponentTemplates'
);
const { index_templates: indexTemplates } = await callAsCurrentUser( const {
'dataManagement.getComposableIndexTemplates' body: { index_templates: indexTemplates },
); } = await client.asCurrentUser.indices.getIndexTemplate();
const body = componentTemplates.map((componentTemplate) => { const body = componentTemplates.map((componentTemplate: ComponentTemplateFromEs) => {
const deserializedComponentTemplateListItem = deserializeComponentTemplateList( const deserializedComponentTemplateListItem = deserializeComponentTemplateList(
componentTemplate, componentTemplate,
indexTemplates indexTemplates
@ -45,16 +43,9 @@ export function registerGetAllRoute({ router, lib: { isEsError } }: RouteDepende
return deserializedComponentTemplateListItem; return deserializedComponentTemplateListItem;
}); });
return res.ok({ body }); return response.ok({ body });
} catch (error) { } catch (error) {
if (isEsError(error)) { return handleEsError({ error, response });
return res.customError({
statusCode: error.statusCode,
body: error,
});
}
throw error;
} }
} }
); );
@ -67,34 +58,26 @@ export function registerGetAllRoute({ router, lib: { isEsError } }: RouteDepende
params: paramsSchema, params: paramsSchema,
}, },
}, },
async (ctx, req, res) => { async (context, request, response) => {
const { callAsCurrentUser } = ctx.dataManagement!.client; const { client } = context.core.elasticsearch;
const { name } = req.params; const { name } = request.params;
try { try {
const { component_templates: componentTemplates } = await callAsCurrentUser( const {
'dataManagement.getComponentTemplates', body: { component_templates: componentTemplates },
{ } = await client.asCurrentUser.cluster.getComponentTemplate({
name, name,
} });
);
const { index_templates: indexTemplates } = await callAsCurrentUser( const {
'dataManagement.getComposableIndexTemplates' body: { index_templates: indexTemplates },
); } = await client.asCurrentUser.indices.getIndexTemplate();
return res.ok({ return response.ok({
body: deserializeComponentTemplate(componentTemplates[0], indexTemplates), body: deserializeComponentTemplate(componentTemplates[0], indexTemplates),
}); });
} catch (error) { } catch (error) {
if (isEsError(error)) { return handleEsError({ error, response });
return res.customError({
statusCode: error.statusCode,
body: error,
});
}
throw error;
} }
} }
); );

View file

@ -18,17 +18,15 @@ const httpService = httpServiceMock.createSetupContract();
const mockedIndexDataEnricher = new IndexDataEnricher(); const mockedIndexDataEnricher = new IndexDataEnricher();
const mockRouteContext = ({ const mockRouteContext = ({ hasPrivileges }: { hasPrivileges: unknown }): RequestHandlerContext => {
callAsCurrentUser,
}: {
callAsCurrentUser: any;
}): RequestHandlerContext => {
const routeContextMock = ({ const routeContextMock = ({
core: { core: {
elasticsearch: { elasticsearch: {
legacy: {
client: { client: {
callAsCurrentUser, asCurrentUser: {
security: {
hasPrivileges,
},
}, },
}, },
}, },
@ -51,8 +49,6 @@ describe('GET privileges', () => {
}, },
indexDataEnricher: mockedIndexDataEnricher, indexDataEnricher: mockedIndexDataEnricher,
lib: { lib: {
isEsError: jest.fn(),
parseEsError: jest.fn(),
handleEsError: jest.fn(), handleEsError: jest.fn(),
}, },
}); });
@ -62,15 +58,17 @@ describe('GET privileges', () => {
it('should return the correct response when a user has privileges', async () => { it('should return the correct response when a user has privileges', async () => {
const privilegesResponseMock = { const privilegesResponseMock = {
body: {
username: 'elastic', username: 'elastic',
has_all_requested: true, has_all_requested: true,
cluster: { manage_index_templates: true }, cluster: { manage_index_templates: true },
index: {}, index: {},
application: {}, application: {},
},
}; };
const routeContextMock = mockRouteContext({ const routeContextMock = mockRouteContext({
callAsCurrentUser: jest.fn().mockResolvedValueOnce(privilegesResponseMock), hasPrivileges: jest.fn().mockResolvedValueOnce(privilegesResponseMock),
}); });
const request = httpServerMock.createKibanaRequest(); const request = httpServerMock.createKibanaRequest();
@ -86,15 +84,17 @@ describe('GET privileges', () => {
it('should return the correct response when a user does not have privileges', async () => { it('should return the correct response when a user does not have privileges', async () => {
const privilegesResponseMock = { const privilegesResponseMock = {
body: {
username: 'elastic', username: 'elastic',
has_all_requested: false, has_all_requested: false,
cluster: { manage_index_templates: false }, cluster: { manage_index_templates: false },
index: {}, index: {},
application: {}, application: {},
},
}; };
const routeContextMock = mockRouteContext({ const routeContextMock = mockRouteContext({
callAsCurrentUser: jest.fn().mockResolvedValueOnce(privilegesResponseMock), hasPrivileges: jest.fn().mockResolvedValueOnce(privilegesResponseMock),
}); });
const request = httpServerMock.createKibanaRequest(); const request = httpServerMock.createKibanaRequest();
@ -119,8 +119,6 @@ describe('GET privileges', () => {
}, },
indexDataEnricher: mockedIndexDataEnricher, indexDataEnricher: mockedIndexDataEnricher,
lib: { lib: {
isEsError: jest.fn(),
parseEsError: jest.fn(),
handleEsError: jest.fn(), handleEsError: jest.fn(),
}, },
}); });
@ -130,7 +128,7 @@ describe('GET privileges', () => {
it('should return the default privileges response', async () => { it('should return the default privileges response', async () => {
const routeContextMock = mockRouteContext({ const routeContextMock = mockRouteContext({
callAsCurrentUser: jest.fn(), hasPrivileges: jest.fn(),
}); });
const request = httpServerMock.createKibanaRequest(); const request = httpServerMock.createKibanaRequest();

View file

@ -17,13 +17,17 @@ const extractMissingPrivileges = (privilegesObject: { [key: string]: boolean } =
return privileges; return privileges;
}, []); }, []);
export const registerPrivilegesRoute = ({ router, config }: RouteDependencies) => { export const registerPrivilegesRoute = ({
router,
config,
lib: { handleEsError },
}: RouteDependencies) => {
router.get( router.get(
{ {
path: addBasePath('/component_templates/privileges'), path: addBasePath('/component_templates/privileges'),
validate: false, validate: false,
}, },
async (ctx, req, res) => { async (context, request, response) => {
const privilegesResult: Privileges = { const privilegesResult: Privileges = {
hasAllPrivileges: true, hasAllPrivileges: true,
missingPrivileges: { missingPrivileges: {
@ -33,38 +37,28 @@ export const registerPrivilegesRoute = ({ router, config }: RouteDependencies) =
// Skip the privileges check if security is not enabled // Skip the privileges check if security is not enabled
if (!config.isSecurityEnabled()) { if (!config.isSecurityEnabled()) {
return res.ok({ body: privilegesResult }); return response.ok({ body: privilegesResult });
} }
const { const { client } = context.core.elasticsearch;
core: {
elasticsearch: {
legacy: { client },
},
},
} = ctx;
try { try {
const { has_all_requested: hasAllPrivileges, cluster } = await client.callAsCurrentUser( const {
'transport.request', body: { has_all_requested: hasAllPrivileges, cluster },
{ } = await client.asCurrentUser.security.hasPrivileges({
path: '/_security/user/_has_privileges',
method: 'POST',
body: { body: {
cluster: ['manage_index_templates'], cluster: ['manage_index_templates'],
}, },
} });
);
if (!hasAllPrivileges) { if (!hasAllPrivileges) {
privilegesResult.missingPrivileges.cluster = extractMissingPrivileges(cluster); privilegesResult.missingPrivileges.cluster = extractMissingPrivileges(cluster);
} }
privilegesResult.hasAllPrivileges = hasAllPrivileges; privilegesResult.hasAllPrivileges = hasAllPrivileges;
return response.ok({ body: privilegesResult });
return res.ok({ body: privilegesResult }); } catch (error) {
} catch (e) { return handleEsError({ error, response });
throw e;
} }
} }
); );

View file

@ -15,7 +15,10 @@ const paramsSchema = schema.object({
name: schema.string(), name: schema.string(),
}); });
export const registerUpdateRoute = ({ router, lib: { isEsError } }: RouteDependencies): void => { export const registerUpdateRoute = ({
router,
lib: { handleEsError },
}: RouteDependencies): void => {
router.put( router.put(
{ {
path: addBasePath('/component_templates/{name}'), path: addBasePath('/component_templates/{name}'),
@ -24,34 +27,28 @@ export const registerUpdateRoute = ({ router, lib: { isEsError } }: RouteDepende
params: paramsSchema, params: paramsSchema,
}, },
}, },
async (ctx, req, res) => { async (context, request, response) => {
const { callAsCurrentUser } = ctx.dataManagement!.client; const { client } = context.core.elasticsearch;
const { name } = req.params; const { name } = request.params;
const { template, version, _meta } = req.body; const { template, version, _meta } = request.body;
try { try {
// Verify component exists; ES will throw 404 if not // Verify component exists; ES will throw 404 if not
await callAsCurrentUser('dataManagement.getComponentTemplate', { name }); await client.asCurrentUser.cluster.getComponentTemplate({ name });
const response = await callAsCurrentUser('dataManagement.saveComponentTemplate', { const { body: responseBody } = await client.asCurrentUser.cluster.putComponentTemplate({
name, name,
body: { body: {
// @ts-expect-error @elastic/elasticsearch Not assignable to type 'IndicesIndexState'
template, template,
version, version,
_meta, _meta,
}, },
}); });
return res.ok({ body: response }); return response.ok({ body: responseBody });
} catch (error) { } catch (error) {
if (isEsError(error)) { return handleEsError({ error, response });
return res.customError({
statusCode: error.statusCode,
body: error,
});
}
throw error;
} }
} }
); );

View file

@ -9,23 +9,22 @@ import { schema, TypeOf } from '@kbn/config-schema';
import { RouteDependencies } from '../../../types'; import { RouteDependencies } from '../../../types';
import { addBasePath } from '../index'; import { addBasePath } from '../index';
import { wrapEsError } from '../../helpers';
const bodySchema = schema.object({ const bodySchema = schema.object({
dataStreams: schema.arrayOf(schema.string()), dataStreams: schema.arrayOf(schema.string()),
}); });
export function registerDeleteRoute({ router }: RouteDependencies) { export function registerDeleteRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.post( router.post(
{ {
path: addBasePath('/delete_data_streams'), path: addBasePath('/delete_data_streams'),
validate: { body: bodySchema }, validate: { body: bodySchema },
}, },
async (ctx, req, res) => { async (context, request, response) => {
const { callAsCurrentUser } = ctx.dataManagement!.client; const { client } = context.core.elasticsearch;
const { dataStreams } = req.body as TypeOf<typeof bodySchema>; const { dataStreams } = request.body as TypeOf<typeof bodySchema>;
const response: { dataStreamsDeleted: string[]; errors: any[] } = { const responseBody: { dataStreamsDeleted: string[]; errors: any[] } = {
dataStreamsDeleted: [], dataStreamsDeleted: [],
errors: [], errors: [],
}; };
@ -33,21 +32,21 @@ export function registerDeleteRoute({ router }: RouteDependencies) {
await Promise.all( await Promise.all(
dataStreams.map(async (name: string) => { dataStreams.map(async (name: string) => {
try { try {
await callAsCurrentUser('dataManagement.deleteDataStream', { await client.asCurrentUser.indices.deleteDataStream({
name, name,
}); });
return response.dataStreamsDeleted.push(name); return responseBody.dataStreamsDeleted.push(name);
} catch (e) { } catch (error) {
return response.errors.push({ return responseBody.errors.push({
name, name,
error: wrapEsError(e), error: handleEsError({ error, response }),
}); });
} }
}) })
); );
return res.ok({ body: response }); return response.ok({ body: responseBody });
} }
); );
} }

View file

@ -7,7 +7,7 @@
import { schema, TypeOf } from '@kbn/config-schema'; import { schema, TypeOf } from '@kbn/config-schema';
import { ElasticsearchClient } from 'kibana/server'; import { IScopedClusterClient } from 'kibana/server';
import { deserializeDataStream, deserializeDataStreamList } from '../../../../common/lib'; import { deserializeDataStream, deserializeDataStreamList } from '../../../../common/lib';
import { DataStreamFromEs } from '../../../../common/types'; import { DataStreamFromEs } from '../../../../common/types';
import { RouteDependencies } from '../../../types'; import { RouteDependencies } from '../../../types';
@ -68,30 +68,23 @@ const enhanceDataStreams = ({
}); });
}; };
const getDataStreams = (client: ElasticsearchClient, name = '*') => { const getDataStreams = (client: IScopedClusterClient, name = '*') => {
// TODO update when elasticsearch client has update requestParams for 'indices.getDataStream' return client.asCurrentUser.indices.getDataStream({
return client.transport.request({ name,
path: `/_data_stream/${encodeURIComponent(name)}`,
method: 'GET',
querystring: {
expand_wildcards: 'all', expand_wildcards: 'all',
},
}); });
}; };
const getDataStreamsStats = (client: ElasticsearchClient, name = '*') => { const getDataStreamsStats = (client: IScopedClusterClient, name = '*') => {
return client.transport.request({ return client.asCurrentUser.indices.dataStreamsStats({
path: `/_data_stream/${encodeURIComponent(name)}/_stats`, name,
method: 'GET', expand_wildcards: 'all',
querystring: {
human: true, human: true,
expand_wildcards: 'all',
},
}); });
}; };
const getDataStreamsPrivileges = (client: ElasticsearchClient, names: string[]) => { const getDataStreamsPrivileges = (client: IScopedClusterClient, names: string[]) => {
return client.security.hasPrivileges({ return client.asCurrentUser.security.hasPrivileges({
body: { body: {
index: [ index: [
{ {
@ -109,15 +102,15 @@ export function registerGetAllRoute({ router, lib: { handleEsError }, config }:
}); });
router.get( router.get(
{ path: addBasePath('/data_streams'), validate: { query: querySchema } }, { path: addBasePath('/data_streams'), validate: { query: querySchema } },
async (ctx, req, response) => { async (context, request, response) => {
const { asCurrentUser } = ctx.core.elasticsearch.client; const { client } = context.core.elasticsearch;
const includeStats = (req.query as TypeOf<typeof querySchema>).includeStats === 'true'; const includeStats = (request.query as TypeOf<typeof querySchema>).includeStats === 'true';
try { try {
let { const {
body: { data_streams: dataStreams }, body: { data_streams: dataStreams },
} = await getDataStreams(asCurrentUser); } = await getDataStreams(client);
let dataStreamsStats; let dataStreamsStats;
let dataStreamsPrivileges; let dataStreamsPrivileges;
@ -125,24 +118,26 @@ export function registerGetAllRoute({ router, lib: { handleEsError }, config }:
if (includeStats) { if (includeStats) {
({ ({
body: { data_streams: dataStreamsStats }, body: { data_streams: dataStreamsStats },
} = await getDataStreamsStats(asCurrentUser)); } = await getDataStreamsStats(client));
} }
if (config.isSecurityEnabled() && dataStreams.length > 0) { if (config.isSecurityEnabled() && dataStreams.length > 0) {
({ body: dataStreamsPrivileges } = await getDataStreamsPrivileges( ({ body: dataStreamsPrivileges } = await getDataStreamsPrivileges(
asCurrentUser, client,
dataStreams.map((dataStream: DataStreamFromEs) => dataStream.name) dataStreams.map((dataStream) => dataStream.name)
)); ));
} }
dataStreams = enhanceDataStreams({ const enhancedDataStreams = enhanceDataStreams({
// @ts-expect-error @elastic/elasticsearch DataStreamFromEs incompatible with IndicesGetDataStreamIndicesGetDataStreamItem
dataStreams, dataStreams,
// @ts-expect-error @elastic/elasticsearch StatsFromEs incompatible with IndicesDataStreamsStatsDataStreamsStatsItem
dataStreamsStats, dataStreamsStats,
// @ts-expect-error PrivilegesFromEs incompatible with ApplicationsPrivileges // @ts-expect-error @elastic/elasticsearch PrivilegesFromEs incompatible with ApplicationsPrivileges
dataStreamsPrivileges, dataStreamsPrivileges,
}); });
return response.ok({ body: deserializeDataStreamList(dataStreams) }); return response.ok({ body: deserializeDataStreamList(enhancedDataStreams) });
} catch (error) { } catch (error) {
return handleEsError({ error, response }); return handleEsError({ error, response });
} }
@ -159,9 +154,9 @@ export function registerGetOneRoute({ router, lib: { handleEsError }, config }:
path: addBasePath('/data_streams/{name}'), path: addBasePath('/data_streams/{name}'),
validate: { params: paramsSchema }, validate: { params: paramsSchema },
}, },
async (ctx, req, response) => { async (context, request, response) => {
const { name } = req.params as TypeOf<typeof paramsSchema>; const { name } = request.params as TypeOf<typeof paramsSchema>;
const { asCurrentUser } = ctx.core.elasticsearch.client; const { client } = context.core.elasticsearch;
try { try {
const [ const [
{ {
@ -170,23 +165,22 @@ export function registerGetOneRoute({ router, lib: { handleEsError }, config }:
{ {
body: { data_streams: dataStreamsStats }, body: { data_streams: dataStreamsStats },
}, },
] = await Promise.all([ ] = await Promise.all([getDataStreams(client, name), getDataStreamsStats(client, name)]);
getDataStreams(asCurrentUser, name),
getDataStreamsStats(asCurrentUser, name),
]);
if (dataStreams[0]) { if (dataStreams[0]) {
let dataStreamsPrivileges; let dataStreamsPrivileges;
if (config.isSecurityEnabled()) { if (config.isSecurityEnabled()) {
({ body: dataStreamsPrivileges } = await getDataStreamsPrivileges(asCurrentUser, [ ({ body: dataStreamsPrivileges } = await getDataStreamsPrivileges(client, [
dataStreams[0].name, dataStreams[0].name,
])); ]));
} }
const enhancedDataStreams = enhanceDataStreams({ const enhancedDataStreams = enhanceDataStreams({
// @ts-expect-error @elastic/elasticsearch DataStreamFromEs incompatible with IndicesGetDataStreamIndicesGetDataStreamItem
dataStreams, dataStreams,
// @ts-expect-error @elastic/elasticsearch StatsFromEs incompatible with IndicesDataStreamsStatsDataStreamsStatsItem
dataStreamsStats, dataStreamsStats,
// @ts-expect-error PrivilegesFromEs incompatible with ApplicationsPrivileges // @ts-expect-error @elastic/elasticsearch PrivilegesFromEs incompatible with ApplicationsPrivileges
dataStreamsPrivileges, dataStreamsPrivileges,
}); });
const body = deserializeDataStream(enhancedDataStreams[0]); const body = deserializeDataStream(enhancedDataStreams[0]);

View file

@ -14,31 +14,24 @@ const bodySchema = schema.object({
indices: schema.arrayOf(schema.string()), indices: schema.arrayOf(schema.string()),
}); });
export function registerClearCacheRoute({ router, lib }: RouteDependencies) { export function registerClearCacheRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.post( router.post(
{ path: addBasePath('/indices/clear_cache'), validate: { body: bodySchema } }, { path: addBasePath('/indices/clear_cache'), validate: { body: bodySchema } },
async (ctx, req, res) => { async (context, request, response) => {
const payload = req.body as typeof bodySchema.type; const { client } = context.core.elasticsearch;
const { indices = [] } = payload; const { indices = [] } = request.body as typeof bodySchema.type;
const params = { const params = {
expandWildcards: 'none', expand_wildcards: 'none',
format: 'json', format: 'json',
index: indices, index: indices,
}; };
try { try {
await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('indices.clearCache', params); await client.asCurrentUser.indices.clearCache(params);
return res.ok(); return response.ok();
} catch (e) { } catch (error) {
if (lib.isEsError(e)) { return handleEsError({ error, response });
return res.customError({
statusCode: e.statusCode,
body: e,
});
}
// Case: default
throw e;
} }
} }
); );

View file

@ -14,31 +14,24 @@ const bodySchema = schema.object({
indices: schema.arrayOf(schema.string()), indices: schema.arrayOf(schema.string()),
}); });
export function registerCloseRoute({ router, lib }: RouteDependencies) { export function registerCloseRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.post( router.post(
{ path: addBasePath('/indices/close'), validate: { body: bodySchema } }, { path: addBasePath('/indices/close'), validate: { body: bodySchema } },
async (ctx, req, res) => { async (context, request, response) => {
const payload = req.body as typeof bodySchema.type; const { client } = context.core.elasticsearch;
const { indices = [] } = payload; const { indices = [] } = request.body as typeof bodySchema.type;
const params = { const params = {
expandWildcards: 'none', expand_wildcards: 'none',
format: 'json', format: 'json',
index: indices, index: indices,
}; };
try { try {
await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('indices.close', params); await client.asCurrentUser.indices.close(params);
return res.ok(); return response.ok();
} catch (e) { } catch (error) {
if (lib.isEsError(e)) { return handleEsError({ error, response });
return res.customError({
statusCode: e.statusCode,
body: e,
});
}
// Case: default
throw e;
} }
} }
); );

View file

@ -14,31 +14,24 @@ const bodySchema = schema.object({
indices: schema.arrayOf(schema.string()), indices: schema.arrayOf(schema.string()),
}); });
export function registerDeleteRoute({ router, lib }: RouteDependencies) { export function registerDeleteRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.post( router.post(
{ path: addBasePath('/indices/delete'), validate: { body: bodySchema } }, { path: addBasePath('/indices/delete'), validate: { body: bodySchema } },
async (ctx, req, res) => { async (context, request, response) => {
const body = req.body as typeof bodySchema.type; const { client } = context.core.elasticsearch;
const { indices = [] } = body; const { indices = [] } = request.body as typeof bodySchema.type;
const params = { const params = {
expandWildcards: 'none', expand_wildcards: 'none',
format: 'json', format: 'json',
index: indices, index: indices,
}; };
try { try {
await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('indices.delete', params); await client.asCurrentUser.indices.delete(params);
return res.ok(); return response.ok();
} catch (e) { } catch (error) {
if (lib.isEsError(e)) { return handleEsError({ error, response });
return res.customError({
statusCode: e.statusCode,
body: e,
});
}
// Case: default
throw e;
} }
} }
); );

View file

@ -14,31 +14,24 @@ const bodySchema = schema.object({
indices: schema.arrayOf(schema.string()), indices: schema.arrayOf(schema.string()),
}); });
export function registerFlushRoute({ router, lib }: RouteDependencies) { export function registerFlushRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.post( router.post(
{ path: addBasePath('/indices/flush'), validate: { body: bodySchema } }, { path: addBasePath('/indices/flush'), validate: { body: bodySchema } },
async (ctx, req, res) => { async (context, request, response) => {
const body = req.body as typeof bodySchema.type; const { client } = context.core.elasticsearch;
const { indices = [] } = body; const { indices = [] } = request.body as typeof bodySchema.type;
const params = { const params = {
expandWildcards: 'none', expand_wildcards: 'none',
format: 'json', format: 'json',
index: indices, index: indices,
}; };
try { try {
await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('indices.flush', params); await client.asCurrentUser.indices.flush(params);
return res.ok(); return response.ok();
} catch (e) { } catch (error) {
if (lib.isEsError(e)) { return handleEsError({ error, response });
return res.customError({
statusCode: e.statusCode,
body: e,
});
}
// Case: default
throw e;
} }
} }
); );

View file

@ -15,7 +15,7 @@ const bodySchema = schema.object({
maxNumSegments: schema.maybe(schema.number()), maxNumSegments: schema.maybe(schema.number()),
}); });
export function registerForcemergeRoute({ router, lib }: RouteDependencies) { export function registerForcemergeRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.post( router.post(
{ {
path: addBasePath('/indices/forcemerge'), path: addBasePath('/indices/forcemerge'),
@ -23,10 +23,11 @@ export function registerForcemergeRoute({ router, lib }: RouteDependencies) {
body: bodySchema, body: bodySchema,
}, },
}, },
async (ctx, req, res) => { async (context, request, response) => {
const { maxNumSegments, indices = [] } = req.body as typeof bodySchema.type; const { client } = context.core.elasticsearch;
const { maxNumSegments, indices = [] } = request.body as typeof bodySchema.type;
const params = { const params = {
expandWildcards: 'none', expand_wildcards: 'none',
index: indices, index: indices,
}; };
@ -35,17 +36,10 @@ export function registerForcemergeRoute({ router, lib }: RouteDependencies) {
} }
try { try {
await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('indices.forcemerge', params); await client.asCurrentUser.indices.forcemerge(params);
return res.ok(); return response.ok();
} catch (e) { } catch (error) {
if (lib.isEsError(e)) { return handleEsError({ error, response });
return res.customError({
statusCode: e.statusCode,
body: e,
});
}
// Case: default
throw e;
} }
} }
); );

View file

@ -14,33 +14,20 @@ const bodySchema = schema.object({
indices: schema.arrayOf(schema.string()), indices: schema.arrayOf(schema.string()),
}); });
export function registerFreezeRoute({ router, lib }: RouteDependencies) { export function registerFreezeRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.post( router.post(
{ path: addBasePath('/indices/freeze'), validate: { body: bodySchema } }, { path: addBasePath('/indices/freeze'), validate: { body: bodySchema } },
async (ctx, req, res) => { async (context, request, response) => {
const body = req.body as typeof bodySchema.type; const { client } = context.core.elasticsearch;
const { indices = [] } = body; const { indices = [] } = request.body as typeof bodySchema.type;
const params = {
path: `/${encodeURIComponent(indices.join(','))}/_freeze`,
method: 'POST',
};
try { try {
await await ctx.core.elasticsearch.legacy.client.callAsCurrentUser( await client.asCurrentUser.indices.freeze({
'transport.request', index: indices.join(','),
params
);
return res.ok();
} catch (e) {
if (lib.isEsError(e)) {
return res.customError({
statusCode: e.statusCode,
body: e,
}); });
} return response.ok();
// Case: default } catch (error) {
throw e; return handleEsError({ error, response });
} }
} }
); );

View file

@ -9,23 +9,21 @@ import { fetchIndices } from '../../../lib/fetch_indices';
import { RouteDependencies } from '../../../types'; import { RouteDependencies } from '../../../types';
import { addBasePath } from '../index'; import { addBasePath } from '../index';
export function registerListRoute({ router, indexDataEnricher, lib }: RouteDependencies) { export function registerListRoute({
router.get({ path: addBasePath('/indices'), validate: false }, async (ctx, req, res) => { router,
indexDataEnricher,
lib: { handleEsError },
}: RouteDependencies) {
router.get(
{ path: addBasePath('/indices'), validate: false },
async (context, request, response) => {
const { client } = context.core.elasticsearch;
try { try {
const indices = await fetchIndices( const indices = await fetchIndices(client, indexDataEnricher);
ctx.core.elasticsearch.legacy.client.callAsCurrentUser, return response.ok({ body: indices });
indexDataEnricher } catch (error) {
return handleEsError({ error, response });
}
}
); );
return res.ok({ body: indices });
} catch (e) {
if (lib.isEsError(e)) {
return res.customError({
statusCode: e.statusCode,
body: e,
});
}
// Case: default
throw e;
}
});
} }

View file

@ -14,31 +14,24 @@ const bodySchema = schema.object({
indices: schema.arrayOf(schema.string()), indices: schema.arrayOf(schema.string()),
}); });
export function registerOpenRoute({ router, lib }: RouteDependencies) { export function registerOpenRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.post( router.post(
{ path: addBasePath('/indices/open'), validate: { body: bodySchema } }, { path: addBasePath('/indices/open'), validate: { body: bodySchema } },
async (ctx, req, res) => { async (context, request, response) => {
const body = req.body as typeof bodySchema.type; const { client } = context.core.elasticsearch;
const { indices = [] } = body; const { indices = [] } = request.body as typeof bodySchema.type;
const params = { const params = {
expandWildcards: 'none', expand_wildcards: 'none',
format: 'json', format: 'json',
index: indices, index: indices,
}; };
try { try {
await await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('indices.open', params); await client.asCurrentUser.indices.open(params);
return res.ok(); return response.ok();
} catch (e) { } catch (error) {
if (lib.isEsError(e)) { return handleEsError({ error, response });
return res.customError({
statusCode: e.statusCode,
body: e,
});
}
// Case: default
throw e;
} }
} }
); );

View file

@ -14,31 +14,24 @@ const bodySchema = schema.object({
indices: schema.arrayOf(schema.string()), indices: schema.arrayOf(schema.string()),
}); });
export function registerRefreshRoute({ router, lib }: RouteDependencies) { export function registerRefreshRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.post( router.post(
{ path: addBasePath('/indices/refresh'), validate: { body: bodySchema } }, { path: addBasePath('/indices/refresh'), validate: { body: bodySchema } },
async (ctx, req, res) => { async (context, request, response) => {
const body = req.body as typeof bodySchema.type; const { client } = context.core.elasticsearch;
const { indices = [] } = body; const { indices = [] } = request.body as typeof bodySchema.type;
const params = { const params = {
expandWildcards: 'none', expand_wildcards: 'none',
format: 'json', format: 'json',
index: indices, index: indices,
}; };
try { try {
await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('indices.refresh', params); await client.asCurrentUser.indices.refresh(params);
return res.ok(); return response.ok();
} catch (e) { } catch (error) {
if (lib.isEsError(e)) { return handleEsError({ error, response });
return res.customError({
statusCode: e.statusCode,
body: e,
});
}
// Case: default
throw e;
} }
} }
); );

View file

@ -17,28 +17,22 @@ const bodySchema = schema.maybe(
}) })
); );
export function registerReloadRoute({ router, indexDataEnricher, lib }: RouteDependencies) { export function registerReloadRoute({
router,
indexDataEnricher,
lib: { handleEsError },
}: RouteDependencies) {
router.post( router.post(
{ path: addBasePath('/indices/reload'), validate: { body: bodySchema } }, { path: addBasePath('/indices/reload'), validate: { body: bodySchema } },
async (ctx, req, res) => { async (context, request, response) => {
const { indexNames = [] } = (req.body as typeof bodySchema.type) ?? {}; const { client } = context.core.elasticsearch;
const { indexNames = [] } = (request.body as typeof bodySchema.type) ?? {};
try { try {
const indices = await fetchIndices( const indices = await fetchIndices(client, indexDataEnricher, indexNames);
ctx.core.elasticsearch.legacy.client.callAsCurrentUser, return response.ok({ body: indices });
indexDataEnricher, } catch (error) {
indexNames return handleEsError({ error, response });
);
return res.ok({ body: indices });
} catch (e) {
if (lib.isEsError(e)) {
return res.customError({
statusCode: e.statusCode,
body: e,
});
}
// Case: default
throw e;
} }
} }
); );

View file

@ -14,28 +14,20 @@ const bodySchema = schema.object({
indices: schema.arrayOf(schema.string()), indices: schema.arrayOf(schema.string()),
}); });
export function registerUnfreezeRoute({ router, lib }: RouteDependencies) { export function registerUnfreezeRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.post( router.post(
{ path: addBasePath('/indices/unfreeze'), validate: { body: bodySchema } }, { path: addBasePath('/indices/unfreeze'), validate: { body: bodySchema } },
async (ctx, req, res) => { async (context, request, response) => {
const { indices = [] } = req.body as typeof bodySchema.type; const { client } = context.core.elasticsearch;
const params = { const { indices = [] } = request.body as typeof bodySchema.type;
path: `/${encodeURIComponent(indices.join(','))}/_unfreeze`,
method: 'POST',
};
try { try {
await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('transport.request', params); await client.asCurrentUser.indices.unfreeze({
return res.ok(); index: indices.join(','),
} catch (e) {
if (lib.isEsError(e)) {
return res.customError({
statusCode: e.statusCode,
body: e,
}); });
} return response.ok();
// Case: default } catch (error) {
throw e; return handleEsError({ error, response });
} }
} }
); );

View file

@ -21,32 +21,23 @@ function formatHit(hit: { [key: string]: { mappings: any } }, indexName: string)
}; };
} }
export function registerMappingRoute({ router, lib }: RouteDependencies) { export function registerMappingRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.get( router.get(
{ path: addBasePath('/mapping/{indexName}'), validate: { params: paramsSchema } }, { path: addBasePath('/mapping/{indexName}'), validate: { params: paramsSchema } },
async (ctx, req, res) => { async (context, request, response) => {
const { indexName } = req.params as typeof paramsSchema.type; const { client } = context.core.elasticsearch;
const { indexName } = request.params as typeof paramsSchema.type;
const params = { const params = {
expand_wildcards: 'none', expand_wildcards: 'none',
index: indexName, index: indexName,
}; };
try { try {
const hit = await ctx.core.elasticsearch.legacy.client.callAsCurrentUser( const { body: hit } = await client.asCurrentUser.indices.getMapping(params);
'indices.getMapping', const responseBody = formatHit(hit, indexName);
params return response.ok({ body: responseBody });
); } catch (error) {
const response = formatHit(hit, indexName); return handleEsError({ error, response });
return res.ok({ body: response });
} catch (e) {
if (lib.isEsError(e)) {
return res.customError({
statusCode: e.statusCode,
body: e,
});
}
// Case: default
throw e;
} }
} }
); );

View file

@ -21,34 +21,25 @@ function formatHit(hit: { [key: string]: {} }) {
return hit[key]; return hit[key];
} }
export function registerLoadRoute({ router, lib }: RouteDependencies) { export function registerLoadRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.get( router.get(
{ path: addBasePath('/settings/{indexName}'), validate: { params: paramsSchema } }, { path: addBasePath('/settings/{indexName}'), validate: { params: paramsSchema } },
async (ctx, req, res) => { async (context, request, response) => {
const { indexName } = req.params as typeof paramsSchema.type; const { client } = context.core.elasticsearch;
const { indexName } = request.params as typeof paramsSchema.type;
const params = { const params = {
expandWildcards: 'none', expand_wildcards: 'none',
flatSettings: false, flat_settings: false,
local: false, local: false,
includeDefaults: true, include_defaults: true,
index: indexName, index: indexName,
}; };
try { try {
const hit = await ctx.core.elasticsearch.legacy.client.callAsCurrentUser( const { body: hit } = await client.asCurrentUser.indices.getSettings(params);
'indices.getSettings', return response.ok({ body: formatHit(hit) });
params } catch (error) {
); return handleEsError({ error, response });
return res.ok({ body: formatHit(hit) });
} catch (e) {
if (lib.isEsError(e)) {
return res.customError({
statusCode: e.statusCode,
body: e,
});
}
// Case: default
throw e;
} }
} }
); );

View file

@ -16,37 +16,28 @@ const paramsSchema = schema.object({
indexName: schema.string(), indexName: schema.string(),
}); });
export function registerUpdateRoute({ router, lib }: RouteDependencies) { export function registerUpdateRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.put( router.put(
{ {
path: addBasePath('/settings/{indexName}'), path: addBasePath('/settings/{indexName}'),
validate: { body: bodySchema, params: paramsSchema }, validate: { body: bodySchema, params: paramsSchema },
}, },
async (ctx, req, res) => { async (context, request, response) => {
const { indexName } = req.params as typeof paramsSchema.type; const { client } = context.core.elasticsearch;
const { indexName } = request.params as typeof paramsSchema.type;
const params = { const params = {
ignoreUnavailable: true, ignore_unavailable: true,
allowNoIndices: false, allow_no_indices: false,
expandWildcards: 'none', expand_wildcards: 'none',
index: indexName, index: indexName,
body: req.body, body: request.body,
}; };
try { try {
const response = await ctx.core.elasticsearch.legacy.client.callAsCurrentUser( const { body: responseBody } = await client.asCurrentUser.indices.putSettings(params);
'indices.putSettings', return response.ok({ body: responseBody });
params } catch (error) {
); return handleEsError({ error, response });
return res.ok({ body: response });
} catch (e) {
if (lib.isEsError(e)) {
return res.customError({
statusCode: e.statusCode,
body: e,
});
}
// Case: default
throw e;
} }
} }
); );

View file

@ -6,6 +6,7 @@
*/ */
import { schema } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema';
import type { estypes } from '@elastic/elasticsearch';
import { RouteDependencies } from '../../../types'; import { RouteDependencies } from '../../../types';
import { addBasePath } from '../index'; import { addBasePath } from '../index';
@ -14,40 +15,37 @@ const paramsSchema = schema.object({
indexName: schema.string(), indexName: schema.string(),
}); });
function formatHit(hit: { _shards: any; indices: { [key: string]: any } }, indexName: string) { interface Hit {
_shards: unknown;
indices?: Record<string, estypes.IndicesStatsIndicesStats>;
}
function formatHit(hit: Hit, indexName: string) {
const { _shards, indices } = hit; const { _shards, indices } = hit;
const stats = indices[indexName]; const stats = indices![indexName];
return { return {
_shards, _shards,
stats, stats,
}; };
} }
export function registerStatsRoute({ router, lib }: RouteDependencies) { export function registerStatsRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.get( router.get(
{ path: addBasePath('/stats/{indexName}'), validate: { params: paramsSchema } }, { path: addBasePath('/stats/{indexName}'), validate: { params: paramsSchema } },
async (ctx, req, res) => { async (context, request, response) => {
const { indexName } = req.params as typeof paramsSchema.type; const { client } = context.core.elasticsearch;
const { indexName } = request.params as typeof paramsSchema.type;
const params = { const params = {
expand_wildcards: 'none', expand_wildcards: 'none',
index: indexName, index: indexName,
}; };
try { try {
const hit = await ctx.core.elasticsearch.legacy.client.callAsCurrentUser( const { body: hit } = await client.asCurrentUser.indices.stats(params);
'indices.stats',
params return response.ok({ body: formatHit(hit, indexName) });
); } catch (error) {
return res.ok({ body: formatHit(hit, indexName) }); return handleEsError({ error, response });
} catch (e) {
if (lib.isEsError(e)) {
return res.customError({
statusCode: e.statusCode,
body: e,
});
}
// Case: default
throw e;
} }
} }
); );

View file

@ -5,32 +5,33 @@
* 2.0. * 2.0.
*/ */
import { IScopedClusterClient } from 'kibana/server';
import { serializeTemplate, serializeLegacyTemplate } from '../../../../common/lib'; import { serializeTemplate, serializeLegacyTemplate } from '../../../../common/lib';
import { TemplateDeserialized, LegacyTemplateSerialized } from '../../../../common'; import { TemplateDeserialized, LegacyTemplateSerialized } from '../../../../common';
import { CallAsCurrentUser } from '../../../types';
export const doesTemplateExist = async ({ export const doesTemplateExist = async ({
name, name,
callAsCurrentUser, client,
isLegacy, isLegacy,
}: { }: {
name: string; name: string;
callAsCurrentUser: CallAsCurrentUser; client: IScopedClusterClient;
isLegacy?: boolean; isLegacy?: boolean;
}) => { }) => {
if (isLegacy) { if (isLegacy) {
return await callAsCurrentUser('indices.existsTemplate', { name }); return await client.asCurrentUser.indices.existsTemplate({ name });
} }
return await callAsCurrentUser('dataManagement.existsTemplate', { name });
return await client.asCurrentUser.indices.existsIndexTemplate({ name });
}; };
export const saveTemplate = async ({ export const saveTemplate = async ({
template, template,
callAsCurrentUser, client,
isLegacy, isLegacy,
}: { }: {
template: TemplateDeserialized; template: TemplateDeserialized;
callAsCurrentUser: CallAsCurrentUser; client: IScopedClusterClient;
isLegacy?: boolean; isLegacy?: boolean;
}) => { }) => {
const serializedTemplate = isLegacy const serializedTemplate = isLegacy
@ -48,8 +49,9 @@ export const saveTemplate = async ({
aliases, aliases,
} = serializedTemplate as LegacyTemplateSerialized; } = serializedTemplate as LegacyTemplateSerialized;
return await callAsCurrentUser('indices.putTemplate', { return await client.asCurrentUser.indices.putTemplate({
name: template.name, name: template.name,
// @ts-expect-error @elastic/elasticsearch not assignable to parameter of type 'IndicesPutTemplateRequest'
order, order,
body: { body: {
index_patterns, index_patterns,
@ -61,8 +63,9 @@ export const saveTemplate = async ({
}); });
} }
return await callAsCurrentUser('dataManagement.saveComposableIndexTemplate', { return await client.asCurrentUser.indices.putIndexTemplate({
name: template.name, name: template.name,
// @ts-expect-error @elastic/elasticsearch Type 'LegacyTemplateSerialized | TemplateSerialized' is not assignable
body: serializedTemplate, body: serializedTemplate,
}); });
}; };

View file

@ -15,25 +15,27 @@ import { saveTemplate, doesTemplateExist } from './lib';
const bodySchema = templateSchema; const bodySchema = templateSchema;
export function registerCreateRoute({ router, lib }: RouteDependencies) { export function registerCreateRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.post( router.post(
{ path: addBasePath('/index_templates'), validate: { body: bodySchema } }, { path: addBasePath('/index_templates'), validate: { body: bodySchema } },
async (ctx, req, res) => { async (context, request, response) => {
const { callAsCurrentUser } = ctx.dataManagement!.client; const { client } = context.core.elasticsearch;
const template = req.body as TemplateDeserialized; const template = request.body as TemplateDeserialized;
try {
const { const {
_kbnMeta: { isLegacy }, _kbnMeta: { isLegacy },
} = template; } = template;
// Check that template with the same name doesn't already exist // Check that template with the same name doesn't already exist
const templateExists = await doesTemplateExist({ const { body: templateExists } = await doesTemplateExist({
name: template.name, name: template.name,
callAsCurrentUser, client,
isLegacy, isLegacy,
}); });
if (templateExists) { if (templateExists) {
return res.conflict({ return response.conflict({
body: new Error( body: new Error(
i18n.translate('xpack.idxMgmt.createRoute.duplicateTemplateIdErrorMessage', { i18n.translate('xpack.idxMgmt.createRoute.duplicateTemplateIdErrorMessage', {
defaultMessage: "There is already a template with name '{name}'.", defaultMessage: "There is already a template with name '{name}'.",
@ -45,24 +47,12 @@ export function registerCreateRoute({ router, lib }: RouteDependencies) {
}); });
} }
try {
// Otherwise create new index template // Otherwise create new index template
const response = await saveTemplate({ template, callAsCurrentUser, isLegacy }); const { body: responseBody } = await saveTemplate({ template, client, isLegacy });
return res.ok({ body: response }); return response.ok({ body: responseBody });
} catch (e) { } catch (error) {
if (lib.isEsError(e)) { return handleEsError({ error, response });
const error = lib.parseEsError(e.response);
return res.customError({
statusCode: e.statusCode,
body: {
message: error.message,
attributes: error,
},
});
}
// Case: default
throw e;
} }
} }
); );

View file

@ -9,7 +9,6 @@ import { schema, TypeOf } from '@kbn/config-schema';
import { RouteDependencies } from '../../../types'; import { RouteDependencies } from '../../../types';
import { addBasePath } from '../index'; import { addBasePath } from '../index';
import { wrapEsError } from '../../helpers';
import { TemplateDeserialized } from '../../../../common'; import { TemplateDeserialized } from '../../../../common';
@ -22,16 +21,19 @@ const bodySchema = schema.object({
), ),
}); });
export function registerDeleteRoute({ router }: RouteDependencies) { export function registerDeleteRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.post( router.post(
{ {
path: addBasePath('/delete_index_templates'), path: addBasePath('/delete_index_templates'),
validate: { body: bodySchema }, validate: { body: bodySchema },
}, },
async (ctx, req, res) => { async (context, request, response) => {
const { callAsCurrentUser } = ctx.dataManagement!.client; const { client } = context.core.elasticsearch;
const { templates } = req.body as TypeOf<typeof bodySchema>; const { templates } = request.body as TypeOf<typeof bodySchema>;
const response: { templatesDeleted: Array<TemplateDeserialized['name']>; errors: any[] } = { const responseBody: {
templatesDeleted: Array<TemplateDeserialized['name']>;
errors: any[];
} = {
templatesDeleted: [], templatesDeleted: [],
errors: [], errors: [],
}; };
@ -40,26 +42,26 @@ export function registerDeleteRoute({ router }: RouteDependencies) {
templates.map(async ({ name, isLegacy }) => { templates.map(async ({ name, isLegacy }) => {
try { try {
if (isLegacy) { if (isLegacy) {
await callAsCurrentUser('indices.deleteTemplate', { await client.asCurrentUser.indices.deleteTemplate({
name, name,
}); });
} else { } else {
await callAsCurrentUser('dataManagement.deleteComposableIndexTemplate', { await client.asCurrentUser.indices.deleteIndexTemplate({
name, name,
}); });
} }
return response.templatesDeleted.push(name); return responseBody.templatesDeleted.push(name);
} catch (e) { } catch (error) {
return response.errors.push({ return responseBody.errors.push({
name, name,
error: wrapEsError(e), error: handleEsError({ error, response }),
}); });
} }
}) })
); );
return res.ok({ body: response }); return response.ok({ body: responseBody });
} }
); );
} }

View file

@ -17,17 +17,19 @@ import { getCloudManagedTemplatePrefix } from '../../../lib/get_managed_template
import { RouteDependencies } from '../../../types'; import { RouteDependencies } from '../../../types';
import { addBasePath } from '../index'; import { addBasePath } from '../index';
export function registerGetAllRoute({ router, lib: { isEsError } }: RouteDependencies) { export function registerGetAllRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.get({ path: addBasePath('/index_templates'), validate: false }, async (ctx, req, res) => { router.get(
const { callAsCurrentUser } = ctx.dataManagement!.client; { path: addBasePath('/index_templates'), validate: false },
async (context, request, response) => {
const { client } = context.core.elasticsearch;
try { try {
const cloudManagedTemplatePrefix = await getCloudManagedTemplatePrefix(callAsCurrentUser); const cloudManagedTemplatePrefix = await getCloudManagedTemplatePrefix(client);
const legacyTemplatesEs = await callAsCurrentUser('indices.getTemplate'); const { body: legacyTemplatesEs } = await client.asCurrentUser.indices.getTemplate();
const { index_templates: templatesEs } = await callAsCurrentUser( const {
'dataManagement.getComposableIndexTemplates' body: { index_templates: templatesEs },
); } = await client.asCurrentUser.indices.getIndexTemplate();
const legacyTemplates = deserializeLegacyTemplateList( const legacyTemplates = deserializeLegacyTemplateList(
legacyTemplatesEs, legacyTemplatesEs,
@ -40,18 +42,12 @@ export function registerGetAllRoute({ router, lib: { isEsError } }: RouteDepende
legacyTemplates, legacyTemplates,
}; };
return res.ok({ body }); return response.ok({ body });
} catch (error) { } catch (error) {
if (isEsError(error)) { return handleEsError({ error, response });
return res.customError({
statusCode: error.statusCode,
body: error,
});
} }
// Case: default
throw error;
} }
}); );
} }
const paramsSchema = schema.object({ const paramsSchema = schema.object({
@ -63,26 +59,27 @@ const querySchema = schema.object({
legacy: schema.maybe(schema.oneOf([schema.literal('true'), schema.literal('false')])), legacy: schema.maybe(schema.oneOf([schema.literal('true'), schema.literal('false')])),
}); });
export function registerGetOneRoute({ router, lib }: RouteDependencies) { export function registerGetOneRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.get( router.get(
{ {
path: addBasePath('/index_templates/{name}'), path: addBasePath('/index_templates/{name}'),
validate: { params: paramsSchema, query: querySchema }, validate: { params: paramsSchema, query: querySchema },
}, },
async (ctx, req, res) => { async (context, request, response) => {
const { name } = req.params as TypeOf<typeof paramsSchema>; const { client } = context.core.elasticsearch;
const { callAsCurrentUser } = ctx.dataManagement!.client; const { name } = request.params as TypeOf<typeof paramsSchema>;
const isLegacy = (request.query as TypeOf<typeof querySchema>).legacy === 'true';
const isLegacy = (req.query as TypeOf<typeof querySchema>).legacy === 'true';
try { try {
const cloudManagedTemplatePrefix = await getCloudManagedTemplatePrefix(callAsCurrentUser); const cloudManagedTemplatePrefix = await getCloudManagedTemplatePrefix(client);
if (isLegacy) { if (isLegacy) {
const indexTemplateByName = await callAsCurrentUser('indices.getTemplate', { name }); const { body: indexTemplateByName } = await client.asCurrentUser.indices.getTemplate({
name,
});
if (indexTemplateByName[name]) { if (indexTemplateByName[name]) {
return res.ok({ return response.ok({
body: deserializeLegacyTemplate( body: deserializeLegacyTemplate(
{ ...indexTemplateByName[name], name }, { ...indexTemplateByName[name], name },
cloudManagedTemplatePrefix cloudManagedTemplatePrefix
@ -91,11 +88,11 @@ export function registerGetOneRoute({ router, lib }: RouteDependencies) {
} }
} else { } else {
const { const {
index_templates: indexTemplates, body: { index_templates: indexTemplates },
} = await callAsCurrentUser('dataManagement.getComposableIndexTemplate', { name }); } = await client.asCurrentUser.indices.getIndexTemplate({ name });
if (indexTemplates.length > 0) { if (indexTemplates.length > 0) {
return res.ok({ return response.ok({
body: deserializeTemplate( body: deserializeTemplate(
{ ...indexTemplates[0].index_template, name }, { ...indexTemplates[0].index_template, name },
cloudManagedTemplatePrefix cloudManagedTemplatePrefix
@ -104,16 +101,9 @@ export function registerGetOneRoute({ router, lib }: RouteDependencies) {
} }
} }
return res.notFound(); return response.notFound();
} catch (e) { } catch (error) {
if (lib.isEsError(e)) { return handleEsError({ error, response });
return res.customError({
statusCode: e.statusCode,
body: e,
});
}
// Case: default
throw e;
} }
} }
); );

View file

@ -12,35 +12,30 @@ import { addBasePath } from '../index';
const bodySchema = schema.object({}, { unknowns: 'allow' }); const bodySchema = schema.object({}, { unknowns: 'allow' });
export function registerSimulateRoute({ router, lib }: RouteDependencies) { export function registerSimulateRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.post( router.post(
{ {
path: addBasePath('/index_templates/simulate'), path: addBasePath('/index_templates/simulate'),
validate: { body: bodySchema }, validate: { body: bodySchema },
}, },
async (ctx, req, res) => { async (context, request, response) => {
const { callAsCurrentUser } = ctx.dataManagement!.client; const { client } = context.core.elasticsearch;
const template = req.body as TypeOf<typeof bodySchema>; const template = request.body as TypeOf<typeof bodySchema>;
try { try {
const templatePreview = await callAsCurrentUser('dataManagement.simulateTemplate', { const { body: templatePreview } = await client.asCurrentUser.indices.simulateTemplate({
body: template,
});
return res.ok({ body: templatePreview });
} catch (e) {
if (lib.isEsError(e)) {
const error = lib.parseEsError(e.response);
return res.customError({
statusCode: e.statusCode,
body: { body: {
message: error.message, ...template,
attributes: error, // Until ES fixes a bug on their side we need to send a fake index pattern
// that won't match any indices.
// Issue: https://github.com/elastic/elasticsearch/issues/59152
index_patterns: ['a_fake_index_pattern_that_wont_match_any_indices'],
}, },
}); });
}
// Case: default return response.ok({ body: templatePreview });
throw e; } catch (error) {
return handleEsError({ error, response });
} }
} }
); );

View file

@ -18,45 +18,35 @@ const paramsSchema = schema.object({
name: schema.string(), name: schema.string(),
}); });
export function registerUpdateRoute({ router, lib }: RouteDependencies) { export function registerUpdateRoute({ router, lib: { handleEsError } }: RouteDependencies) {
router.put( router.put(
{ {
path: addBasePath('/index_templates/{name}'), path: addBasePath('/index_templates/{name}'),
validate: { body: bodySchema, params: paramsSchema }, validate: { body: bodySchema, params: paramsSchema },
}, },
async (ctx, req, res) => { async (context, request, response) => {
const { callAsCurrentUser } = ctx.dataManagement!.client; const { client } = context.core.elasticsearch;
const { name } = req.params as typeof paramsSchema.type; const { name } = request.params as typeof paramsSchema.type;
const template = req.body as TemplateDeserialized; const template = request.body as TemplateDeserialized;
try {
const { const {
_kbnMeta: { isLegacy }, _kbnMeta: { isLegacy },
} = template; } = template;
// Verify the template exists (ES will throw 404 if not) // Verify the template exists (ES will throw 404 if not)
const doesExist = await doesTemplateExist({ name, callAsCurrentUser, isLegacy }); const { body: templateExists } = await doesTemplateExist({ name, client, isLegacy });
if (!doesExist) { if (!templateExists) {
return res.notFound(); return response.notFound();
} }
try {
// Next, update index template // Next, update index template
const response = await saveTemplate({ template, callAsCurrentUser, isLegacy }); const { body: responseBody } = await saveTemplate({ template, client, isLegacy });
return res.ok({ body: response }); return response.ok({ body: responseBody });
} catch (e) { } catch (error) {
if (lib.isEsError(e)) { return handleEsError({ error, response });
const error = lib.parseEsError(e.response);
return res.customError({
statusCode: e.statusCode,
body: {
message: error.message,
attributes: error,
},
});
}
// Case: default
throw e;
} }
} }
); );

View file

@ -1,58 +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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
const extractCausedByChain = (causedBy: any = {}, accumulator: any[] = []): any => {
const { reason, caused_by } = causedBy; // eslint-disable-line @typescript-eslint/naming-convention
if (reason) {
accumulator.push(reason);
}
if (caused_by) {
return extractCausedByChain(caused_by, accumulator);
}
return accumulator;
};
/**
* Wraps an error thrown by the ES JS client into a Boom error response and returns it
*
* @param err Object Error thrown by ES JS client
* @param statusCodeToMessageMap Object Optional map of HTTP status codes => error messages
* @return Object Boom error response
*/
export const wrapEsError = (err: any, statusCodeToMessageMap: any = {}) => {
const { statusCode, response } = err;
const {
error: {
root_cause = [], // eslint-disable-line @typescript-eslint/naming-convention
caused_by = {}, // eslint-disable-line @typescript-eslint/naming-convention
} = {},
} = JSON.parse(response);
// If no custom message if specified for the error's status code, just
// wrap the error as a Boom error response, include the additional information from ES, and return it
if (!statusCodeToMessageMap[statusCode]) {
// const boomError = Boom.boomify(err, { statusCode });
const error: any = { statusCode };
// The caused_by chain has the most information so use that if it's available. If not then
// settle for the root_cause.
const causedByChain = extractCausedByChain(caused_by);
const defaultCause = root_cause.length ? extractCausedByChain(root_cause[0]) : undefined;
error.cause = causedByChain.length ? causedByChain : defaultCause;
return error;
}
// Otherwise, use the custom message to create a Boom error response and
// return it
const message = statusCodeToMessageMap[statusCode];
return { message, statusCode };
};

View file

@ -5,10 +5,10 @@
* 2.0. * 2.0.
*/ */
import { CallAsCurrentUser } from '../types'; import { IScopedClusterClient } from 'kibana/server';
import { Index } from '../index'; import { Index } from '../index';
export type Enricher = (indices: Index[], callAsCurrentUser: CallAsCurrentUser) => Promise<Index[]>; export type Enricher = (indices: Index[], client: IScopedClusterClient) => Promise<Index[]>;
export class IndexDataEnricher { export class IndexDataEnricher {
private readonly _enrichers: Enricher[] = []; private readonly _enrichers: Enricher[] = [];
@ -19,14 +19,14 @@ export class IndexDataEnricher {
public enrichIndices = async ( public enrichIndices = async (
indices: Index[], indices: Index[],
callAsCurrentUser: CallAsCurrentUser client: IScopedClusterClient
): Promise<Index[]> => { ): Promise<Index[]> => {
let enrichedIndices = indices; let enrichedIndices = indices;
for (let i = 0; i < this.enrichers.length; i++) { for (let i = 0; i < this.enrichers.length; i++) {
const dataEnricher = this.enrichers[i]; const dataEnricher = this.enrichers[i];
try { try {
const dataEnricherResponse = await dataEnricher(enrichedIndices, callAsCurrentUser); const dataEnricherResponse = await dataEnricher(enrichedIndices, client);
enrichedIndices = dataEnricherResponse; enrichedIndices = dataEnricherResponse;
} catch (e) { } catch (e) {
// silently swallow enricher response errors // silently swallow enricher response errors

View file

@ -5,8 +5,4 @@
* 2.0. * 2.0.
*/ */
export { export { handleEsError } from '../../../../src/plugins/es_ui_shared/server';
isEsError,
parseEsError,
handleEsError,
} from '../../../../src/plugins/es_ui_shared/server';

View file

@ -5,17 +5,13 @@
* 2.0. * 2.0.
*/ */
import type { import { IRouter } from 'src/core/server';
LegacyScopedClusterClient,
ILegacyScopedClusterClient,
IRouter,
RequestHandlerContext,
} from 'src/core/server';
import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server';
import { LicensingPluginSetup } from '../../licensing/server'; import { LicensingPluginSetup } from '../../licensing/server';
import { SecurityPluginSetup } from '../../security/server'; import { SecurityPluginSetup } from '../../security/server';
import { IndexDataEnricher } from './services'; import { IndexDataEnricher } from './services';
import { isEsError, parseEsError, handleEsError } from './shared_imports'; import { handleEsError } from './shared_imports';
export interface Dependencies { export interface Dependencies {
security: SecurityPluginSetup; security: SecurityPluginSetup;
@ -24,39 +20,12 @@ export interface Dependencies {
} }
export interface RouteDependencies { export interface RouteDependencies {
router: IndexManagementRouter; router: IRouter;
config: { config: {
isSecurityEnabled: () => boolean; isSecurityEnabled: () => boolean;
}; };
indexDataEnricher: IndexDataEnricher; indexDataEnricher: IndexDataEnricher;
lib: { lib: {
isEsError: typeof isEsError;
parseEsError: typeof parseEsError;
handleEsError: typeof handleEsError; handleEsError: typeof handleEsError;
}; };
} }
export type CallAsCurrentUser = LegacyScopedClusterClient['callAsCurrentUser'];
export interface DataManagementContext {
client: ILegacyScopedClusterClient;
}
/**
* @internal
*/
export interface IndexManagementApiRequestHandlerContext {
client: ILegacyScopedClusterClient;
}
/**
* @internal
*/
export interface IndexManagementRequestHandlerContext extends RequestHandlerContext {
dataManagement: IndexManagementApiRequestHandlerContext;
}
/**
* @internal
*/
export type IndexManagementRouter = IRouter<IndexManagementRequestHandlerContext>;

View file

@ -5,20 +5,19 @@
* 2.0. * 2.0.
*/ */
import { IScopedClusterClient } from 'kibana/server';
import { Index } from '../../../plugins/index_management/server'; import { Index } from '../../../plugins/index_management/server';
export const rollupDataEnricher = async (indicesList: Index[], callWithRequest: any) => { export const rollupDataEnricher = async (indicesList: Index[], client: IScopedClusterClient) => {
if (!indicesList || !indicesList.length) { if (!indicesList || !indicesList.length) {
return Promise.resolve(indicesList); return Promise.resolve(indicesList);
} }
const params = {
path: '/_all/_rollup/data',
method: 'GET',
};
try { try {
const rollupJobData = await callWithRequest('transport.request', params); const { body: rollupJobData } = await client.asCurrentUser.rollup.getRollupIndexCaps({
index: '_all',
});
return indicesList.map((index) => { return indicesList.map((index) => {
const isRollupIndex = !!rollupJobData[index.name]; const isRollupIndex = !!rollupJobData[index.name];
return { return {

View file

@ -281,8 +281,19 @@ export default function ({ getService }: FtrProviderContext) {
expect(body).to.eql({ expect(body).to.eql({
statusCode: 404, statusCode: 404,
error: 'Not Found', error: 'Not Found',
message: message: 'component template matching [component_does_not_exist] not found',
'[resource_not_found_exception] component template matching [component_does_not_exist] not found', attributes: {
error: {
reason: 'component template matching [component_does_not_exist] not found',
root_cause: [
{
reason: 'component template matching [component_does_not_exist] not found',
type: 'resource_not_found_exception',
},
],
type: 'resource_not_found_exception',
},
},
}); });
}); });
}); });
@ -356,10 +367,19 @@ export default function ({ getService }: FtrProviderContext) {
const uri = `${API_BASE_PATH}/component_templates/${componentTemplateName},${COMPONENT_DOES_NOT_EXIST}`; const uri = `${API_BASE_PATH}/component_templates/${componentTemplateName},${COMPONENT_DOES_NOT_EXIST}`;
const { body } = await supertest.delete(uri).set('kbn-xsrf', 'xxx').expect(200); const { body } = await supertest.delete(uri).set('kbn-xsrf', 'xxx').expect(200);
expect(body.itemsDeleted).to.eql([componentTemplateName]); expect(body.itemsDeleted).to.eql([componentTemplateName]);
expect(body.errors[0].name).to.eql(COMPONENT_DOES_NOT_EXIST); expect(body.errors[0].name).to.eql(COMPONENT_DOES_NOT_EXIST);
expect(body.errors[0].error.msg).to.contain('resource_not_found_exception');
expect(body.errors[0].error.payload.attributes.error).to.eql({
root_cause: [
{
type: 'resource_not_found_exception',
reason: 'component_does_not_exist',
},
],
type: 'resource_not_found_exception',
reason: 'component_does_not_exist',
});
}); });
}); });

View file

@ -14,11 +14,11 @@ import { DataStream } from '../../../../../plugins/index_management/common';
export default function ({ getService }: FtrProviderContext) { export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest'); const supertest = getService('supertest');
const es = getService('legacyEs'); const es = getService('es');
const createDataStream = async (name: string) => { const createDataStream = async (name: string) => {
// A data stream requires an index template before it can be created. // A data stream requires an index template before it can be created.
await es.dataManagement.saveComposableIndexTemplate({ await es.indices.putIndexTemplate({
name, name,
body: { body: {
// We need to match the names of backing indices with this template. // We need to match the names of backing indices with this template.
@ -36,15 +36,15 @@ export default function ({ getService }: FtrProviderContext) {
}, },
}); });
await es.dataManagement.createDataStream({ name }); await es.indices.createDataStream({ name });
}; };
const deleteComposableIndexTemplate = async (name: string) => { const deleteComposableIndexTemplate = async (name: string) => {
await es.dataManagement.deleteComposableIndexTemplate({ name }); await es.indices.deleteIndexTemplate({ name });
}; };
const deleteDataStream = async (name: string) => { const deleteDataStream = async (name: string) => {
await es.dataManagement.deleteDataStream({ name }); await es.indices.deleteDataStream({ name });
await deleteComposableIndexTemplate(name); await deleteComposableIndexTemplate(name);
}; };

View file

@ -56,13 +56,17 @@ export default function ({ getService }) {
const index = await createIndex(); const index = await createIndex();
// Make sure the index is open // Make sure the index is open
const [cat1] = await catIndex(index); const {
body: [cat1],
} = await catIndex(index);
expect(cat1.status).to.be('open'); expect(cat1.status).to.be('open');
await closeIndex(index).expect(200); await closeIndex(index).expect(200);
// Make sure the index has been closed // Make sure the index has been closed
const [cat2] = await catIndex(index); const {
body: [cat2],
} = await catIndex(index);
expect(cat2.status).to.be('close'); expect(cat2.status).to.be('close');
}); });
}); });
@ -78,13 +82,17 @@ export default function ({ getService }) {
await closeIndex(index); await closeIndex(index);
// Make sure the index is closed // Make sure the index is closed
const [cat1] = await catIndex(index); const {
body: [cat1],
} = await catIndex(index);
expect(cat1.status).to.be('close'); expect(cat1.status).to.be('close');
await openIndex(index).expect(200); await openIndex(index).expect(200);
// Make sure the index is opened // Make sure the index is opened
const [cat2] = await catIndex(index); const {
body: [cat2],
} = await catIndex(index);
expect(cat2.status).to.be('open'); expect(cat2.status).to.be('open');
}); });
}); });
@ -93,12 +101,12 @@ export default function ({ getService }) {
it('should delete an index', async () => { it('should delete an index', async () => {
const index = await createIndex(); const index = await createIndex();
const indices1 = await catIndex(undefined, 'i'); const { body: indices1 } = await catIndex(undefined, 'i');
expect(indices1.map((index) => index.i)).to.contain(index); expect(indices1.map((index) => index.i)).to.contain(index);
await deleteIndex([index]).expect(200); await deleteIndex([index]).expect(200);
const indices2 = await catIndex(undefined, 'i'); const { body: indices2 } = await catIndex(undefined, 'i');
expect(indices2.map((index) => index.i)).not.to.contain(index); expect(indices2.map((index) => index.i)).not.to.contain(index);
}); });
@ -112,12 +120,16 @@ export default function ({ getService }) {
it('should flush an index', async () => { it('should flush an index', async () => {
const index = await createIndex(); const index = await createIndex();
const { indices: indices1 } = await indexStats(index, 'flush'); const {
body: { indices: indices1 },
} = await indexStats(index, 'flush');
expect(indices1[index].total.flush.total).to.be(0); expect(indices1[index].total.flush.total).to.be(0);
await flushIndex(index).expect(200); await flushIndex(index).expect(200);
const { indices: indices2 } = await indexStats(index, 'flush'); const {
body: { indices: indices2 },
} = await indexStats(index, 'flush');
expect(indices2[index].total.flush.total).to.be(1); expect(indices2[index].total.flush.total).to.be(1);
}); });
}); });
@ -126,12 +138,16 @@ export default function ({ getService }) {
it('should refresh an index', async () => { it('should refresh an index', async () => {
const index = await createIndex(); const index = await createIndex();
const { indices: indices1 } = await indexStats(index, 'refresh'); const {
body: { indices: indices1 },
} = await indexStats(index, 'refresh');
const previousRefreshes = indices1[index].total.refresh.total; const previousRefreshes = indices1[index].total.refresh.total;
await refreshIndex(index).expect(200); await refreshIndex(index).expect(200);
const { indices: indices2 } = await indexStats(index, 'refresh'); const {
body: { indices: indices2 },
} = await indexStats(index, 'refresh');
expect(indices2[index].total.refresh.total).to.be(previousRefreshes + 1); expect(indices2[index].total.refresh.total).to.be(previousRefreshes + 1);
}); });
}); });
@ -153,12 +169,16 @@ export default function ({ getService }) {
const index = await createIndex(); const index = await createIndex();
// "sth" correspond to search throttling. Frozen indices are normal indices // "sth" correspond to search throttling. Frozen indices are normal indices
// with search throttling turned on. // with search throttling turned on.
const [cat1] = await catIndex(index, 'sth'); const {
body: [cat1],
} = await catIndex(index, 'sth');
expect(cat1.sth).to.be('false'); expect(cat1.sth).to.be('false');
await freeze(index).expect(200); await freeze(index).expect(200);
const [cat2] = await catIndex(index, 'sth'); const {
body: [cat2],
} = await catIndex(index, 'sth');
expect(cat2.sth).to.be('true'); expect(cat2.sth).to.be('true');
}); });
}); });
@ -168,11 +188,15 @@ export default function ({ getService }) {
const index = await createIndex(); const index = await createIndex();
await freeze(index).expect(200); await freeze(index).expect(200);
const [cat1] = await catIndex(index, 'sth'); const {
body: [cat1],
} = await catIndex(index, 'sth');
expect(cat1.sth).to.be('true'); expect(cat1.sth).to.be('true');
await unfreeze(index).expect(200); await unfreeze(index).expect(200);
const [cat2] = await catIndex(index, 'sth'); const {
body: [cat2],
} = await catIndex(index, 'sth');
expect(cat2.sth).to.be('false'); expect(cat2.sth).to.be('false');
}); });
}); });

View file

@ -13,7 +13,7 @@ import { getRandomString } from './random';
* @param {ElasticsearchClient} es The Elasticsearch client instance * @param {ElasticsearchClient} es The Elasticsearch client instance
*/ */
export const initElasticsearchHelpers = (getService) => { export const initElasticsearchHelpers = (getService) => {
const es = getService('legacyEs'); const es = getService('es');
const esDeleteAllIndices = getService('esDeleteAllIndices'); const esDeleteAllIndices = getService('esDeleteAllIndices');
let indicesCreated = []; let indicesCreated = [];
@ -42,11 +42,11 @@ export const initElasticsearchHelpers = (getService) => {
componentTemplatesCreated.push(componentTemplate.name); componentTemplatesCreated.push(componentTemplate.name);
} }
return es.dataManagement.saveComponentTemplate(componentTemplate); return es.cluster.putComponentTemplate(componentTemplate);
}; };
const deleteComponentTemplate = (componentTemplateName) => { const deleteComponentTemplate = (componentTemplateName) => {
return es.dataManagement.deleteComponentTemplate({ name: componentTemplateName }); return es.cluster.deleteComponentTemplate({ name: componentTemplateName });
}; };
const cleanUpComponentTemplates = () => const cleanUpComponentTemplates = () =>

View file

@ -193,8 +193,8 @@ export default function ({ getService }) {
}); });
it('should parse the ES error and return the cause', async () => { it('should parse the ES error and return the cause', async () => {
const templateName = `template-${getRandomString()}`; const templateName = `template-create-parse-es-error}`;
const payload = getTemplatePayload(templateName, [getRandomString()]); const payload = getTemplatePayload(templateName, ['create-parse-es-error']);
const runtime = { const runtime = {
myRuntimeField: { myRuntimeField: {
type: 'boolean', type: 'boolean',
@ -207,9 +207,9 @@ export default function ({ getService }) {
const { body } = await createTemplate(payload).expect(400); const { body } = await createTemplate(payload).expect(400);
expect(body.attributes).an('object'); expect(body.attributes).an('object');
expect(body.attributes.message).contain('template after composition is invalid'); expect(body.attributes.error.reason).contain('template after composition is invalid');
// one of the item of the cause array should point to our script // one of the item of the cause array should point to our script
expect(body.attributes.cause.join(',')).contain('"hello with error'); expect(body.attributes.causes.join(',')).contain('"hello with error');
}); });
}); });
@ -220,7 +220,7 @@ export default function ({ getService }) {
await createTemplate(indexTemplate).expect(200); await createTemplate(indexTemplate).expect(200);
let catTemplateResponse = await catTemplate(templateName); let { body: catTemplateResponse } = await catTemplate(templateName);
const { name, version } = indexTemplate; const { name, version } = indexTemplate;
@ -234,7 +234,7 @@ export default function ({ getService }) {
200 200
); );
catTemplateResponse = await catTemplate(templateName); ({ body: catTemplateResponse } = await catTemplate(templateName));
expect( expect(
catTemplateResponse.find(({ name: templateName }) => templateName === name).version catTemplateResponse.find(({ name: templateName }) => templateName === name).version
@ -247,7 +247,7 @@ export default function ({ getService }) {
await createTemplate(legacyIndexTemplate).expect(200); await createTemplate(legacyIndexTemplate).expect(200);
let catTemplateResponse = await catTemplate(templateName); let { body: catTemplateResponse } = await catTemplate(templateName);
const { name, version } = legacyIndexTemplate; const { name, version } = legacyIndexTemplate;
@ -262,7 +262,7 @@ export default function ({ getService }) {
templateName templateName
).expect(200); ).expect(200);
catTemplateResponse = await catTemplate(templateName); ({ body: catTemplateResponse } = await catTemplate(templateName));
expect( expect(
catTemplateResponse.find(({ name: templateName }) => templateName === name).version catTemplateResponse.find(({ name: templateName }) => templateName === name).version
@ -270,8 +270,8 @@ export default function ({ getService }) {
}); });
it('should parse the ES error and return the cause', async () => { it('should parse the ES error and return the cause', async () => {
const templateName = `template-${getRandomString()}`; const templateName = `template-update-parse-es-error}`;
const payload = getTemplatePayload(templateName, [getRandomString()]); const payload = getTemplatePayload(templateName, ['update-parse-es-error']);
const runtime = { const runtime = {
myRuntimeField: { myRuntimeField: {
type: 'keyword', type: 'keyword',
@ -292,7 +292,7 @@ export default function ({ getService }) {
expect(body.attributes).an('object'); expect(body.attributes).an('object');
// one of the item of the cause array should point to our script // one of the item of the cause array should point to our script
expect(body.attributes.cause.join(',')).contain('"hello with error'); expect(body.attributes.causes.join(',')).contain('"hello with error');
}); });
}); });
@ -306,7 +306,7 @@ export default function ({ getService }) {
throw new Error(`Error creating template: ${createStatus} ${createBody.message}`); throw new Error(`Error creating template: ${createStatus} ${createBody.message}`);
} }
let catTemplateResponse = await catTemplate(templateName); let { body: catTemplateResponse } = await catTemplate(templateName);
expect( expect(
catTemplateResponse.find((template) => template.name === payload.name).name catTemplateResponse.find((template) => template.name === payload.name).name
@ -322,7 +322,7 @@ export default function ({ getService }) {
expect(deleteBody.errors).to.be.empty; expect(deleteBody.errors).to.be.empty;
expect(deleteBody.templatesDeleted[0]).to.equal(templateName); expect(deleteBody.templatesDeleted[0]).to.equal(templateName);
catTemplateResponse = await catTemplate(templateName); ({ body: catTemplateResponse } = await catTemplate(templateName));
expect(catTemplateResponse.find((template) => template.name === payload.name)).to.equal( expect(catTemplateResponse.find((template) => template.name === payload.name)).to.equal(
undefined undefined
@ -335,7 +335,7 @@ export default function ({ getService }) {
await createTemplate(payload).expect(200); await createTemplate(payload).expect(200);
let catTemplateResponse = await catTemplate(templateName); let { body: catTemplateResponse } = await catTemplate(templateName);
expect( expect(
catTemplateResponse.find((template) => template.name === payload.name).name catTemplateResponse.find((template) => template.name === payload.name).name
@ -348,7 +348,7 @@ export default function ({ getService }) {
expect(body.errors).to.be.empty; expect(body.errors).to.be.empty;
expect(body.templatesDeleted[0]).to.equal(templateName); expect(body.templatesDeleted[0]).to.equal(templateName);
catTemplateResponse = await catTemplate(templateName); ({ body: catTemplateResponse } = await catTemplate(templateName));
expect(catTemplateResponse.find((template) => template.name === payload.name)).to.equal( expect(catTemplateResponse.find((template) => template.name === payload.name)).to.equal(
undefined undefined

View file

@ -9,7 +9,6 @@ import { format as formatUrl } from 'url';
import * as legacyElasticsearch from 'elasticsearch'; import * as legacyElasticsearch from 'elasticsearch';
import { elasticsearchJsPlugin as indexManagementEsClientPlugin } from '../../../plugins/index_management/server/client/elasticsearch';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths // eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { DEFAULT_API_VERSION } from '../../../../src/core/server/elasticsearch/elasticsearch_config'; import { DEFAULT_API_VERSION } from '../../../../src/core/server/elasticsearch/elasticsearch_config';
@ -20,6 +19,5 @@ export function LegacyEsProvider({ getService }) {
apiVersion: DEFAULT_API_VERSION, apiVersion: DEFAULT_API_VERSION,
host: formatUrl(config.get('servers.elasticsearch')), host: formatUrl(config.get('servers.elasticsearch')),
requestTimeout: config.get('timeouts.esRequestTimeout'), requestTimeout: config.get('timeouts.esRequestTimeout'),
plugins: [indexManagementEsClientPlugin],
}); });
} }