diff --git a/x-pack/plugins/reporting/common/constants.ts b/x-pack/plugins/reporting/common/constants.ts index 2a95557473fc0..54df24db49f29 100644 --- a/x-pack/plugins/reporting/common/constants.ts +++ b/x-pack/plugins/reporting/common/constants.ts @@ -89,6 +89,7 @@ export const API_BASE_URL = '/api/reporting'; // "Generation URL" from share men export const API_BASE_GENERATE = `${API_BASE_URL}/generate`; export const API_LIST_URL = `${API_BASE_URL}/jobs`; export const API_DIAGNOSE_URL = `${API_BASE_URL}/diagnose`; +export const API_MIGRATE_ILM_POLICY_URL = `${API_BASE_URL}/deprecations/migrate_ilm_policy`; // hacky endpoint: download CSV without queueing a report export const API_BASE_URL_V1 = '/api/reporting/v1'; // diff --git a/x-pack/plugins/reporting/server/deprecations.test.ts b/x-pack/plugins/reporting/server/deprecations.test.ts deleted file mode 100644 index cce4721b941a0..0000000000000 --- a/x-pack/plugins/reporting/server/deprecations.test.ts +++ /dev/null @@ -1,107 +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. - */ - -import { ReportingCore } from '.'; -import { registerDeprecations } from './deprecations'; -import { createMockConfigSchema, createMockReportingCore } from './test_helpers'; -import { coreMock, elasticsearchServiceMock } from 'src/core/server/mocks'; -import { GetDeprecationsContext, IScopedClusterClient } from 'kibana/server'; - -let reportingCore: ReportingCore; -let context: GetDeprecationsContext; -let esClient: jest.Mocked; - -beforeEach(async () => { - const mockReportingConfig = createMockConfigSchema({ roles: { enabled: false } }); - reportingCore = await createMockReportingCore(mockReportingConfig); - esClient = elasticsearchServiceMock.createScopedClusterClient(); - esClient.asCurrentUser.security.getUser = jest.fn().mockResolvedValue({ - body: { xyz: { username: 'normal_user', roles: ['data_analyst'] } }, - }); - context = ({ esClient } as unknown) as GetDeprecationsContext; -}); - -test('logs no deprecations when setup has no issues', async () => { - const { getDeprecations } = await registerDeprecations(reportingCore, coreMock.createSetup()); - expect(await getDeprecations(context)).toMatchInlineSnapshot(`Array []`); -}); - -test('logs a plain message when only a reporting_user role issue is found', async () => { - esClient.asCurrentUser.security.getUser = jest.fn().mockResolvedValue({ - body: { reportron: { username: 'reportron', roles: ['kibana_admin', 'reporting_user'] } }, - }); - - const { getDeprecations } = await registerDeprecations(reportingCore, coreMock.createSetup()); - expect(await getDeprecations(context)).toMatchInlineSnapshot(` - Array [ - Object { - "correctiveActions": Object { - "manualSteps": Array [ - "Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.", - "Assign the custom role(s) as desired, and remove the \\"reporting_user\\" role from the user(s).", - ], - }, - "documentationUrl": "https://www.elastic.co/guide/en/kibana/current/secure-reporting.html", - "level": "critical", - "message": "The deprecated \\"reporting_user\\" role has been found for 1 user(s): \\"reportron\\"", - }, - ] - `); -}); - -test('logs multiple entries when multiple reporting_user role issues are found', async () => { - esClient.asCurrentUser.security.getUser = jest.fn().mockResolvedValue({ - body: { - reportron: { username: 'reportron', roles: ['kibana_admin', 'reporting_user'] }, - supercooluser: { username: 'supercooluser', roles: ['kibana_admin', 'reporting_user'] }, - }, - }); - - const { getDeprecations } = await registerDeprecations(reportingCore, coreMock.createSetup()); - expect(await getDeprecations(context)).toMatchInlineSnapshot(` - Array [ - Object { - "correctiveActions": Object { - "manualSteps": Array [ - "Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.", - "Assign the custom role(s) as desired, and remove the \\"reporting_user\\" role from the user(s).", - ], - }, - "documentationUrl": "https://www.elastic.co/guide/en/kibana/current/secure-reporting.html", - "level": "critical", - "message": "The deprecated \\"reporting_user\\" role has been found for 2 user(s): \\"reportron\\", \\"supercooluser\\"", - }, - ] - `); -}); - -test('logs an expanded message when a config issue and a reporting_user role issue is found', async () => { - esClient.asCurrentUser.security.getUser = jest.fn().mockResolvedValue({ - body: { reportron: { username: 'reportron', roles: ['kibana_admin', 'reporting_user'] } }, - }); - - const mockReportingConfig = createMockConfigSchema({ roles: { enabled: true } }); - reportingCore = await createMockReportingCore(mockReportingConfig); - - const { getDeprecations } = await registerDeprecations(reportingCore, coreMock.createSetup()); - expect(await getDeprecations(context)).toMatchInlineSnapshot(` - Array [ - Object { - "correctiveActions": Object { - "manualSteps": Array [ - "Set \\"xpack.reporting.roles.enabled: false\\" in kibana.yml", - "Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.", - "Assign the custom role(s) as desired, and remove the \\"reporting_user\\" role from the user(s).", - ], - }, - "documentationUrl": "https://www.elastic.co/guide/en/kibana/current/secure-reporting.html", - "level": "critical", - "message": "The deprecated \\"reporting_user\\" role has been found for 1 user(s): \\"reportron\\"", - }, - ] - `); -}); diff --git a/x-pack/plugins/reporting/server/deprecations.ts b/x-pack/plugins/reporting/server/deprecations.ts deleted file mode 100644 index 61074fff012a2..0000000000000 --- a/x-pack/plugins/reporting/server/deprecations.ts +++ /dev/null @@ -1,52 +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. - */ - -import type { CoreSetup, DeprecationsDetails, RegisterDeprecationsConfig } from 'src/core/server'; -import { ReportingCore } from '.'; - -const deprecatedRole = 'reporting_user'; -const upgradableConfig = 'xpack.reporting.roles.enabled: false'; - -export async function registerDeprecations( - reporting: ReportingCore, - { deprecations: deprecationsService }: CoreSetup -) { - const deprecationsConfig: RegisterDeprecationsConfig = { - getDeprecations: async ({ esClient }) => { - const usingDeprecatedConfig = !reporting.getContract().usesUiCapabilities(); - const deprecations: DeprecationsDetails[] = []; - const { body: users } = await esClient.asCurrentUser.security.getUser(); - - const reportingUsers = Object.entries(users) - .filter(([username, user]) => user.roles.includes(deprecatedRole)) - .map(([, user]) => user.username); - const numReportingUsers = reportingUsers.length; - - if (numReportingUsers > 0) { - const usernames = reportingUsers.join('", "'); - deprecations.push({ - message: `The deprecated "${deprecatedRole}" role has been found for ${numReportingUsers} user(s): "${usernames}"`, - documentationUrl: 'https://www.elastic.co/guide/en/kibana/current/secure-reporting.html', - level: 'critical', - correctiveActions: { - manualSteps: [ - ...(usingDeprecatedConfig ? [`Set "${upgradableConfig}" in kibana.yml`] : []), - `Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.`, - `Assign the custom role(s) as desired, and remove the "${deprecatedRole}" role from the user(s).`, - ], - }, - }); - } - - return deprecations; - }, - }; - - deprecationsService.registerDeprecations(deprecationsConfig); - - return deprecationsConfig; -} diff --git a/x-pack/plugins/reporting/server/deprecations/index.ts b/x-pack/plugins/reporting/server/deprecations/index.ts new file mode 100644 index 0000000000000..9ecb3b7ab88ad --- /dev/null +++ b/x-pack/plugins/reporting/server/deprecations/index.ts @@ -0,0 +1,28 @@ +/* + * 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. + */ +import { CoreSetup } from 'src/core/server'; +import { ReportingCore } from '../core'; + +import { getDeprecationsInfo as getIlmPolicyDeprecationsInfo } from './migrate_existing_indices_ilm_policy'; +import { getDeprecationsInfo as getReportingRoleDeprecationsInfo } from './reporting_role'; + +export const registerDeprecations = ({ + core, + reportingCore, +}: { + core: CoreSetup; + reportingCore: ReportingCore; +}) => { + core.deprecations.registerDeprecations({ + getDeprecations: async (ctx) => { + return [ + ...(await getIlmPolicyDeprecationsInfo(ctx, { reportingCore })), + ...(await getReportingRoleDeprecationsInfo(ctx, { reportingCore })), + ]; + }, + }); +}; diff --git a/x-pack/plugins/reporting/server/deprecations/migrage_existing_indices_ilm_policy.test.ts b/x-pack/plugins/reporting/server/deprecations/migrage_existing_indices_ilm_policy.test.ts new file mode 100644 index 0000000000000..638373899943e --- /dev/null +++ b/x-pack/plugins/reporting/server/deprecations/migrage_existing_indices_ilm_policy.test.ts @@ -0,0 +1,98 @@ +/* + * 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. + */ + +import type { GetDeprecationsContext } from 'src/core/server'; +import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks'; + +import { ReportingCore } from '../core'; +import { createMockConfigSchema, createMockReportingCore } from '../test_helpers'; + +import { getDeprecationsInfo } from './migrate_existing_indices_ilm_policy'; + +type ScopedClusterClientMock = ReturnType< + typeof elasticsearchServiceMock.createScopedClusterClient +>; + +const { createApiResponse } = elasticsearchServiceMock; + +describe("Migrate existing indices' ILM policy deprecations", () => { + let esClient: ScopedClusterClientMock; + let deprecationsCtx: GetDeprecationsContext; + let reportingCore: ReportingCore; + + beforeEach(async () => { + esClient = elasticsearchServiceMock.createScopedClusterClient(); + deprecationsCtx = { esClient, savedObjectsClient: savedObjectsClientMock.create() }; + reportingCore = await createMockReportingCore(createMockConfigSchema()); + }); + + const createIndexSettings = (lifecycleName: string) => ({ + aliases: {}, + mappings: {}, + settings: { + index: { + lifecycle: { + name: lifecycleName, + }, + }, + }, + }); + + it('returns deprecation information when reporting indices are not using the reporting ILM policy', async () => { + esClient.asInternalUser.indices.getSettings.mockResolvedValueOnce( + createApiResponse({ + body: { + indexA: createIndexSettings('not-reporting-lifecycle'), + indexB: createIndexSettings('kibana-reporting'), + }, + }) + ); + + expect(await getDeprecationsInfo(deprecationsCtx, { reportingCore })).toMatchInlineSnapshot(` + Array [ + Object { + "correctiveActions": Object { + "api": Object { + "method": "PUT", + "path": "/api/reporting/deprecations/migrate_ilm_policy", + }, + "manualSteps": Array [ + "Send a request to Elasticsearch that configures indices matching \\".reporting-*\\" to be managed by the \\"kibana-reporting\\" Index Lifecycle Policy.", + ], + }, + "level": "warning", + "message": "All new reporting indices will be managed by a provisioned ILM policy: \\"kibana-reporting\\". To manage the lifecycle of reports edit the kibana-reporting policy. Please note, this action will target all indices prefixed with \\".reporting-*\\".", + }, + ] + `); + }); + + it('does not return deprecations when all reporting indices are managed by the provisioned ILM policy', async () => { + esClient.asInternalUser.indices.getSettings.mockResolvedValueOnce( + createApiResponse({ + body: { + indexA: createIndexSettings('kibana-reporting'), + indexB: createIndexSettings('kibana-reporting'), + }, + }) + ); + + expect(await getDeprecationsInfo(deprecationsCtx, { reportingCore })).toMatchInlineSnapshot( + `Array []` + ); + + esClient.asInternalUser.indices.getSettings.mockResolvedValueOnce( + createApiResponse({ + body: {}, + }) + ); + + expect(await getDeprecationsInfo(deprecationsCtx, { reportingCore })).toMatchInlineSnapshot( + `Array []` + ); + }); +}); diff --git a/x-pack/plugins/reporting/server/deprecations/migrate_existing_indices_ilm_policy.ts b/x-pack/plugins/reporting/server/deprecations/migrate_existing_indices_ilm_policy.ts new file mode 100644 index 0000000000000..4facfc051e8be --- /dev/null +++ b/x-pack/plugins/reporting/server/deprecations/migrate_existing_indices_ilm_policy.ts @@ -0,0 +1,55 @@ +/* + * 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. + */ + +import { i18n } from '@kbn/i18n'; +import { DeprecationsDetails, GetDeprecationsContext } from 'src/core/server'; +import { API_MIGRATE_ILM_POLICY_URL } from '../../common/constants'; +import { ReportingCore } from '../core'; + +interface ExtraDependencies { + reportingCore: ReportingCore; +} + +export const getDeprecationsInfo = async ( + { esClient }: GetDeprecationsContext, + { reportingCore }: ExtraDependencies +): Promise => { + const store = await reportingCore.getStore(); + const reportingIlmPolicy = store.getIlmPolicyName(); + const indexPattern = store.getReportingIndexPattern(); + + const { body: reportingIndicesSettings } = await esClient.asInternalUser.indices.getSettings({ + index: indexPattern, + }); + + const someIndicesNotManagedByReportingIlm = Object.values(reportingIndicesSettings).some( + (settings) => settings?.settings?.index?.lifecycle?.name !== reportingIlmPolicy + ); + + if (someIndicesNotManagedByReportingIlm) { + return [ + { + level: 'warning', + message: i18n.translate('xpack.reporting.deprecations.migrateIndexIlmPolicyActionMessage', { + defaultMessage: `All new reporting indices will be managed by a provisioned ILM policy: "{reportingIlmPolicy}". To manage the lifecycle of reports edit the {reportingIlmPolicy} policy. Please note, this action will target all indices prefixed with "{indexPattern}".`, + values: { + reportingIlmPolicy, + indexPattern, + }, + }), + correctiveActions: { + api: { + method: 'PUT', + path: API_MIGRATE_ILM_POLICY_URL, + }, + }, + }, + ]; + } + + return []; +}; diff --git a/x-pack/plugins/reporting/server/deprecations/reporting_role.test.ts b/x-pack/plugins/reporting/server/deprecations/reporting_role.test.ts new file mode 100644 index 0000000000000..f8d294c937781 --- /dev/null +++ b/x-pack/plugins/reporting/server/deprecations/reporting_role.test.ts @@ -0,0 +1,107 @@ +/* + * 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. + */ + +import type { GetDeprecationsContext, IScopedClusterClient } from 'kibana/server'; +import { elasticsearchServiceMock } from 'src/core/server/mocks'; + +import { ReportingCore } from '../'; +import { createMockConfigSchema, createMockReportingCore } from '../test_helpers'; + +import { getDeprecationsInfo } from './reporting_role'; + +describe('Reporting role deprecations', () => { + let reportingCore: ReportingCore; + let context: GetDeprecationsContext; + let esClient: jest.Mocked; + + beforeEach(async () => { + const mockReportingConfig = createMockConfigSchema({ roles: { enabled: false } }); + reportingCore = await createMockReportingCore(mockReportingConfig); + esClient = elasticsearchServiceMock.createScopedClusterClient(); + esClient.asCurrentUser.security.getUser = jest.fn().mockResolvedValue({ + body: { xyz: { username: 'normal_user', roles: ['data_analyst'] } }, + }); + context = ({ esClient } as unknown) as GetDeprecationsContext; + }); + + test('logs no deprecations when setup has no issues', async () => { + expect(await getDeprecationsInfo(context, { reportingCore })).toMatchInlineSnapshot(`Array []`); + }); + + test('logs a plain message when only a reporting_user role issue is found', async () => { + esClient.asCurrentUser.security.getUser = jest.fn().mockResolvedValue({ + body: { reportron: { username: 'reportron', roles: ['kibana_admin', 'reporting_user'] } }, + }); + + expect(await getDeprecationsInfo(context, { reportingCore })).toMatchInlineSnapshot(` + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.", + "Assign the custom role(s) as desired, and remove the \\"reporting_user\\" role from the user(s).", + ], + }, + "documentationUrl": "https://www.elastic.co/guide/en/kibana/current/secure-reporting.html", + "level": "critical", + "message": "The deprecated \\"reporting_user\\" role has been found for 1 user(s): \\"reportron\\"", + }, + ] + `); + }); + + test('logs multiple entries when multiple reporting_user role issues are found', async () => { + esClient.asCurrentUser.security.getUser = jest.fn().mockResolvedValue({ + body: { + reportron: { username: 'reportron', roles: ['kibana_admin', 'reporting_user'] }, + supercooluser: { username: 'supercooluser', roles: ['kibana_admin', 'reporting_user'] }, + }, + }); + + expect(await getDeprecationsInfo(context, { reportingCore })).toMatchInlineSnapshot(` + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.", + "Assign the custom role(s) as desired, and remove the \\"reporting_user\\" role from the user(s).", + ], + }, + "documentationUrl": "https://www.elastic.co/guide/en/kibana/current/secure-reporting.html", + "level": "critical", + "message": "The deprecated \\"reporting_user\\" role has been found for 2 user(s): \\"reportron\\", \\"supercooluser\\"", + }, + ] + `); + }); + + test('logs an expanded message when a config issue and a reporting_user role issue is found', async () => { + esClient.asCurrentUser.security.getUser = jest.fn().mockResolvedValue({ + body: { reportron: { username: 'reportron', roles: ['kibana_admin', 'reporting_user'] } }, + }); + + const mockReportingConfig = createMockConfigSchema({ roles: { enabled: true } }); + reportingCore = await createMockReportingCore(mockReportingConfig); + + expect(await getDeprecationsInfo(context, { reportingCore })).toMatchInlineSnapshot(` + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Set \\"xpack.reporting.roles.enabled: false\\" in kibana.yml", + "Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.", + "Assign the custom role(s) as desired, and remove the \\"reporting_user\\" role from the user(s).", + ], + }, + "documentationUrl": "https://www.elastic.co/guide/en/kibana/current/secure-reporting.html", + "level": "critical", + "message": "The deprecated \\"reporting_user\\" role has been found for 1 user(s): \\"reportron\\"", + }, + ] + `); + }); +}); diff --git a/x-pack/plugins/reporting/server/deprecations/reporting_role.ts b/x-pack/plugins/reporting/server/deprecations/reporting_role.ts new file mode 100644 index 0000000000000..3ac6f02d91db2 --- /dev/null +++ b/x-pack/plugins/reporting/server/deprecations/reporting_role.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DeprecationsDetails, GetDeprecationsContext } from 'src/core/server'; +import { ReportingCore } from '../'; + +const deprecatedRole = 'reporting_user'; +const upgradableConfig = 'xpack.reporting.roles.enabled: false'; + +interface ExtraDependencies { + reportingCore: ReportingCore; +} + +export const getDeprecationsInfo = async ( + { esClient }: GetDeprecationsContext, + { reportingCore }: ExtraDependencies +): Promise => { + const usingDeprecatedConfig = !reportingCore.getContract().usesUiCapabilities(); + const deprecations: DeprecationsDetails[] = []; + const { body: users } = await esClient.asCurrentUser.security.getUser(); + + const reportingUsers = Object.entries(users) + .filter(([username, user]) => user.roles.includes(deprecatedRole)) + .map(([, user]) => user.username); + const numReportingUsers = reportingUsers.length; + + if (numReportingUsers > 0) { + const usernames = reportingUsers.join('", "'); + deprecations.push({ + message: `The deprecated "${deprecatedRole}" role has been found for ${numReportingUsers} user(s): "${usernames}"`, + documentationUrl: 'https://www.elastic.co/guide/en/kibana/current/secure-reporting.html', + level: 'critical', + correctiveActions: { + manualSteps: [ + ...(usingDeprecatedConfig ? [`Set "${upgradableConfig}" in kibana.yml`] : []), + `Create one or more custom roles that provide Kibana application privileges to reporting features in **Management > Security > Roles**.`, + `Assign the custom role(s) as desired, and remove the "${deprecatedRole}" role from the user(s).`, + ], + }, + }); + } + + return deprecations; +}; diff --git a/x-pack/plugins/reporting/server/lib/store/store.ts b/x-pack/plugins/reporting/server/lib/store/store.ts index 9fb203fd5627a..37c4dc9d92c97 100644 --- a/x-pack/plugins/reporting/server/lib/store/store.ts +++ b/x-pack/plugins/reporting/server/lib/store/store.ts @@ -42,6 +42,7 @@ const checkReportIsEditable = (report: Report) => { * - interface for downloading the report */ export class ReportingStore { + private readonly ilmPolicyName = 'kibana-reporting'; // name of the ILM policy for managing indices created by this service. private readonly indexPrefix: string; // config setting of index prefix in system index name private readonly indexInterval: string; // config setting of index prefix: how often to poll for pending work private readonly queueTimeoutMins: number; // config setting of queue timeout, rounded up to nearest minute @@ -134,8 +135,6 @@ export class ReportingStore { return client.indices.refresh({ index }); } - private readonly ilmPolicyName = 'kibana-reporting'; - private async doesIlmPolicyExist(): Promise { const client = await this.getClient(); try { @@ -400,4 +399,12 @@ export class ReportingStore { return body.hits?.hits as ReportRecordTimeout[]; } + + public getIlmPolicyName(): string { + return this.ilmPolicyName; + } + + public getReportingIndexPattern(): string { + return `${this.indexPrefix}-*`; + } } diff --git a/x-pack/plugins/reporting/server/plugin.ts b/x-pack/plugins/reporting/server/plugin.ts index dc0ddf27a53b3..a5f722306f40f 100644 --- a/x-pack/plugins/reporting/server/plugin.ts +++ b/x-pack/plugins/reporting/server/plugin.ts @@ -65,9 +65,12 @@ export class ReportingPlugin }); registerUiSettings(core); - registerDeprecations(reportingCore, core); registerReportingUsageCollector(reportingCore, plugins); registerRoutes(reportingCore, this.logger); + registerDeprecations({ + core, + reportingCore, + }); // async background setup (async () => { diff --git a/x-pack/plugins/reporting/server/routes/deprecations.test.ts b/x-pack/plugins/reporting/server/routes/deprecations.test.ts new file mode 100644 index 0000000000000..509d12cdf9de1 --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/deprecations.test.ts @@ -0,0 +1,158 @@ +/* + * 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. + */ + +import supertest from 'supertest'; +import { errors } from '@elastic/elasticsearch'; +import { UnwrapPromise } from '@kbn/utility-types'; +import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; +import { of } from 'rxjs'; +import { ElasticsearchClient } from 'kibana/server'; +import { setupServer } from 'src/core/server/test_utils'; +import { elasticsearchServiceMock } from 'src/core/server/mocks'; + +import { API_MIGRATE_ILM_POLICY_URL } from '../../common/constants'; +import { ReportingCore } from '..'; +import { ReportingInternalSetup } from '../core'; +import { + createMockConfigSchema, + createMockPluginSetup, + createMockReportingCore, + createMockLevelLogger, +} from '../test_helpers'; +import { ReportingRequestHandlerContext } from '../types'; + +import { registerDeprecationsRoutes } from './deprecations'; + +type SetupServerReturn = UnwrapPromise>; +type MockLogger = ReturnType; + +const { createApiResponse } = elasticsearchServiceMock; + +describe(`PUT ${API_MIGRATE_ILM_POLICY_URL}`, () => { + const reportingSymbol = Symbol('reporting'); + let server: SetupServerReturn['server']; + let handlerContext: SetupServerReturn['handlerContext']; + let httpSetup: SetupServerReturn['httpSetup']; + let core: ReportingCore; + let mockSetupDeps: ReportingInternalSetup; + let logger: MockLogger; + let mockEsClient: DeeplyMockedKeys; + + beforeEach(async () => { + logger = createMockLevelLogger(); + ({ server, httpSetup, handlerContext } = await setupServer(reportingSymbol)); + + httpSetup.registerRouteHandlerContext( + reportingSymbol, + 'reporting', + () => ({ usesUiCapabilities: jest.fn() }) + ); + mockSetupDeps = createMockPluginSetup({ + security: { + license: { + isEnabled: () => true, + }, + authc: { + getCurrentUser: () => ({ + id: '123', + roles: ['superuser'], + username: 'Tom Riddle', + }), + }, + }, + router: httpSetup.createRouter(''), + licensing: { + license$: of({ + isActive: true, + isAvailable: true, + type: 'gold', + }), + }, + }); + + core = await createMockReportingCore( + createMockConfigSchema({ roles: { enabled: false } }), + mockSetupDeps + ); + + mockEsClient = handlerContext.elasticsearch.client.asCurrentUser; + }); + + afterEach(async () => { + await server.stop(); + }); + + it('sends the expected request to Elasticsearch', async () => { + mockEsClient.indices.putSettings.mockResolvedValueOnce(createApiResponse()); + registerDeprecationsRoutes(core, logger); + + await server.start(); + + await supertest(httpSetup.server.listener) + .put(API_MIGRATE_ILM_POLICY_URL) + .expect(200) + .then((response) => { + expect(response.body).toMatchInlineSnapshot(`Object {}`); // empty body + }); + + expect(mockEsClient.indices.putSettings).toHaveBeenCalledTimes(1); + expect(mockEsClient.indices.putSettings.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "body": Object { + "index": Object { + "lifecycle": Object { + "name": "kibana-reporting", + }, + }, + }, + "index": ".reporting-*", + } + `); + }); + + it('returns the ES error in case something goes wrong', async () => { + mockEsClient.indices.putSettings.mockRejectedValueOnce( + new errors.ResponseError({ + body: { message: 'something is wrong' }, + headers: {}, + meta: {} as any, + statusCode: 503, + warnings: [], + }) + ); + registerDeprecationsRoutes(core, logger); + + await server.start(); + + await supertest(httpSetup.server.listener) + .put(API_MIGRATE_ILM_POLICY_URL) + .expect(503) + .then((response) => { + expect(response.body).toMatchInlineSnapshot(` + Object { + "error": "Service Unavailable", + "message": "Response Error", + "statusCode": 503, + } + `); + }); + + expect(mockEsClient.indices.putSettings).toHaveBeenCalledTimes(1); + expect(mockEsClient.indices.putSettings.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "body": Object { + "index": Object { + "lifecycle": Object { + "name": "kibana-reporting", + }, + }, + }, + "index": ".reporting-*", + } + `); + }); +}); diff --git a/x-pack/plugins/reporting/server/routes/deprecations.ts b/x-pack/plugins/reporting/server/routes/deprecations.ts new file mode 100644 index 0000000000000..7c5d04908d33d --- /dev/null +++ b/x-pack/plugins/reporting/server/routes/deprecations.ts @@ -0,0 +1,55 @@ +/* + * 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. + */ +import { errors } from '@elastic/elasticsearch'; +import { API_MIGRATE_ILM_POLICY_URL } from '../../common/constants'; +import { ReportingCore } from '../core'; +import { LevelLogger as Logger } from '../lib'; + +export const registerDeprecationsRoutes = (reporting: ReportingCore, logger: Logger) => { + const { router } = reporting.getPluginSetupDeps(); + + router.put( + { path: API_MIGRATE_ILM_POLICY_URL, validate: false }, + async ({ core: { elasticsearch } }, req, res) => { + const store = await reporting.getStore(); + const { + client: { asCurrentUser: client }, + } = elasticsearch; + + const indexPattern = store.getReportingIndexPattern(); + const reportingIlmPolicy = store.getIlmPolicyName(); + + try { + await client.indices.putSettings({ + index: indexPattern, + body: { + index: { + lifecycle: { + name: reportingIlmPolicy, + }, + }, + }, + }); + return res.ok(); + } catch (err) { + logger.error(err); + + if (err instanceof errors.ResponseError) { + return res.customError({ + statusCode: err.statusCode ?? 500, + body: { + message: err.message, + name: err.name, + }, + }); + } + + throw err; + } + } + ); +}; diff --git a/x-pack/plugins/reporting/server/routes/index.ts b/x-pack/plugins/reporting/server/routes/index.ts index e061bd4f7d66c..a462da3849083 100644 --- a/x-pack/plugins/reporting/server/routes/index.ts +++ b/x-pack/plugins/reporting/server/routes/index.ts @@ -6,15 +6,17 @@ */ import { LevelLogger as Logger } from '../lib'; +import { registerDeprecationsRoutes } from './deprecations'; +import { registerDiagnosticRoutes } from './diagnostic'; import { registerJobGenerationRoutes } from './generation'; import { registerJobInfoRoutes } from './jobs'; import { ReportingCore } from '../core'; -import { registerDiagnosticRoutes } from './diagnostic'; export function registerRoutes(reporting: ReportingCore, logger: Logger) { + registerDeprecationsRoutes(reporting, logger); + registerDiagnosticRoutes(reporting, logger); registerJobGenerationRoutes(reporting, logger); registerJobInfoRoutes(reporting); - registerDiagnosticRoutes(reporting, logger); } export interface ReportingRequestPre {