mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* Add override pattern for fetch_indices
* Add tests for fetch_indices override pattern
* Include only aliases.
* Add negative check to the tests and added some comments
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
(cherry picked from commit 64c1d3cb2b
)
Co-authored-by: Efe Gürkan YALAMAN <efeguerkan.yalaman@elastic.co>
This commit is contained in:
parent
69dfad2689
commit
90ee18f0e2
4 changed files with 324 additions and 28 deletions
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* 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 mockSingleIndexResponse = {
|
||||
'search-regular-index': {
|
||||
aliases: {},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockSingleIndexStatsResponse = {
|
||||
indices: {
|
||||
'search-regular-index': {
|
||||
health: 'green',
|
||||
status: 'open',
|
||||
total: {
|
||||
docs: {
|
||||
count: 100,
|
||||
deleted: 0,
|
||||
},
|
||||
store: {
|
||||
size_in_bytes: 108000,
|
||||
},
|
||||
},
|
||||
uuid: '83a81e7e-5955-4255-b008-5d6961203f57',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockMultiIndexResponse = {
|
||||
'hidden-index': {
|
||||
aliases: {
|
||||
'alias-hidden-index': {},
|
||||
'search-alias-hidden-index': {},
|
||||
},
|
||||
settings: { index: { hidden: 'true' } },
|
||||
},
|
||||
'regular-index': {
|
||||
aliases: {
|
||||
'alias-regular-index': {},
|
||||
'search-alias-regular-index': {},
|
||||
},
|
||||
},
|
||||
'search-prefixed-hidden-index': {
|
||||
aliases: {
|
||||
'alias-search-prefixed-hidden-index': {},
|
||||
'search-alias-search-prefixed-hidden-index': {},
|
||||
},
|
||||
settings: { index: { hidden: 'true' } },
|
||||
},
|
||||
'search-prefixed-regular-index': {
|
||||
aliases: {
|
||||
'alias-search-prefixed-regular-index': {},
|
||||
'search-alias-search-prefixed-regular-index': {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockMultiStatsResponse: {
|
||||
indices: Record<string, { total: object; [k: string]: unknown }>;
|
||||
} = {
|
||||
indices: {
|
||||
'alias-hidden-index': {
|
||||
...mockSingleIndexStatsResponse.indices['search-regular-index'],
|
||||
},
|
||||
'alias-regular-index': {
|
||||
...mockSingleIndexStatsResponse.indices['search-regular-index'],
|
||||
},
|
||||
'alias-search-prefixed-hidden-index': {
|
||||
...mockSingleIndexStatsResponse.indices['search-regular-index'],
|
||||
},
|
||||
'alias-search-prefixed-regular-index': {
|
||||
...mockSingleIndexStatsResponse.indices['search-regular-index'],
|
||||
},
|
||||
'hidden-index': {
|
||||
...mockSingleIndexStatsResponse.indices['search-regular-index'],
|
||||
},
|
||||
'regular-index': {
|
||||
...mockSingleIndexStatsResponse.indices['search-regular-index'],
|
||||
},
|
||||
'search-alias-hidden-index': {
|
||||
...mockSingleIndexStatsResponse.indices['search-regular-index'],
|
||||
},
|
||||
'search-alias-regular-index': {
|
||||
...mockSingleIndexStatsResponse.indices['search-regular-index'],
|
||||
},
|
||||
'search-alias-search-prefixed-hidden-index': {
|
||||
...mockSingleIndexStatsResponse.indices['search-regular-index'],
|
||||
},
|
||||
'search-alias-search-prefixed-regular-index': {
|
||||
...mockSingleIndexStatsResponse.indices['search-regular-index'],
|
||||
},
|
||||
'search-prefixed-hidden-index': {
|
||||
...mockSingleIndexStatsResponse.indices['search-regular-index'],
|
||||
},
|
||||
'search-prefixed-regular-index': {
|
||||
...mockSingleIndexStatsResponse.indices['search-regular-index'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockPrivilegesResponse = Object.keys(mockMultiStatsResponse.indices).reduce<
|
||||
Record<string, unknown>
|
||||
>((acc, key) => {
|
||||
acc[key] = { manage: true, read: true };
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
export const getIndexReturnValue = (indexName: string) => {
|
||||
return {
|
||||
...mockMultiStatsResponse.indices[indexName],
|
||||
alias: indexName.startsWith('alias') || indexName.startsWith('search-alias'),
|
||||
count: 100,
|
||||
name: indexName,
|
||||
privileges: { manage: true, read: true },
|
||||
total: {
|
||||
...mockMultiStatsResponse.indices[indexName].total,
|
||||
store: {
|
||||
size_in_bytes: '105.47kb',
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
|
@ -5,6 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
getIndexReturnValue,
|
||||
mockMultiIndexResponse,
|
||||
mockMultiStatsResponse,
|
||||
mockPrivilegesResponse,
|
||||
} from '../../__mocks__/fetch_indices.mock';
|
||||
|
||||
import { ByteSizeValue } from '@kbn/config-schema';
|
||||
import { IScopedClusterClient } from '@kbn/core/server';
|
||||
|
||||
|
@ -13,6 +20,7 @@ import { fetchIndices } from './fetch_indices';
|
|||
describe('fetchIndices lib function', () => {
|
||||
const mockClient = {
|
||||
asCurrentUser: {
|
||||
count: jest.fn().mockReturnValue({ count: 100 }),
|
||||
indices: {
|
||||
get: jest.fn(),
|
||||
stats: jest.fn(),
|
||||
|
@ -20,7 +28,6 @@ describe('fetchIndices lib function', () => {
|
|||
security: {
|
||||
hasPrivileges: jest.fn(),
|
||||
},
|
||||
count: jest.fn().mockReturnValue({ count: 100 }),
|
||||
},
|
||||
asInternalUser: {},
|
||||
};
|
||||
|
@ -53,11 +60,11 @@ describe('fetchIndices lib function', () => {
|
|||
|
||||
mockClient.asCurrentUser.security.hasPrivileges.mockImplementation(() => ({
|
||||
index: {
|
||||
'index-without-prefix': { read: true, manage: true },
|
||||
'search-aliased': { read: true, manage: true },
|
||||
'search-double-aliased': { read: true, manage: true },
|
||||
'search-regular-index': { read: true, manage: true },
|
||||
'second-index': { read: true, manage: true },
|
||||
'index-without-prefix': { manage: true, read: true },
|
||||
'search-aliased': { manage: true, read: true },
|
||||
'search-double-aliased': { manage: true, read: true },
|
||||
'search-regular-index': { manage: true, read: true },
|
||||
'second-index': { manage: true, read: true },
|
||||
},
|
||||
}));
|
||||
|
||||
|
@ -76,12 +83,12 @@ describe('fetchIndices lib function', () => {
|
|||
fetchIndices(mockClient as unknown as IScopedClusterClient, 'search-*', false, true)
|
||||
).resolves.toEqual([
|
||||
{
|
||||
alias: false,
|
||||
count: 100,
|
||||
health: 'green',
|
||||
name: 'search-regular-index',
|
||||
privileges: { manage: true, read: true },
|
||||
status: 'open',
|
||||
alias: false,
|
||||
privileges: { read: true, manage: true },
|
||||
total: {
|
||||
docs: {
|
||||
count: 100,
|
||||
|
@ -125,12 +132,12 @@ describe('fetchIndices lib function', () => {
|
|||
fetchIndices(mockClient as unknown as IScopedClusterClient, 'search-*', true, true)
|
||||
).resolves.toEqual([
|
||||
{
|
||||
alias: false,
|
||||
count: 100,
|
||||
health: 'green',
|
||||
name: 'search-regular-index',
|
||||
privileges: { manage: true, read: true },
|
||||
status: 'open',
|
||||
alias: false,
|
||||
privileges: { read: true, manage: true },
|
||||
total: {
|
||||
docs: {
|
||||
count: 100,
|
||||
|
@ -368,4 +375,113 @@ describe('fetchIndices lib function', () => {
|
|||
).resolves.toEqual([]);
|
||||
expect(mockClient.asCurrentUser.indices.stats).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('alwaysShowSearchPattern', () => {
|
||||
beforeEach(() => {
|
||||
mockClient.asCurrentUser.indices.get.mockImplementation(() => mockMultiIndexResponse);
|
||||
mockClient.asCurrentUser.indices.stats.mockImplementation(() => mockMultiStatsResponse);
|
||||
|
||||
mockClient.asCurrentUser.security.hasPrivileges.mockImplementation(() => ({
|
||||
index: mockPrivilegesResponse,
|
||||
}));
|
||||
});
|
||||
|
||||
it('overrides hidden indices setting', async () => {
|
||||
const returnValue = await fetchIndices(
|
||||
mockClient as unknown as IScopedClusterClient,
|
||||
'*',
|
||||
false,
|
||||
true,
|
||||
'search-'
|
||||
);
|
||||
|
||||
// This is the list of mock indices and aliases that are:
|
||||
// - Non-hidden indices and aliases
|
||||
// - search- prefixed aliases that point to hidden indices
|
||||
expect(returnValue).toEqual(
|
||||
[
|
||||
'regular-index',
|
||||
'alias-regular-index',
|
||||
'search-alias-regular-index',
|
||||
'search-prefixed-regular-index',
|
||||
'alias-search-prefixed-regular-index',
|
||||
'search-alias-search-prefixed-regular-index',
|
||||
'search-alias-hidden-index',
|
||||
'search-alias-search-prefixed-hidden-index',
|
||||
].map(getIndexReturnValue)
|
||||
);
|
||||
|
||||
// This is the list of mock indices and aliases that are:
|
||||
// - Hidden indices
|
||||
// - aliases to hidden indices that has no prefix
|
||||
expect(returnValue).toEqual(
|
||||
expect.not.arrayContaining(
|
||||
[
|
||||
'hidden-index',
|
||||
'search-prefixed-hidden-index',
|
||||
'alias-hidden-index',
|
||||
'alias-search-prefixed-hidden-index',
|
||||
].map(getIndexReturnValue)
|
||||
)
|
||||
);
|
||||
|
||||
expect(mockClient.asCurrentUser.indices.get).toHaveBeenCalledWith({
|
||||
expand_wildcards: ['hidden', 'all'],
|
||||
features: ['aliases', 'settings'],
|
||||
filter_path: ['*.aliases', '*.settings.index.hidden'],
|
||||
index: '*',
|
||||
});
|
||||
|
||||
expect(mockClient.asCurrentUser.indices.stats).toHaveBeenCalledWith({
|
||||
expand_wildcards: ['hidden', 'all'],
|
||||
index: '*',
|
||||
metric: ['docs', 'store'],
|
||||
});
|
||||
|
||||
expect(mockClient.asCurrentUser.security.hasPrivileges).toHaveBeenCalledWith({
|
||||
index: [
|
||||
{
|
||||
names: expect.arrayContaining(Object.keys(mockMultiStatsResponse.indices)),
|
||||
privileges: ['read', 'manage'],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('returns everything if hidden indices set', async () => {
|
||||
const returnValue = await fetchIndices(
|
||||
mockClient as unknown as IScopedClusterClient,
|
||||
'*',
|
||||
true,
|
||||
true,
|
||||
'search-'
|
||||
);
|
||||
|
||||
expect(returnValue).toEqual(
|
||||
expect.arrayContaining(Object.keys(mockMultiStatsResponse.indices).map(getIndexReturnValue))
|
||||
);
|
||||
|
||||
expect(mockClient.asCurrentUser.indices.get).toHaveBeenCalledWith({
|
||||
expand_wildcards: ['hidden', 'all'],
|
||||
features: ['aliases', 'settings'],
|
||||
filter_path: ['*.aliases', '*.settings.index.hidden'],
|
||||
index: '*',
|
||||
});
|
||||
|
||||
expect(mockClient.asCurrentUser.indices.stats).toHaveBeenCalledWith({
|
||||
expand_wildcards: ['hidden', 'all'],
|
||||
index: '*',
|
||||
metric: ['docs', 'store'],
|
||||
});
|
||||
|
||||
expect(mockClient.asCurrentUser.security.hasPrivileges).toHaveBeenCalledWith({
|
||||
index: [
|
||||
{
|
||||
names: expect.arrayContaining(Object.keys(mockMultiStatsResponse.indices)),
|
||||
privileges: ['read', 'manage'],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -59,10 +59,13 @@ export const fetchIndices = async (
|
|||
client: IScopedClusterClient,
|
||||
indexPattern: string,
|
||||
returnHiddenIndices: boolean,
|
||||
includeAliases: boolean
|
||||
includeAliases: boolean,
|
||||
alwaysShowSearchPattern?: 'search-'
|
||||
): Promise<ElasticsearchIndexWithPrivileges[]> => {
|
||||
// This call retrieves alias and settings information about indices
|
||||
const expandWildcards: ExpandWildcard[] = returnHiddenIndices ? ['hidden', 'all'] : ['open'];
|
||||
// If we provide an override pattern with alwaysShowSearchPattern we get everything and filter out hiddens.
|
||||
const expandWildcards: ExpandWildcard[] =
|
||||
returnHiddenIndices || alwaysShowSearchPattern ? ['hidden', 'all'] : ['open'];
|
||||
const totalIndices = await client.asCurrentUser.indices.get({
|
||||
expand_wildcards: expandWildcards,
|
||||
// for better performance only compute aliases and settings of indices but not mappings
|
||||
|
@ -73,12 +76,22 @@ export const fetchIndices = async (
|
|||
index: indexPattern,
|
||||
});
|
||||
|
||||
// Index names that with one of their aliases match with the alwaysShowSearchPattern
|
||||
const alwaysShowPatternMatches = new Set<string>();
|
||||
|
||||
const indexAndAliasNames = Object.keys(totalIndices).reduce((accum, indexName) => {
|
||||
accum.push(indexName);
|
||||
|
||||
if (includeAliases) {
|
||||
const aliases = Object.keys(totalIndices[indexName].aliases!);
|
||||
aliases.forEach((alias) => accum.push(alias));
|
||||
aliases.forEach((alias) => {
|
||||
accum.push(alias);
|
||||
|
||||
// Add indexName to the set if an alias matches the pattern
|
||||
if (alwaysShowSearchPattern && alias.startsWith(alwaysShowSearchPattern)) {
|
||||
alwaysShowPatternMatches.add(indexName);
|
||||
}
|
||||
});
|
||||
}
|
||||
return accum;
|
||||
}, [] as string[]);
|
||||
|
@ -110,7 +123,36 @@ export const fetchIndices = async (
|
|||
|
||||
const indexCounts = await fetchIndexCounts(client, indexAndAliasNames);
|
||||
|
||||
return indicesNames
|
||||
// Index data to show even if they are hidden, set by alwaysShowSearchPattern
|
||||
const alwaysShowIndices = alwaysShowSearchPattern
|
||||
? Array.from(alwaysShowPatternMatches)
|
||||
.map((indexName: string) => {
|
||||
const indexData = totalIndices[indexName];
|
||||
const indexStats = indicesStats[indexName];
|
||||
return mapIndexStats(indexData, indexStats, indexName);
|
||||
})
|
||||
.flatMap(({ name, aliases, ...indexData }) => {
|
||||
const indicesAndAliases = [] as ElasticsearchIndexWithPrivileges[];
|
||||
|
||||
if (includeAliases) {
|
||||
aliases.forEach((alias) => {
|
||||
if (alias.startsWith(alwaysShowSearchPattern)) {
|
||||
indicesAndAliases.push({
|
||||
alias: true,
|
||||
count: indexCounts[alias] ?? 0,
|
||||
name: alias,
|
||||
privileges: { manage: false, read: false, ...indexPrivileges[name] },
|
||||
...indexData,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return indicesAndAliases;
|
||||
})
|
||||
: [];
|
||||
|
||||
const regularIndexData = indicesNames
|
||||
.map((indexName: string) => {
|
||||
const indexData = totalIndices[indexName];
|
||||
const indexStats = indicesStats[indexName];
|
||||
|
@ -120,30 +162,42 @@ export const fetchIndices = async (
|
|||
// expand aliases and add to results
|
||||
const indicesAndAliases = [] as ElasticsearchIndexWithPrivileges[];
|
||||
indicesAndAliases.push({
|
||||
name,
|
||||
count: indexCounts[name] ?? 0,
|
||||
alias: false,
|
||||
privileges: { read: false, manage: false, ...indexPrivileges[name] },
|
||||
count: indexCounts[name] ?? 0,
|
||||
name,
|
||||
privileges: { manage: false, read: false, ...indexPrivileges[name] },
|
||||
...indexData,
|
||||
});
|
||||
|
||||
if (includeAliases) {
|
||||
aliases.forEach((alias) => {
|
||||
indicesAndAliases.push({
|
||||
name: alias,
|
||||
count: indexCounts[alias] ?? 0,
|
||||
alias: true,
|
||||
privileges: { read: false, manage: false, ...indexPrivileges[name] },
|
||||
count: indexCounts[alias] ?? 0,
|
||||
name: alias,
|
||||
privileges: { manage: false, read: false, ...indexPrivileges[name] },
|
||||
...indexData,
|
||||
});
|
||||
});
|
||||
}
|
||||
return indicesAndAliases;
|
||||
})
|
||||
.filter(
|
||||
({ name }, index, array) =>
|
||||
// make list of aliases unique since we add an alias per index above
|
||||
// and aliases can point to multiple indices
|
||||
array.findIndex((engineData) => engineData.name === name) === index
|
||||
);
|
||||
});
|
||||
|
||||
const indexNamesAlreadyIncluded = regularIndexData.map(({ name }) => name);
|
||||
const indexNamesToInclude = alwaysShowIndices
|
||||
.map(({ name }) => name)
|
||||
.filter((name) => !indexNamesAlreadyIncluded.includes(name));
|
||||
|
||||
const itemsToInclude = alwaysShowIndices.filter(({ name }) => indexNamesToInclude.includes(name));
|
||||
|
||||
const indicesData = alwaysShowSearchPattern
|
||||
? ([...regularIndexData, ...itemsToInclude] as ElasticsearchIndexWithPrivileges[])
|
||||
: regularIndexData;
|
||||
|
||||
return indicesData.filter(
|
||||
({ name }, index, array) =>
|
||||
// make list of aliases unique since we add an alias per index above
|
||||
// and aliases can point to multiple indices
|
||||
array.findIndex((engineData) => engineData.name === name) === index
|
||||
);
|
||||
};
|
||||
|
|
|
@ -27,7 +27,7 @@ export function registerIndexRoutes({ router, log }: RouteDependencies) {
|
|||
{ path: '/internal/enterprise_search/search_indices', validate: false },
|
||||
elasticsearchErrorHandler(log, async (context, _, response) => {
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const indices = await fetchIndices(client, '*', false, true);
|
||||
const indices = await fetchIndices(client, '*', false, true, 'search-');
|
||||
|
||||
return response.ok({
|
||||
body: indices,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue