From b3aabe98aa6b6d80fc4a2776320fd65909de2e8a Mon Sep 17 00:00:00 2001 From: Josh Dover Date: Wed, 16 Sep 2020 14:24:11 -0600 Subject: [PATCH] Migrate /api/settings to KP (#77554) --- docs/developer/plugin-list.asciidoc | 4 + .../server/context/context_service.mock.ts | 4 +- src/core/server/mocks.ts | 1 + src/core/utils/context.mock.ts | 8 +- x-pack/legacy/plugins/xpack_main/index.js | 3 +- .../xpack_main/server/routes/api/v1/index.js | 1 - .../server/routes/api/v1/settings.js | 68 ------------ x-pack/plugins/monitoring/server/index.ts | 1 + .../collectors/get_settings_collector.ts | 14 ++- .../kibana_monitoring/collectors/index.ts | 2 + x-pack/plugins/xpack_legacy/README.md | 3 + x-pack/plugins/xpack_legacy/kibana.json | 10 ++ x-pack/plugins/xpack_legacy/server/index.ts | 11 ++ x-pack/plugins/xpack_legacy/server/plugin.ts | 48 ++++++++ .../server/routes/settings.test.ts | 105 ++++++++++++++++++ .../xpack_legacy/server/routes/settings.ts | 93 ++++++++++++++++ x-pack/test/api_integration/apis/index.js | 2 +- .../{xpack_main => xpack_legacy}/index.js | 2 +- .../settings/index.js | 0 .../settings/settings.js | 0 20 files changed, 298 insertions(+), 82 deletions(-) delete mode 100644 x-pack/legacy/plugins/xpack_main/server/routes/api/v1/settings.js create mode 100644 x-pack/plugins/xpack_legacy/README.md create mode 100644 x-pack/plugins/xpack_legacy/kibana.json create mode 100644 x-pack/plugins/xpack_legacy/server/index.ts create mode 100644 x-pack/plugins/xpack_legacy/server/plugin.ts create mode 100644 x-pack/plugins/xpack_legacy/server/routes/settings.test.ts create mode 100644 x-pack/plugins/xpack_legacy/server/routes/settings.ts rename x-pack/test/api_integration/apis/{xpack_main => xpack_legacy}/index.js (90%) rename x-pack/test/api_integration/apis/{xpack_main => xpack_legacy}/settings/index.js (100%) rename x-pack/test/api_integration/apis/{xpack_main => xpack_legacy}/settings/settings.js (100%) diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 7727cd322181..501a3698d07d 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -494,6 +494,10 @@ in their infrastructure. |This plugins adopts some conventions in addition to or in place of conventions in Kibana (at the time of the plugin's creation): +|{kib-repo}blob/{branch}/x-pack/plugins/xpack_legacy/README.md[xpackLegacy] +|Contains HTTP endpoints and UiSettings that are slated for removal. + + |=== include::{kibana-root}/src/plugins/dashboard/README.asciidoc[leveloffset=+1] diff --git a/src/core/server/context/context_service.mock.ts b/src/core/server/context/context_service.mock.ts index a8d895acad62..24e0d52100bb 100644 --- a/src/core/server/context/context_service.mock.ts +++ b/src/core/server/context/context_service.mock.ts @@ -21,9 +21,9 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { ContextService, ContextSetup } from './context_service'; import { contextMock } from '../../utils/context.mock'; -const createSetupContractMock = () => { +const createSetupContractMock = (mockContext = {}) => { const setupContract: jest.Mocked = { - createContextContainer: jest.fn().mockImplementation(() => contextMock.create()), + createContextContainer: jest.fn().mockImplementation(() => contextMock.create(mockContext)), }; return setupContract; }; diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 52dccb688088..2f995ff91529 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -52,6 +52,7 @@ export { typeRegistryMock as savedObjectsTypeRegistryMock } from './saved_object export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; export { metricsServiceMock } from './metrics/metrics_service.mock'; export { renderingMock } from './rendering/rendering_service.mock'; +export { contextServiceMock } from './context/context_service.mock'; export function pluginInitializerContextConfigMock(config: T) { const globalConfig: SharedGlobalConfig = { diff --git a/src/core/utils/context.mock.ts b/src/core/utils/context.mock.ts index de844f3f0f07..273d64ec8f82 100644 --- a/src/core/utils/context.mock.ts +++ b/src/core/utils/context.mock.ts @@ -21,15 +21,13 @@ import { IContextContainer } from './context'; export type ContextContainerMock = jest.Mocked>; -const createContextMock = () => { +const createContextMock = (mockContext = {}) => { const contextMock: ContextContainerMock = { registerContext: jest.fn(), - createHandler: jest.fn((id, handler) => (...args: any[]) => - Promise.resolve(handler({}, ...args)) - ), + createHandler: jest.fn(), }; contextMock.createHandler.mockImplementation((pluginId, handler) => (...args) => - handler({}, ...args) + handler(mockContext, ...args) ); return contextMock; }; diff --git a/x-pack/legacy/plugins/xpack_main/index.js b/x-pack/legacy/plugins/xpack_main/index.js index 854fba662471..493139403846 100644 --- a/x-pack/legacy/plugins/xpack_main/index.js +++ b/x-pack/legacy/plugins/xpack_main/index.js @@ -7,7 +7,7 @@ import { resolve } from 'path'; import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status'; import { setupXPackMain } from './server/lib/setup_xpack_main'; -import { xpackInfoRoute, settingsRoute } from './server/routes/api/v1'; +import { xpackInfoRoute } from './server/routes/api/v1'; export const xpackMain = (kibana) => { return new kibana.Plugin({ @@ -29,7 +29,6 @@ export const xpackMain = (kibana) => { // register routes xpackInfoRoute(server); - settingsRoute(server, this.kbnServer); }, }); }; diff --git a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/index.js b/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/index.js index c0e59b4ea4ab..80baf7bf1a64 100644 --- a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/index.js +++ b/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/index.js @@ -5,4 +5,3 @@ */ export { xpackInfoRoute } from './xpack_info'; -export { settingsRoute } from './settings'; diff --git a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/settings.js b/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/settings.js deleted file mode 100644 index 34fc4d97c132..000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/settings.js +++ /dev/null @@ -1,68 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { boomify } from 'boom'; -import { get } from 'lodash'; -import { KIBANA_SETTINGS_TYPE } from '../../../../../../../plugins/monitoring/common/constants'; - -const getClusterUuid = async (callCluster) => { - const { cluster_uuid: uuid } = await callCluster('info', { filterPath: 'cluster_uuid' }); - return uuid; -}; - -export function settingsRoute(server, kbnServer) { - server.route({ - path: '/api/settings', - method: 'GET', - async handler(req) { - const { server } = req; - const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); - const callCluster = (...args) => callWithRequest(req, ...args); // All queries from HTTP API must use authentication headers from the request - - try { - const { usageCollection } = server.newPlatform.setup.plugins; - const settingsCollector = usageCollection.getCollectorByType(KIBANA_SETTINGS_TYPE); - - let settings = await settingsCollector.fetch(callCluster); - if (!settings) { - settings = settingsCollector.getEmailValueStructure(null); - } - const uuid = await getClusterUuid(callCluster); - - const snapshotRegex = /-snapshot/i; - const config = server.config(); - const status = kbnServer.status.toJSON(); - const kibana = { - uuid: config.get('server.uuid'), - name: config.get('server.name'), - index: config.get('kibana.index'), - host: config.get('server.host'), - port: config.get('server.port'), - locale: config.get('i18n.locale'), - transport_address: `${config.get('server.host')}:${config.get('server.port')}`, - version: kbnServer.version.replace(snapshotRegex, ''), - snapshot: snapshotRegex.test(kbnServer.version), - status: get(status, 'overall.state'), - }; - - return { - cluster_uuid: uuid, - settings: { - ...settings, - kibana, - }, - }; - } catch (err) { - req.log(['error'], err); // FIXME doesn't seem to log anything useful if ES times out - if (err.isBoom) { - return err; - } else { - return boomify(err, { statusCode: err.statusCode, message: err.message }); - } - } - }, - }); -} diff --git a/x-pack/plugins/monitoring/server/index.ts b/x-pack/plugins/monitoring/server/index.ts index 60f04c535ebf..de679a2834d7 100644 --- a/x-pack/plugins/monitoring/server/index.ts +++ b/x-pack/plugins/monitoring/server/index.ts @@ -10,6 +10,7 @@ import { Plugin } from './plugin'; import { configSchema } from './config'; import { deprecations } from './deprecations'; +export { KibanaSettingsCollector } from './kibana_monitoring/collectors'; export { MonitoringConfig } from './config'; export const plugin = (initContext: PluginInitializerContext) => new Plugin(initContext); export const config: PluginConfigDescriptor> = { diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.ts index c66adfcabd67..a3ff4b952ce9 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.ts +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Collector } from '../../../../../../src/plugins/usage_collection/server'; + import { KIBANA_SETTINGS_TYPE } from '../../../common/constants'; import { MonitoringConfig } from '../../config'; @@ -38,11 +40,19 @@ export async function checkForEmailValue( } } +interface EmailSettingData { + xpack: { default_admin_email: string | null }; +} + +export interface KibanaSettingsCollector extends Collector { + getEmailValueStructure(email: string | null): EmailSettingData; +} + export function getSettingsCollector(usageCollection: any, config: MonitoringConfig) { return usageCollection.makeStatsCollector({ type: KIBANA_SETTINGS_TYPE, isReady: () => true, - async fetch() { + async fetch(this: KibanaSettingsCollector) { let kibanaSettingsData; const defaultAdminEmail = await checkForEmailValue(config); @@ -64,7 +74,7 @@ export function getSettingsCollector(usageCollection: any, config: MonitoringCon // returns undefined if there was no result return kibanaSettingsData; }, - getEmailValueStructure(email: string) { + getEmailValueStructure(email: string | null) { return { xpack: { default_admin_email: email, diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/index.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/index.ts index dcd35b0d323e..aa4853ab226f 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/index.ts +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/index.ts @@ -8,6 +8,8 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { getSettingsCollector } from './get_settings_collector'; import { MonitoringConfig } from '../../config'; +export { KibanaSettingsCollector } from './get_settings_collector'; + export function registerCollectors( usageCollection: UsageCollectionSetup, config: MonitoringConfig diff --git a/x-pack/plugins/xpack_legacy/README.md b/x-pack/plugins/xpack_legacy/README.md new file mode 100644 index 000000000000..be4382534795 --- /dev/null +++ b/x-pack/plugins/xpack_legacy/README.md @@ -0,0 +1,3 @@ +# Xpack Leagcy + +Contains HTTP endpoints and UiSettings that are slated for removal. diff --git a/x-pack/plugins/xpack_legacy/kibana.json b/x-pack/plugins/xpack_legacy/kibana.json new file mode 100644 index 000000000000..fc45b612d72c --- /dev/null +++ b/x-pack/plugins/xpack_legacy/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "xpackLegacy", + "version": "8.0.0", + "kibanaVersion": "kibana", + "server": true, + "ui": false, + "requiredPlugins": [ + "usageCollection" + ] +} diff --git a/x-pack/plugins/xpack_legacy/server/index.ts b/x-pack/plugins/xpack_legacy/server/index.ts new file mode 100644 index 000000000000..ecdee0692fc9 --- /dev/null +++ b/x-pack/plugins/xpack_legacy/server/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from '../../../../src/core/server'; +import { XpackLegacyPlugin } from './plugin'; + +export const plugin = (initializerContext: PluginInitializerContext) => + new XpackLegacyPlugin(initializerContext); diff --git a/x-pack/plugins/xpack_legacy/server/plugin.ts b/x-pack/plugins/xpack_legacy/server/plugin.ts new file mode 100644 index 000000000000..2905f8e4bf3d --- /dev/null +++ b/x-pack/plugins/xpack_legacy/server/plugin.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { first } from 'rxjs/operators'; + +import { + CoreStart, + CoreSetup, + Plugin, + PluginInitializerContext, +} from '../../../../src/core/server'; +import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server'; +import { registerSettingsRoute } from './routes/settings'; + +interface SetupPluginDeps { + usageCollection: UsageCollectionSetup; +} + +export class XpackLegacyPlugin implements Plugin { + constructor(private readonly initContext: PluginInitializerContext) {} + + public async setup(core: CoreSetup, { usageCollection }: SetupPluginDeps) { + const router = core.http.createRouter(); + const globalConfig = await this.initContext.config.legacy.globalConfig$ + .pipe(first()) + .toPromise(); + const serverInfo = core.http.getServerInfo(); + + registerSettingsRoute({ + router, + usageCollection, + overallStatus$: core.status.overall$, + config: { + kibanaIndex: globalConfig.kibana.index, + kibanaVersion: this.initContext.env.packageInfo.version, + uuid: this.initContext.env.instanceUuid, + server: serverInfo, + }, + }); + } + + public start(core: CoreStart) {} + + public stop() {} +} diff --git a/x-pack/plugins/xpack_legacy/server/routes/settings.test.ts b/x-pack/plugins/xpack_legacy/server/routes/settings.test.ts new file mode 100644 index 000000000000..02fa669cb05e --- /dev/null +++ b/x-pack/plugins/xpack_legacy/server/routes/settings.test.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { BehaviorSubject } from 'rxjs'; +import { UnwrapPromise } from '@kbn/utility-types'; +import supertest from 'supertest'; + +import { + LegacyAPICaller, + ServiceStatus, + ServiceStatusLevels, +} from '../../../../../src/core/server'; +import { contextServiceMock } from '../../../../../src/core/server/mocks'; +import { createHttpServer } from '../../../../../src/core/server/test_utils'; +import { registerSettingsRoute } from './settings'; + +type HttpService = ReturnType; +type HttpSetup = UnwrapPromise>; + +describe('/api/stats', () => { + let server: HttpService; + let httpSetup: HttpSetup; + let overallStatus$: BehaviorSubject; + let mockApiCaller: jest.Mocked; + + beforeEach(async () => { + mockApiCaller = jest.fn().mockResolvedValue({ cluster_uuid: 'yyy-yyyyy' }); + server = createHttpServer(); + httpSetup = await server.setup({ + context: contextServiceMock.createSetupContract({ + core: { + elasticsearch: { + legacy: { + client: { + callAsCurrentUser: mockApiCaller, + }, + }, + }, + }, + }), + }); + + overallStatus$ = new BehaviorSubject({ + level: ServiceStatusLevels.available, + summary: 'everything is working', + }); + + const usageCollection = { + getCollectorByType: jest.fn().mockReturnValue({ + fetch: jest + .fn() + .mockReturnValue({ xpack: { default_admin_email: 'kibana-machine@elastic.co' } }), + }), + } as any; + + const router = httpSetup.createRouter(''); + registerSettingsRoute({ + router, + overallStatus$, + usageCollection, + config: { + kibanaIndex: '.kibana-test', + kibanaVersion: '8.8.8-SNAPSHOT', + server: { + name: 'mykibana', + hostname: 'mykibana.com', + port: 1234, + }, + uuid: 'xxx-xxxxx', + }, + }); + + await server.start(); + }); + + afterEach(async () => { + await server.stop(); + }); + + it('successfully returns data', async () => { + const response = await supertest(httpSetup.server.listener).get('/api/settings').expect(200); + expect(response.body).toMatchObject({ + cluster_uuid: 'yyy-yyyyy', + settings: { + xpack: { + default_admin_email: 'kibana-machine@elastic.co', + }, + kibana: { + uuid: 'xxx-xxxxx', + name: 'mykibana', + index: '.kibana-test', + host: 'mykibana.com', + locale: 'en', + transport_address: `mykibana.com:1234`, + version: '8.8.8', + snapshot: true, + status: 'green', + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/xpack_legacy/server/routes/settings.ts b/x-pack/plugins/xpack_legacy/server/routes/settings.ts new file mode 100644 index 000000000000..2a0eb3d11584 --- /dev/null +++ b/x-pack/plugins/xpack_legacy/server/routes/settings.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; + +import { IRouter, ServiceStatus, ServiceStatusLevels } from '../../../../../src/core/server'; +import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/server'; +import { KIBANA_SETTINGS_TYPE } from '../../../monitoring/common/constants'; +import { KibanaSettingsCollector } from '../../../monitoring/server'; + +const SNAPSHOT_REGEX = /-snapshot/i; + +export function registerSettingsRoute({ + router, + usageCollection, + overallStatus$, + config, +}: { + router: IRouter; + usageCollection: UsageCollectionSetup; + overallStatus$: Observable; + config: { + kibanaIndex: string; + kibanaVersion: string; + uuid: string; + server: { + name: string; + hostname: string; + port: number; + }; + }; +}) { + router.get( + { + path: '/api/settings', + validate: false, + }, + async (context, req, res) => { + const { callAsCurrentUser } = context.core.elasticsearch.legacy.client; + + const settingsCollector = usageCollection.getCollectorByType(KIBANA_SETTINGS_TYPE) as + | KibanaSettingsCollector + | undefined; + if (!settingsCollector) { + return res.internalError(); + } + + const settings = + (await settingsCollector.fetch(callAsCurrentUser)) ?? + settingsCollector.getEmailValueStructure(null); + const { cluster_uuid: uuid } = await callAsCurrentUser('info', { + filterPath: 'cluster_uuid', + }); + + const overallStatus = await overallStatus$.pipe(first()).toPromise(); + + const kibana = { + uuid: config.uuid, + name: config.server.name, + index: config.kibanaIndex, + host: config.server.hostname, + port: config.server.port, + locale: i18n.getLocale(), + transport_address: `${config.server.hostname}:${config.server.port}`, + version: config.kibanaVersion.replace(SNAPSHOT_REGEX, ''), + snapshot: SNAPSHOT_REGEX.test(config.kibanaVersion), + status: ServiceStatusToLegacyState[overallStatus.level.toString()], + }; + + return res.ok({ + body: { + cluster_uuid: uuid, + settings: { + ...settings, + kibana, + }, + }, + }); + } + ); +} + +const ServiceStatusToLegacyState: Record = { + [ServiceStatusLevels.critical.toString()]: 'red', + [ServiceStatusLevels.unavailable.toString()]: 'red', + [ServiceStatusLevels.degraded.toString()]: 'yellow', + [ServiceStatusLevels.available.toString()]: 'green', +}; diff --git a/x-pack/test/api_integration/apis/index.js b/x-pack/test/api_integration/apis/index.js index 23532d131175..a1bcaa13cc52 100644 --- a/x-pack/test/api_integration/apis/index.js +++ b/x-pack/test/api_integration/apis/index.js @@ -12,7 +12,7 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./security')); loadTestFile(require.resolve('./spaces')); loadTestFile(require.resolve('./monitoring')); - loadTestFile(require.resolve('./xpack_main')); + loadTestFile(require.resolve('./xpack_legacy')); loadTestFile(require.resolve('./features')); loadTestFile(require.resolve('./telemetry')); loadTestFile(require.resolve('./logstash')); diff --git a/x-pack/test/api_integration/apis/xpack_main/index.js b/x-pack/test/api_integration/apis/xpack_legacy/index.js similarity index 90% rename from x-pack/test/api_integration/apis/xpack_main/index.js rename to x-pack/test/api_integration/apis/xpack_legacy/index.js index c011ef870df4..b712d565311b 100644 --- a/x-pack/test/api_integration/apis/xpack_main/index.js +++ b/x-pack/test/api_integration/apis/xpack_legacy/index.js @@ -5,7 +5,7 @@ */ export default function ({ loadTestFile }) { - describe('xpack_main', () => { + describe('xpack_legacy', () => { loadTestFile(require.resolve('./settings')); }); } diff --git a/x-pack/test/api_integration/apis/xpack_main/settings/index.js b/x-pack/test/api_integration/apis/xpack_legacy/settings/index.js similarity index 100% rename from x-pack/test/api_integration/apis/xpack_main/settings/index.js rename to x-pack/test/api_integration/apis/xpack_legacy/settings/index.js diff --git a/x-pack/test/api_integration/apis/xpack_main/settings/settings.js b/x-pack/test/api_integration/apis/xpack_legacy/settings/settings.js similarity index 100% rename from x-pack/test/api_integration/apis/xpack_main/settings/settings.js rename to x-pack/test/api_integration/apis/xpack_legacy/settings/settings.js