From 732ccf2498c019947234debb611f3c55ed5923c1 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 2 Dec 2019 21:31:08 +0100 Subject: [PATCH 01/18] move and migrate uuid code to new platform --- src/core/server/uuid/manage_uuid.test.ts | 132 ++++++++++++++++++ src/core/server/uuid/manage_uuid.ts | 77 ++++++++++ src/legacy/core_plugins/kibana/index.js | 3 - .../server/lib/__tests__/manage_uuid.js | 102 -------------- .../kibana/server/lib/manage_uuid.js | 97 ------------- 5 files changed, 209 insertions(+), 202 deletions(-) create mode 100644 src/core/server/uuid/manage_uuid.test.ts create mode 100644 src/core/server/uuid/manage_uuid.ts delete mode 100644 src/legacy/core_plugins/kibana/server/lib/__tests__/manage_uuid.js delete mode 100644 src/legacy/core_plugins/kibana/server/lib/manage_uuid.js diff --git a/src/core/server/uuid/manage_uuid.test.ts b/src/core/server/uuid/manage_uuid.test.ts new file mode 100644 index 0000000000000..f6a22c516d2f6 --- /dev/null +++ b/src/core/server/uuid/manage_uuid.test.ts @@ -0,0 +1,132 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { readFile, writeFile } from 'fs'; +import { join } from 'path'; +import { manageInstanceUUID } from './manage_uuid'; +import { configServiceMock } from '../config/config_service.mock'; + +jest.mock('uuid', () => ({ + v4: () => 'NEW_UUID', +})); + +jest.mock('fs', () => ({ + readFile: jest.fn((path, callback) => callback(null, Buffer.from(''))), + writeFile: jest.fn((path, value, options, callback) => callback(null, null)), +})); + +const DEFAULT_FILE_UUID = 'FILE_UUID'; +const DEFAULT_CONFIG_UUID = 'CONFIG_UUID'; +const fileNotFoundError = { code: 'ENOENT' }; + +const mockReadFile = ({ + uuid = DEFAULT_FILE_UUID, + error = null, +}: Partial<{ + uuid: string; + error: any; +}>) => { + ((readFile as unknown) as jest.Mock).mockImplementation((path, callback) => { + if (error) { + callback(error, null); + } else { + callback(null, Buffer.from(uuid)); + } + }); +}; + +const getConfigService = (serverUUID: string | undefined) => { + return configServiceMock.create({ + getConfig$: { + path: { + data: 'data-folder', + }, + server: { + uuid: serverUUID, + }, + }, + }); +}; + +describe('manageInstanceUUID', () => { + let configService: ReturnType; + + beforeEach(() => { + jest.clearAllMocks(); + mockReadFile({ uuid: DEFAULT_FILE_UUID }); + configService = getConfigService(DEFAULT_CONFIG_UUID); + }); + + describe('when file is present and config property is set', () => { + it('writes to file and returns the config uuid if they mismatch', async () => { + const uuid = await manageInstanceUUID(configService); + expect(uuid).toEqual(DEFAULT_CONFIG_UUID); + expect(writeFile).toHaveBeenCalledWith( + join('data-folder', 'uuid'), + DEFAULT_CONFIG_UUID, + expect.any(Object), + expect.any(Function) + ); + }); + it('does not write to file if they match', async () => { + mockReadFile({ uuid: DEFAULT_CONFIG_UUID }); + const uuid = await manageInstanceUUID(configService); + expect(uuid).toEqual(DEFAULT_CONFIG_UUID); + expect(writeFile).not.toHaveBeenCalled(); + }); + }); + + describe('when file is not present and config property is set', () => { + it('writes the uuid to file and returns the config uuid', async () => { + mockReadFile({ error: fileNotFoundError }); + const uuid = await manageInstanceUUID(configService); + expect(uuid).toEqual(DEFAULT_CONFIG_UUID); + expect(writeFile).toHaveBeenCalledWith( + join('data-folder', 'uuid'), + DEFAULT_CONFIG_UUID, + expect.any(Object), + expect.any(Function) + ); + }); + }); + + describe('when file is present and config property is not set', () => { + it('does not write to file and returns the file uuid', async () => { + configService = getConfigService(undefined); + const uuid = await manageInstanceUUID(configService); + expect(uuid).toEqual(DEFAULT_FILE_UUID); + expect(writeFile).not.toHaveBeenCalled(); + }); + }); + + describe('when file is not present and config property is not set', () => { + it('generates a new uuid and write it to file', async () => { + configService = getConfigService(undefined); + mockReadFile({ error: fileNotFoundError }); + const uuid = await manageInstanceUUID(configService); + expect(uuid).toEqual('NEW_UUID'); + expect(writeFile).toHaveBeenCalledWith( + join('data-folder', 'uuid'), + 'NEW_UUID', + expect.any(Object), + expect.any(Function) + ); + }); + }); +}); diff --git a/src/core/server/uuid/manage_uuid.ts b/src/core/server/uuid/manage_uuid.ts new file mode 100644 index 0000000000000..30ba856be8d99 --- /dev/null +++ b/src/core/server/uuid/manage_uuid.ts @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import uuid from 'uuid'; +import { promisify } from 'util'; +import * as fs from 'fs'; +import { join } from 'path'; +import { take } from 'rxjs/operators'; +import { IConfigService } from '../config'; + +const FILE_ENCODING = 'utf8'; +const FILE_NAME = 'uuid'; + +const readFile = promisify(fs.readFile); +const writeFile = promisify(fs.writeFile); + +export async function manageInstanceUUID(configService: IConfigService): Promise { + const config = await configService + .getConfig$() + .pipe(take(1)) + .toPromise(); + const uuidFilePath = join(config.get('path.data'), FILE_NAME); + + const uuidFromFile = await readUUIDFromFile(uuidFilePath); + const uuidFromConfig = config.get('server.uuid'); + + if (uuidFromConfig) { + if (uuidFromConfig === uuidFromFile) { + // uuid matches, nothing to do + return uuidFromConfig; + } else { + // uuid in file don't match, or file was not present, we need to write it. + await writeUUIDToFile(uuidFilePath, uuidFromConfig); + return uuidFromConfig; + } + } + if (uuidFromFile === undefined) { + // no uuid either in config or file, we need to generate and write it. + const newUUID = uuid.v4(); + await writeUUIDToFile(uuidFilePath, newUUID); + return newUUID; + } + return uuidFromFile; +} + +async function readUUIDFromFile(filepath: string): Promise { + try { + const content = await readFile(filepath); + return content.toString(FILE_ENCODING); + } catch (e) { + if (e.code === 'ENOENT') { + // non-existent uuid file is ok, we will create it. + return undefined; + } + throw e; + } +} + +async function writeUUIDToFile(filepath: string, uuidValue: string) { + return await writeFile(filepath, uuidValue, { encoding: FILE_ENCODING }); +} diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 33e820a5300c6..70e5e0b20bff9 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -22,7 +22,6 @@ import { resolve } from 'path'; import { promisify } from 'util'; import { migrations } from './migrations'; -import manageUuid from './server/lib/manage_uuid'; import { importApi } from './server/routes/api/import'; import { exportApi } from './server/routes/api/export'; import { homeApi } from './server/routes/api/home'; @@ -323,8 +322,6 @@ export default function (kibana) { init: async function (server) { const { usageCollection } = server.newPlatform.setup.plugins; - // uuid - await manageUuid(server); // routes scriptsApi(server); importApi(server); diff --git a/src/legacy/core_plugins/kibana/server/lib/__tests__/manage_uuid.js b/src/legacy/core_plugins/kibana/server/lib/__tests__/manage_uuid.js deleted file mode 100644 index fe870c2b04ff8..0000000000000 --- a/src/legacy/core_plugins/kibana/server/lib/__tests__/manage_uuid.js +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import { createTestServers } from '../../../../../../test_utils/kbn_server'; -import manageUuid from '../manage_uuid'; - -describe('legacy/core_plugins/kibana/server/lib', function () { - describe('manage_uuid', function () { - const testUuid = 'c4add484-0cba-4e05-86fe-4baa112d9e53'; - let kbn; - let kbnServer; - let esServer; - let config; - let servers; - - before(async function () { - servers = createTestServers({ - adjustTimeout: (t) => { - this.timeout(t); - }, - }); - esServer = await servers.startES(); - - kbn = await servers.startKibana(); - kbnServer = kbn.kbnServer; - }); - - // Clear uuid stuff from previous test runs - beforeEach(function () { - kbnServer.server.log = sinon.stub(); - config = kbnServer.server.config(); - }); - - after(() => { - esServer.stop(); - kbn.stop(); - }); - - it('ensure config uuid is validated as a guid', async function () { - config.set('server.uuid', testUuid); - expect(config.get('server.uuid')).to.be(testUuid); - - expect(() => { - config.set('server.uuid', 'foouid'); - }).to.throwException((e) => { - expect(e.name).to.be('ValidationError'); - }); - }); - - it('finds the previously set uuid with config match', async function () { - const msg = `Kibana instance UUID: ${testUuid}`; - config.set('server.uuid', testUuid); - - await manageUuid(kbnServer.server); - await manageUuid(kbnServer.server); - - expect(kbnServer.server.log.lastCall.args[1]).to.be.eql(msg); - }); - - it('updates the previously set uuid with config value', async function () { - config.set('server.uuid', testUuid); - - await manageUuid(kbnServer.server); - - const newUuid = '5b2de169-2785-441b-ae8c-186a1936b17d'; - const msg = `Updating Kibana instance UUID to: ${newUuid} (was: ${testUuid})`; - - config.set('server.uuid', newUuid); - await manageUuid(kbnServer.server); - - expect(kbnServer.server.log.lastCall.args[1]).to.be(msg); - }); - - it('resumes the uuid stored in data and sets it to the config', async function () { - const partialMsg = 'Resuming persistent Kibana instance UUID'; - config.set('server.uuid'); // set to undefined - - await manageUuid(kbnServer.server); - - expect(config.get('server.uuid')).to.be.ok(); // not undefined any more - expect(kbnServer.server.log.lastCall.args[1]).to.match(new RegExp(`^${partialMsg}`)); - }); - }); -}); diff --git a/src/legacy/core_plugins/kibana/server/lib/manage_uuid.js b/src/legacy/core_plugins/kibana/server/lib/manage_uuid.js deleted file mode 100644 index d367682a2646d..0000000000000 --- a/src/legacy/core_plugins/kibana/server/lib/manage_uuid.js +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import uuid from 'uuid'; -import Bluebird from 'bluebird'; -import { join as pathJoin } from 'path'; -import { readFile as readFileCallback, writeFile as writeFileCallback } from 'fs'; - -const FILE_ENCODING = 'utf8'; - -export default async function manageUuid(server) { - const config = server.config(); - const fileName = 'uuid'; - const uuidFile = pathJoin(config.get('path.data'), fileName); - - async function detectUuid() { - const readFile = Bluebird.promisify(readFileCallback); - try { - const result = await readFile(uuidFile); - return result.toString(FILE_ENCODING); - } catch (err) { - if (err.code === 'ENOENT') { - // non-existent uuid file is ok - return false; - } - server.log(['error', 'read-uuid'], err); - // Note: this will most likely be logged as an Unhandled Rejection - throw err; - } - } - - async function writeUuid(uuid) { - const writeFile = Bluebird.promisify(writeFileCallback); - try { - return await writeFile(uuidFile, uuid, { encoding: FILE_ENCODING }); - } catch (err) { - server.log(['error', 'write-uuid'], err); - // Note: this will most likely be logged as an Unhandled Rejection - throw err; - } - } - - // detect if uuid exists already from before a restart - const logToServer = (msg) => server.log(['server', 'uuid', fileName], msg); - const dataFileUuid = await detectUuid(); - let serverConfigUuid = config.get('server.uuid'); // check if already set in config - - if (dataFileUuid) { - // data uuid found - if (serverConfigUuid === dataFileUuid) { - // config uuid exists, data uuid exists and matches - logToServer(`Kibana instance UUID: ${dataFileUuid}`); - return; - } - - if (!serverConfigUuid) { - // config uuid missing, data uuid exists - serverConfigUuid = dataFileUuid; - logToServer(`Resuming persistent Kibana instance UUID: ${serverConfigUuid}`); - config.set('server.uuid', serverConfigUuid); - return; - } - - if (serverConfigUuid !== dataFileUuid) { - // config uuid exists, data uuid exists but mismatches - logToServer(`Updating Kibana instance UUID to: ${serverConfigUuid} (was: ${dataFileUuid})`); - return writeUuid(serverConfigUuid); - } - } - - // data uuid missing - - if (!serverConfigUuid) { - // config uuid missing - serverConfigUuid = uuid.v4(); - config.set('server.uuid', serverConfigUuid); - } - - logToServer(`Setting new Kibana instance UUID: ${serverConfigUuid}`); - return writeUuid(serverConfigUuid); -} From bb7c5f00e51ee066a9edfc54b65aef3a0770921c Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 3 Dec 2019 10:02:28 +0100 Subject: [PATCH 02/18] create and wire uuid service --- src/core/server/index.ts | 4 ++ src/core/server/internal_types.ts | 2 + src/core/server/legacy/legacy_service.test.ts | 2 + src/core/server/legacy/legacy_service.ts | 3 + src/core/server/mocks.ts | 3 + src/core/server/plugins/plugin_context.ts | 3 + src/core/server/server.test.mocks.ts | 6 ++ src/core/server/server.ts | 6 ++ src/core/server/uuid/index.ts | 20 ++++++ src/core/server/uuid/manage_uuid.test.ts | 12 ++-- src/core/server/uuid/manage_uuid.ts | 2 +- src/core/server/uuid/uuid_service.mock.ts | 41 ++++++++++++ src/core/server/uuid/uuid_service.test.ts | 65 +++++++++++++++++++ src/core/server/uuid/uuid_service.ts | 53 +++++++++++++++ 14 files changed, 215 insertions(+), 7 deletions(-) create mode 100644 src/core/server/uuid/index.ts create mode 100644 src/core/server/uuid/uuid_service.mock.ts create mode 100644 src/core/server/uuid/uuid_service.test.ts create mode 100644 src/core/server/uuid/uuid_service.ts diff --git a/src/core/server/index.ts b/src/core/server/index.ts index efff85142c3e4..ad9b63c30b1aa 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -47,6 +47,7 @@ import { IUiSettingsClient, UiSettingsServiceSetup } from './ui_settings'; import { SavedObjectsClientContract } from './saved_objects/types'; import { SavedObjectsServiceSetup, SavedObjectsServiceStart } from './saved_objects'; import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; +import { UuidServiceSetup } from './uuid'; export { bootstrap } from './bootstrap'; export { Capabilities, CapabilitiesProvider, CapabilitiesSwitcher } from './capabilities'; @@ -254,6 +255,8 @@ export interface CoreSetup { savedObjects: SavedObjectsServiceSetup; /** {@link UiSettingsServiceSetup} */ uiSettings: UiSettingsServiceSetup; + /** {@link UuidServiceSetup} */ + uuid: UuidServiceSetup; } /** @@ -275,4 +278,5 @@ export { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId, + UuidServiceSetup, }; diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts index 07fd77f83d774..06cf848bff25a 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -26,6 +26,7 @@ import { InternalSavedObjectsServiceSetup, } from './saved_objects'; import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; +import { UuidServiceSetup } from './uuid'; /** @internal */ export interface InternalCoreSetup { @@ -35,6 +36,7 @@ export interface InternalCoreSetup { elasticsearch: InternalElasticsearchServiceSetup; uiSettings: InternalUiSettingsServiceSetup; savedObjects: InternalSavedObjectsServiceSetup; + uuid: UuidServiceSetup; } /** diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index 73b7ced60ee49..fa2498a2e1d95 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -47,6 +47,7 @@ import { httpServiceMock } from '../http/http_service.mock'; import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock'; import { capabilitiesServiceMock } from '../capabilities/capabilities_service.mock'; +import { uuidServiceMock } from '../uuid/uuid_service.mock'; const MockKbnServer: jest.Mock = KbnServer as any; @@ -89,6 +90,7 @@ beforeEach(() => { browserConfigs: new Map(), }, }, + uuid: uuidServiceMock.createSetupContract(), }, plugins: { 'plugin-id': 'plugin-value' }, }; diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 5d111884144c1..10ca89313b7b7 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -282,6 +282,9 @@ export class LegacyService implements CoreService { uiSettings: { register: setupDeps.core.uiSettings.register, }, + uuid: { + getInstanceUuid: setupDeps.core.uuid.getInstanceUuid, + }, }; const coreStart: CoreStart = { capabilities: startDeps.core.capabilities, diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 8f864dda6b9f3..9d8aa60e8176e 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -37,6 +37,7 @@ export { httpServiceMock } from './http/http_service.mock'; export { loggingServiceMock } from './logging/logging_service.mock'; export { savedObjectsClientMock } from './saved_objects/service/saved_objects_client.mock'; export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; +import { uuidServiceMock } from './uuid/uuid_service.mock'; export function pluginInitializerContextConfigMock(config: T) { const globalConfig: SharedGlobalConfig = { @@ -107,6 +108,7 @@ function createCoreSetupMock() { http: httpMock, savedObjects: savedObjectsServiceMock.createSetupContract(), uiSettings: uiSettingsMock, + uuid: uuidServiceMock.createSetupContract(), }; return mock; @@ -129,6 +131,7 @@ function createInternalCoreSetupMock() { http: httpServiceMock.createSetupContract(), uiSettings: uiSettingsServiceMock.createSetupContract(), savedObjects: savedObjectsServiceMock.createSetupContract(), + uuid: uuidServiceMock.createSetupContract(), }; return setupDeps; } diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index dfd1052bbec75..4ea6221e75da9 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -171,6 +171,9 @@ export function createPluginSetupContext( uiSettings: { register: deps.uiSettings.register, }, + uuid: { + getInstanceUuid: deps.uuid.getInstanceUuid, + }, }; } diff --git a/src/core/server/server.test.mocks.ts b/src/core/server/server.test.mocks.ts index 7820a14fec8f3..59925f46543e7 100644 --- a/src/core/server/server.test.mocks.ts +++ b/src/core/server/server.test.mocks.ts @@ -69,3 +69,9 @@ export const mockEnsureValidConfiguration = jest.fn(); jest.doMock('./legacy/config/ensure_valid_configuration', () => ({ ensureValidConfiguration: mockEnsureValidConfiguration, })); + +import { uuidServiceMock } from './uuid/uuid_service.mock'; +export const mockUuidService = uuidServiceMock.create(); +jest.doMock('./uuid/uuid_service', () => ({ + UuidService: jest.fn(() => mockUuidService), +})); diff --git a/src/core/server/server.ts b/src/core/server/server.ts index e7166f30caa34..f6ebbe1a2c9ed 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -42,6 +42,7 @@ import { ContextService } from './context'; import { RequestHandlerContext } from '.'; import { InternalCoreSetup } from './internal_types'; import { CapabilitiesService } from './capabilities'; +import { UuidService } from './uuid'; const coreId = Symbol('core'); @@ -56,6 +57,7 @@ export class Server { private readonly plugins: PluginsService; private readonly savedObjects: SavedObjectsService; private readonly uiSettings: UiSettingsService; + private readonly uuid: UuidService; constructor( public readonly config$: Observable, @@ -74,6 +76,7 @@ export class Server { this.savedObjects = new SavedObjectsService(core); this.uiSettings = new UiSettingsService(core); this.capabilities = new CapabilitiesService(core); + this.uuid = new UuidService(core); } public async setup() { @@ -97,6 +100,8 @@ export class Server { ]), }); + const uuidSetup = await this.uuid.setup(); + const httpSetup = await this.http.setup({ context: contextServiceSetup, }); @@ -125,6 +130,7 @@ export class Server { http: httpSetup, uiSettings: uiSettingsSetup, savedObjects: savedObjectsSetup, + uuid: uuidSetup, }; const pluginsSetup = await this.plugins.setup(coreSetup); diff --git a/src/core/server/uuid/index.ts b/src/core/server/uuid/index.ts new file mode 100644 index 0000000000000..ad57041a124c9 --- /dev/null +++ b/src/core/server/uuid/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { UuidService, UuidServiceSetup } from './uuid_service'; diff --git a/src/core/server/uuid/manage_uuid.test.ts b/src/core/server/uuid/manage_uuid.test.ts index f6a22c516d2f6..064595f5223ea 100644 --- a/src/core/server/uuid/manage_uuid.test.ts +++ b/src/core/server/uuid/manage_uuid.test.ts @@ -19,7 +19,7 @@ import { readFile, writeFile } from 'fs'; import { join } from 'path'; -import { manageInstanceUUID } from './manage_uuid'; +import { manageInstanceUuid } from './manage_uuid'; import { configServiceMock } from '../config/config_service.mock'; jest.mock('uuid', () => ({ @@ -75,7 +75,7 @@ describe('manageInstanceUUID', () => { describe('when file is present and config property is set', () => { it('writes to file and returns the config uuid if they mismatch', async () => { - const uuid = await manageInstanceUUID(configService); + const uuid = await manageInstanceUuid(configService); expect(uuid).toEqual(DEFAULT_CONFIG_UUID); expect(writeFile).toHaveBeenCalledWith( join('data-folder', 'uuid'), @@ -86,7 +86,7 @@ describe('manageInstanceUUID', () => { }); it('does not write to file if they match', async () => { mockReadFile({ uuid: DEFAULT_CONFIG_UUID }); - const uuid = await manageInstanceUUID(configService); + const uuid = await manageInstanceUuid(configService); expect(uuid).toEqual(DEFAULT_CONFIG_UUID); expect(writeFile).not.toHaveBeenCalled(); }); @@ -95,7 +95,7 @@ describe('manageInstanceUUID', () => { describe('when file is not present and config property is set', () => { it('writes the uuid to file and returns the config uuid', async () => { mockReadFile({ error: fileNotFoundError }); - const uuid = await manageInstanceUUID(configService); + const uuid = await manageInstanceUuid(configService); expect(uuid).toEqual(DEFAULT_CONFIG_UUID); expect(writeFile).toHaveBeenCalledWith( join('data-folder', 'uuid'), @@ -109,7 +109,7 @@ describe('manageInstanceUUID', () => { describe('when file is present and config property is not set', () => { it('does not write to file and returns the file uuid', async () => { configService = getConfigService(undefined); - const uuid = await manageInstanceUUID(configService); + const uuid = await manageInstanceUuid(configService); expect(uuid).toEqual(DEFAULT_FILE_UUID); expect(writeFile).not.toHaveBeenCalled(); }); @@ -119,7 +119,7 @@ describe('manageInstanceUUID', () => { it('generates a new uuid and write it to file', async () => { configService = getConfigService(undefined); mockReadFile({ error: fileNotFoundError }); - const uuid = await manageInstanceUUID(configService); + const uuid = await manageInstanceUuid(configService); expect(uuid).toEqual('NEW_UUID'); expect(writeFile).toHaveBeenCalledWith( join('data-folder', 'uuid'), diff --git a/src/core/server/uuid/manage_uuid.ts b/src/core/server/uuid/manage_uuid.ts index 30ba856be8d99..63a18c9f0fc0b 100644 --- a/src/core/server/uuid/manage_uuid.ts +++ b/src/core/server/uuid/manage_uuid.ts @@ -30,7 +30,7 @@ const FILE_NAME = 'uuid'; const readFile = promisify(fs.readFile); const writeFile = promisify(fs.writeFile); -export async function manageInstanceUUID(configService: IConfigService): Promise { +export async function manageInstanceUuid(configService: IConfigService): Promise { const config = await configService .getConfig$() .pipe(take(1)) diff --git a/src/core/server/uuid/uuid_service.mock.ts b/src/core/server/uuid/uuid_service.mock.ts new file mode 100644 index 0000000000000..bf40eaee20636 --- /dev/null +++ b/src/core/server/uuid/uuid_service.mock.ts @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { UuidService, UuidServiceSetup } from './uuid_service'; + +const createSetupContractMock = () => { + const setupContract: jest.Mocked = { + getInstanceUuid: jest.fn().mockImplementation(() => 'uuid'), + }; + return setupContract; +}; + +type UuidServiceContract = PublicMethodsOf; +const createMock = () => { + const mocked: jest.Mocked = { + setup: jest.fn(), + }; + mocked.setup.mockResolvedValue(createSetupContractMock()); + return mocked; +}; + +export const uuidServiceMock = { + create: createMock, + createSetupContract: createSetupContractMock, +}; diff --git a/src/core/server/uuid/uuid_service.test.ts b/src/core/server/uuid/uuid_service.test.ts new file mode 100644 index 0000000000000..9e7fd62e2e273 --- /dev/null +++ b/src/core/server/uuid/uuid_service.test.ts @@ -0,0 +1,65 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { UuidService } from './uuid_service'; +import { loggingServiceMock } from '../logging/logging_service.mock'; +import { mockCoreContext } from '../core_context.mock'; +import { manageInstanceUuid } from './manage_uuid'; +import { CoreContext } from '../core_context'; + +jest.mock('./manage_uuid', () => ({ + manageInstanceUuid: jest.fn().mockResolvedValue('SOME_UUID'), +})); + +describe('UuidService', () => { + let logger: ReturnType; + let coreContext: CoreContext; + let service: UuidService; + + beforeEach(() => { + jest.clearAllMocks(); + logger = loggingServiceMock.create(); + coreContext = mockCoreContext.create({ logger }); + service = new UuidService(coreContext); + }); + + describe('#setup()', () => { + it('should call manageInstanceUuid with core configuration service', async () => { + await service.setup(); + expect(manageInstanceUuid).toHaveBeenCalledTimes(1); + expect(manageInstanceUuid).toHaveBeenCalledWith(coreContext.configService); + }); + + it('should log a message containing the UUID', async () => { + await service.setup(); + expect(loggingServiceMock.collect(logger).info).toMatchInlineSnapshot(` + Array [ + Array [ + "Kibana instance UUID: SOME_UUID", + ], + ] + `); + }); + + it('should returns the uuid resolved from manageInstanceUuid', async () => { + const setup = await service.setup(); + expect(setup.getInstanceUuid()).toEqual('SOME_UUID'); + }); + }); +}); diff --git a/src/core/server/uuid/uuid_service.ts b/src/core/server/uuid/uuid_service.ts new file mode 100644 index 0000000000000..cdce4c92508ec --- /dev/null +++ b/src/core/server/uuid/uuid_service.ts @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { manageInstanceUuid } from './manage_uuid'; +import { CoreContext } from '../core_context'; +import { Logger } from '../logging'; +import { IConfigService } from '../config'; + +/** + * APIs to access the application's instance uuid. + */ +export interface UuidServiceSetup { + /** + * Retrieve the Kibana instance uuid. + */ + getInstanceUuid(): string; +} + +/** @internal */ +export class UuidService { + private readonly log: Logger; + private readonly configService: IConfigService; + private uuid: string = ''; + + constructor(core: CoreContext) { + this.log = core.logger.get('uuid'); + this.configService = core.configService; + } + + public async setup() { + this.uuid = await manageInstanceUuid(this.configService); + this.log.info(`Kibana instance UUID: ${this.uuid}`); + return { + getInstanceUuid: () => this.uuid, + }; + } +} From 958b28ea18245ab8251653e3c4a9df6c08ed9343 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 3 Dec 2019 11:24:34 +0100 Subject: [PATCH 03/18] handle legacy compatibility --- src/core/server/legacy/legacy_service.mock.ts | 36 +++++++++++++++++++ src/core/server/server.ts | 2 +- src/core/server/uuid/uuid_service.test.ts | 28 ++++++++++----- src/core/server/uuid/uuid_service.ts | 11 +++++- 4 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 src/core/server/legacy/legacy_service.mock.ts diff --git a/src/core/server/legacy/legacy_service.mock.ts b/src/core/server/legacy/legacy_service.mock.ts new file mode 100644 index 0000000000000..4754ab0e77b65 --- /dev/null +++ b/src/core/server/legacy/legacy_service.mock.ts @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { LegacyServiceDiscoverPlugins } from './legacy_service'; + +const createDiscoverMock = () => { + const setupContract: DeeplyMockedKeys = { + pluginSpecs: [], + uiExports: {} as any, + pluginExtendedConfig: { + get: jest.fn(), + set: jest.fn(), + } as any, + }; + return setupContract; +}; + +export const legacyServiceMock = { + createDiscover: createDiscoverMock, +}; diff --git a/src/core/server/server.ts b/src/core/server/server.ts index f6ebbe1a2c9ed..d26319f859d69 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -100,7 +100,7 @@ export class Server { ]), }); - const uuidSetup = await this.uuid.setup(); + const uuidSetup = await this.uuid.setup({ legacyPlugins }); const httpSetup = await this.http.setup({ context: contextServiceSetup, diff --git a/src/core/server/uuid/uuid_service.test.ts b/src/core/server/uuid/uuid_service.test.ts index 9e7fd62e2e273..a286b140bd9f3 100644 --- a/src/core/server/uuid/uuid_service.test.ts +++ b/src/core/server/uuid/uuid_service.test.ts @@ -18,17 +18,20 @@ */ import { UuidService } from './uuid_service'; -import { loggingServiceMock } from '../logging/logging_service.mock'; -import { mockCoreContext } from '../core_context.mock'; import { manageInstanceUuid } from './manage_uuid'; import { CoreContext } from '../core_context'; +import { loggingServiceMock } from '../logging/logging_service.mock'; +import { mockCoreContext } from '../core_context.mock'; +import { legacyServiceMock } from '../legacy/legacy_service.mock'; + jest.mock('./manage_uuid', () => ({ manageInstanceUuid: jest.fn().mockResolvedValue('SOME_UUID'), })); describe('UuidService', () => { let logger: ReturnType; + let legacyDiscover: ReturnType; let coreContext: CoreContext; let service: UuidService; @@ -36,18 +39,19 @@ describe('UuidService', () => { jest.clearAllMocks(); logger = loggingServiceMock.create(); coreContext = mockCoreContext.create({ logger }); + legacyDiscover = legacyServiceMock.createDiscover(); service = new UuidService(coreContext); }); describe('#setup()', () => { - it('should call manageInstanceUuid with core configuration service', async () => { - await service.setup(); + it('calls manageInstanceUuid with core configuration service', async () => { + await service.setup({ legacyPlugins: legacyDiscover }); expect(manageInstanceUuid).toHaveBeenCalledTimes(1); expect(manageInstanceUuid).toHaveBeenCalledWith(coreContext.configService); }); - it('should log a message containing the UUID', async () => { - await service.setup(); + it('logs a message containing the UUID', async () => { + await service.setup({ legacyPlugins: legacyDiscover }); expect(loggingServiceMock.collect(logger).info).toMatchInlineSnapshot(` Array [ Array [ @@ -57,9 +61,17 @@ describe('UuidService', () => { `); }); - it('should returns the uuid resolved from manageInstanceUuid', async () => { - const setup = await service.setup(); + it('returns the uuid resolved from manageInstanceUuid', async () => { + const setup = await service.setup({ legacyPlugins: legacyDiscover }); expect(setup.getInstanceUuid()).toEqual('SOME_UUID'); }); + + it('sets the uuid value in the legacy config', async () => { + await service.setup({ legacyPlugins: legacyDiscover }); + expect(legacyDiscover.pluginExtendedConfig.set).toHaveBeenCalledWith( + 'server.uuid', + 'SOME_UUID' + ); + }); }); }); diff --git a/src/core/server/uuid/uuid_service.ts b/src/core/server/uuid/uuid_service.ts index cdce4c92508ec..de2e535c44848 100644 --- a/src/core/server/uuid/uuid_service.ts +++ b/src/core/server/uuid/uuid_service.ts @@ -21,6 +21,7 @@ import { manageInstanceUuid } from './manage_uuid'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; import { IConfigService } from '../config'; +import { LegacyServiceDiscoverPlugins } from '../legacy/legacy_service'; /** * APIs to access the application's instance uuid. @@ -32,6 +33,10 @@ export interface UuidServiceSetup { getInstanceUuid(): string; } +interface SetupDeps { + legacyPlugins: LegacyServiceDiscoverPlugins; +} + /** @internal */ export class UuidService { private readonly log: Logger; @@ -43,9 +48,13 @@ export class UuidService { this.configService = core.configService; } - public async setup() { + public async setup({ legacyPlugins }: SetupDeps) { this.uuid = await manageInstanceUuid(this.configService); this.log.info(`Kibana instance UUID: ${this.uuid}`); + + // propagate the instance uuid to the legacy config, as it was the legacy way to access it. + legacyPlugins.pluginExtendedConfig.set('server.uuid', this.uuid); + return { getInstanceUuid: () => this.uuid, }; From 3ed08c51fdc244c80f7cf9d34cf9bbea1ed1b704 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Tue, 3 Dec 2019 11:32:56 +0100 Subject: [PATCH 04/18] update generated docs --- .../server/kibana-plugin-server.coresetup.md | 1 + .../kibana-plugin-server.coresetup.uuid.md | 13 ++++++++++++ .../core/server/kibana-plugin-server.md | 1 + ...server.uuidservicesetup.getinstanceuuid.md | 17 ++++++++++++++++ .../kibana-plugin-server.uuidservicesetup.md | 20 +++++++++++++++++++ src/core/server/server.api.md | 9 +++++++++ 6 files changed, 61 insertions(+) create mode 100644 docs/development/core/server/kibana-plugin-server.coresetup.uuid.md create mode 100644 docs/development/core/server/kibana-plugin-server.uuidservicesetup.getinstanceuuid.md create mode 100644 docs/development/core/server/kibana-plugin-server.uuidservicesetup.md diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.md b/docs/development/core/server/kibana-plugin-server.coresetup.md index 3886b6e05e657..3f7f5b727ee80 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.md @@ -22,4 +22,5 @@ export interface CoreSetup | [http](./kibana-plugin-server.coresetup.http.md) | HttpServiceSetup | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | | [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md) | SavedObjectsServiceSetup | [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | | [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) | UiSettingsServiceSetup | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | +| [uuid](./kibana-plugin-server.coresetup.uuid.md) | UuidServiceSetup | [UuidServiceSetup](./kibana-plugin-server.uuidservicesetup.md) | diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.uuid.md b/docs/development/core/server/kibana-plugin-server.coresetup.uuid.md new file mode 100644 index 0000000000000..2b9077735d8e3 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.coresetup.uuid.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) > [uuid](./kibana-plugin-server.coresetup.uuid.md) + +## CoreSetup.uuid property + +[UuidServiceSetup](./kibana-plugin-server.uuidservicesetup.md) + +Signature: + +```typescript +uuid: UuidServiceSetup; +``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 9144742c9bb73..79cc20ee19e66 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -132,6 +132,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | UiSettings parameters defined by the plugins. | | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | | | [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md) | Describes the values explicitly set by user. | +| [UuidServiceSetup](./kibana-plugin-server.uuidservicesetup.md) | APIs to access the application's instance uuid. | ## Variables diff --git a/docs/development/core/server/kibana-plugin-server.uuidservicesetup.getinstanceuuid.md b/docs/development/core/server/kibana-plugin-server.uuidservicesetup.getinstanceuuid.md new file mode 100644 index 0000000000000..e0b7012bea4aa --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uuidservicesetup.getinstanceuuid.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UuidServiceSetup](./kibana-plugin-server.uuidservicesetup.md) > [getInstanceUuid](./kibana-plugin-server.uuidservicesetup.getinstanceuuid.md) + +## UuidServiceSetup.getInstanceUuid() method + +Retrieve the Kibana instance uuid. + +Signature: + +```typescript +getInstanceUuid(): string; +``` +Returns: + +`string` + diff --git a/docs/development/core/server/kibana-plugin-server.uuidservicesetup.md b/docs/development/core/server/kibana-plugin-server.uuidservicesetup.md new file mode 100644 index 0000000000000..f2a6cfdeac704 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uuidservicesetup.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UuidServiceSetup](./kibana-plugin-server.uuidservicesetup.md) + +## UuidServiceSetup interface + +APIs to access the application's instance uuid. + +Signature: + +```typescript +export interface UuidServiceSetup +``` + +## Methods + +| Method | Description | +| --- | --- | +| [getInstanceUuid()](./kibana-plugin-server.uuidservicesetup.getinstanceuuid.md) | Retrieve the Kibana instance uuid. | + diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 7e1226aa7238b..d64c4baaa18b9 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -544,6 +544,8 @@ export interface CoreSetup { savedObjects: SavedObjectsServiceSetup; // (undocumented) uiSettings: UiSettingsServiceSetup; + // (undocumented) + uuid: UuidServiceSetup; } // @public @@ -1755,6 +1757,13 @@ export interface UserProvidedValues { userValue?: T; } +// Warning: (ae-missing-release-tag) "UuidServiceSetup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface UuidServiceSetup { + getInstanceUuid(): string; +} + // @public export const validBodyOutput: readonly ["data", "stream"]; From 6964d1ee0cdecd3044de77704f0dc80a0fa30826 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 6 Dec 2019 14:52:54 +0100 Subject: [PATCH 05/18] add `set` to LegacyConfig interface --- src/core/server/legacy/config/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/server/legacy/config/types.ts b/src/core/server/legacy/config/types.ts index cc4a6ac11fee4..312c0a9019be7 100644 --- a/src/core/server/legacy/config/types.ts +++ b/src/core/server/legacy/config/types.ts @@ -25,4 +25,6 @@ export interface LegacyConfig { get(key?: string): T; has(key: string): boolean; + set(key: string, value: any): void; + set(config: Record): void; } From 88f9790bcab10e5238a2316ca4f668c0a08d5954 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 6 Dec 2019 16:18:58 +0100 Subject: [PATCH 06/18] Fix types --- src/core/server/legacy/legacy_service.mock.ts | 3 +++ .../migrations/core/build_index_map.test.ts | 10 ++++++---- src/legacy/server/kbn_server.d.ts | 3 ++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/core/server/legacy/legacy_service.mock.ts b/src/core/server/legacy/legacy_service.mock.ts index 4754ab0e77b65..ac0319cdf4eb5 100644 --- a/src/core/server/legacy/legacy_service.mock.ts +++ b/src/core/server/legacy/legacy_service.mock.ts @@ -22,9 +22,12 @@ import { LegacyServiceDiscoverPlugins } from './legacy_service'; const createDiscoverMock = () => { const setupContract: DeeplyMockedKeys = { pluginSpecs: [], + disabledPluginSpecs: [], uiExports: {} as any, + settings: {}, pluginExtendedConfig: { get: jest.fn(), + has: jest.fn(), set: jest.fn(), } as any, }; diff --git a/src/core/server/saved_objects/migrations/core/build_index_map.test.ts b/src/core/server/saved_objects/migrations/core/build_index_map.test.ts index 622f32d3e653b..4fcaf50d4f538 100644 --- a/src/core/server/saved_objects/migrations/core/build_index_map.test.ts +++ b/src/core/server/saved_objects/migrations/core/build_index_map.test.ts @@ -22,9 +22,11 @@ import { ObjectToConfigAdapter } from '../../../config'; import { SavedObjectsSchema } from '../../schema'; import { LegacyConfig } from '../../../legacy/config'; +const config = (new ObjectToConfigAdapter({}) as unknown) as LegacyConfig; + test('mappings without index pattern goes to default index', () => { const result = createIndexMap({ - config: new ObjectToConfigAdapter({}) as LegacyConfig, + config, kibanaIndexName: '.kibana', schema: new SavedObjectsSchema({ type1: { @@ -58,7 +60,7 @@ test('mappings without index pattern goes to default index', () => { test(`mappings with custom index pattern doesn't go to default index`, () => { const result = createIndexMap({ - config: new ObjectToConfigAdapter({}) as LegacyConfig, + config, kibanaIndexName: '.kibana', schema: new SavedObjectsSchema({ type1: { @@ -93,7 +95,7 @@ test(`mappings with custom index pattern doesn't go to default index`, () => { test('creating a script gets added to the index pattern', () => { const result = createIndexMap({ - config: new ObjectToConfigAdapter({}) as LegacyConfig, + config, kibanaIndexName: '.kibana', schema: new SavedObjectsSchema({ type1: { @@ -158,7 +160,7 @@ test('throws when two scripts are defined for an index pattern', () => { }; expect(() => createIndexMap({ - config: new ObjectToConfigAdapter({}) as LegacyConfig, + config, kibanaIndexName: defaultIndex, schema, indexMap, diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index d6dea4decee00..9a18f7e8a3003 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -45,7 +45,8 @@ import { IndexPatternsServiceFactory } from './index_patterns'; import { Capabilities } from '../../core/server'; import { UiSettingsServiceFactoryOptions } from '../../legacy/ui/ui_settings/ui_settings_service_factory'; -export type KibanaConfig = LegacyConfig; +// lot of legacy code was assuming this type only had these two methods +export type KibanaConfig = Pick; export interface UiApp { getId(): string; From c8a808d98ea43608e97b67a5a832a3cc4c78c5e0 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 6 Dec 2019 16:56:49 +0100 Subject: [PATCH 07/18] fix config access --- src/core/server/http/http_config.ts | 1 + ...gacy_object_to_config_adapter.test.ts.snap | 2 ++ .../config/legacy_object_to_config_adapter.ts | 1 + src/core/server/uuid/manage_uuid.test.ts | 20 +++++++++++------- src/core/server/uuid/manage_uuid.ts | 21 +++++++++++++------ 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index cb7726de4da5a..f8afe4da3b67d 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -91,6 +91,7 @@ export const config = { ) ), }), + uuid: schema.maybe(schema.string()), }, { validate: rawConfig => { diff --git a/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap b/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap index d327860052eb9..0ebd8b8371628 100644 --- a/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap +++ b/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap @@ -20,6 +20,7 @@ Object { "keyPassphrase": "some-phrase", "someNewValue": "new", }, + "uuid": undefined, } `; @@ -43,6 +44,7 @@ Object { "enabled": false, "key": "key", }, + "uuid": undefined, } `; diff --git a/src/core/server/legacy/config/legacy_object_to_config_adapter.ts b/src/core/server/legacy/config/legacy_object_to_config_adapter.ts index 8035596bb6072..c2019cfb537a0 100644 --- a/src/core/server/legacy/config/legacy_object_to_config_adapter.ts +++ b/src/core/server/legacy/config/legacy_object_to_config_adapter.ts @@ -72,6 +72,7 @@ export class LegacyObjectToConfigAdapter extends ObjectToConfigAdapter { keepaliveTimeout: configValue.keepaliveTimeout, socketTimeout: configValue.socketTimeout, compression: configValue.compression, + uuid: configValue.uuid, }; } diff --git a/src/core/server/uuid/manage_uuid.test.ts b/src/core/server/uuid/manage_uuid.test.ts index 064595f5223ea..bc318368abb84 100644 --- a/src/core/server/uuid/manage_uuid.test.ts +++ b/src/core/server/uuid/manage_uuid.test.ts @@ -21,6 +21,7 @@ import { readFile, writeFile } from 'fs'; import { join } from 'path'; import { manageInstanceUuid } from './manage_uuid'; import { configServiceMock } from '../config/config_service.mock'; +import { BehaviorSubject } from 'rxjs'; jest.mock('uuid', () => ({ v4: () => 'NEW_UUID', @@ -52,16 +53,21 @@ const mockReadFile = ({ }; const getConfigService = (serverUUID: string | undefined) => { - return configServiceMock.create({ - getConfig$: { - path: { + const configService = configServiceMock.create(); + configService.atPath.mockImplementation(path => { + if (path === 'path') { + return new BehaviorSubject({ data: 'data-folder', - }, - server: { + }); + } + if (path === 'server') { + return new BehaviorSubject({ uuid: serverUUID, - }, - }, + }); + } + return new BehaviorSubject({}); }); + return configService; }; describe('manageInstanceUUID', () => { diff --git a/src/core/server/uuid/manage_uuid.ts b/src/core/server/uuid/manage_uuid.ts index 63a18c9f0fc0b..2bd40f5688991 100644 --- a/src/core/server/uuid/manage_uuid.ts +++ b/src/core/server/uuid/manage_uuid.ts @@ -23,6 +23,8 @@ import * as fs from 'fs'; import { join } from 'path'; import { take } from 'rxjs/operators'; import { IConfigService } from '../config'; +import { PathConfigType } from '../path'; +import { HttpConfigType } from '../http'; const FILE_ENCODING = 'utf8'; const FILE_NAME = 'uuid'; @@ -31,14 +33,21 @@ const readFile = promisify(fs.readFile); const writeFile = promisify(fs.writeFile); export async function manageInstanceUuid(configService: IConfigService): Promise { - const config = await configService - .getConfig$() - .pipe(take(1)) - .toPromise(); - const uuidFilePath = join(config.get('path.data'), FILE_NAME); + const [pathConfig, serverConfig] = await Promise.all([ + configService + .atPath('path') + .pipe(take(1)) + .toPromise(), + configService + .atPath('server') + .pipe(take(1)) + .toPromise(), + ]); + + const uuidFilePath = join(pathConfig.data, FILE_NAME); const uuidFromFile = await readUUIDFromFile(uuidFilePath); - const uuidFromConfig = config.get('server.uuid'); + const uuidFromConfig = serverConfig.uuid; if (uuidFromConfig) { if (uuidFromConfig === uuidFromFile) { From a499ff1fc4481de004aff1cbe8bc8b2c7c918eeb Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 12 Dec 2019 08:08:23 +0100 Subject: [PATCH 08/18] respect naming conventions for uuid --- src/core/server/uuid/manage_uuid.test.ts | 4 ++-- src/core/server/uuid/manage_uuid.ts | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/core/server/uuid/manage_uuid.test.ts b/src/core/server/uuid/manage_uuid.test.ts index bc318368abb84..e82c4202a7a4e 100644 --- a/src/core/server/uuid/manage_uuid.test.ts +++ b/src/core/server/uuid/manage_uuid.test.ts @@ -52,7 +52,7 @@ const mockReadFile = ({ }); }; -const getConfigService = (serverUUID: string | undefined) => { +const getConfigService = (serverUuid: string | undefined) => { const configService = configServiceMock.create(); configService.atPath.mockImplementation(path => { if (path === 'path') { @@ -62,7 +62,7 @@ const getConfigService = (serverUUID: string | undefined) => { } if (path === 'server') { return new BehaviorSubject({ - uuid: serverUUID, + uuid: serverUuid, }); } return new BehaviorSubject({}); diff --git a/src/core/server/uuid/manage_uuid.ts b/src/core/server/uuid/manage_uuid.ts index 2bd40f5688991..f46e366c4e564 100644 --- a/src/core/server/uuid/manage_uuid.ts +++ b/src/core/server/uuid/manage_uuid.ts @@ -46,7 +46,7 @@ export async function manageInstanceUuid(configService: IConfigService): Promise const uuidFilePath = join(pathConfig.data, FILE_NAME); - const uuidFromFile = await readUUIDFromFile(uuidFilePath); + const uuidFromFile = await readUuidFromFile(uuidFilePath); const uuidFromConfig = serverConfig.uuid; if (uuidFromConfig) { @@ -55,20 +55,20 @@ export async function manageInstanceUuid(configService: IConfigService): Promise return uuidFromConfig; } else { // uuid in file don't match, or file was not present, we need to write it. - await writeUUIDToFile(uuidFilePath, uuidFromConfig); + await writeUuidToFile(uuidFilePath, uuidFromConfig); return uuidFromConfig; } } if (uuidFromFile === undefined) { // no uuid either in config or file, we need to generate and write it. - const newUUID = uuid.v4(); - await writeUUIDToFile(uuidFilePath, newUUID); - return newUUID; + const newUuid = uuid.v4(); + await writeUuidToFile(uuidFilePath, newUuid); + return newUuid; } return uuidFromFile; } -async function readUUIDFromFile(filepath: string): Promise { +async function readUuidFromFile(filepath: string): Promise { try { const content = await readFile(filepath); return content.toString(FILE_ENCODING); @@ -81,6 +81,6 @@ async function readUUIDFromFile(filepath: string): Promise { } } -async function writeUUIDToFile(filepath: string, uuidValue: string) { +async function writeUuidToFile(filepath: string, uuidValue: string) { return await writeFile(filepath, uuidValue, { encoding: FILE_ENCODING }); } From afcdf9a9c7daf811b88d6083126bbb80ecc5bf14 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 12 Dec 2019 08:16:15 +0100 Subject: [PATCH 09/18] remove hardcoded config paths --- src/core/server/uuid/manage_uuid.test.ts | 1 + src/core/server/uuid/manage_uuid.ts | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/core/server/uuid/manage_uuid.test.ts b/src/core/server/uuid/manage_uuid.test.ts index e82c4202a7a4e..bea00256f2269 100644 --- a/src/core/server/uuid/manage_uuid.test.ts +++ b/src/core/server/uuid/manage_uuid.test.ts @@ -28,6 +28,7 @@ jest.mock('uuid', () => ({ })); jest.mock('fs', () => ({ + ...jest.requireActual('fs'), readFile: jest.fn((path, callback) => callback(null, Buffer.from(''))), writeFile: jest.fn((path, value, options, callback) => callback(null, null)), })); diff --git a/src/core/server/uuid/manage_uuid.ts b/src/core/server/uuid/manage_uuid.ts index f46e366c4e564..4e852fa7b9b75 100644 --- a/src/core/server/uuid/manage_uuid.ts +++ b/src/core/server/uuid/manage_uuid.ts @@ -23,8 +23,8 @@ import * as fs from 'fs'; import { join } from 'path'; import { take } from 'rxjs/operators'; import { IConfigService } from '../config'; -import { PathConfigType } from '../path'; -import { HttpConfigType } from '../http'; +import { PathConfigType, config as pathConfigDef } from '../path'; +import { HttpConfigType, config as httpConfigDef } from '../http'; const FILE_ENCODING = 'utf8'; const FILE_NAME = 'uuid'; @@ -35,11 +35,11 @@ const writeFile = promisify(fs.writeFile); export async function manageInstanceUuid(configService: IConfigService): Promise { const [pathConfig, serverConfig] = await Promise.all([ configService - .atPath('path') + .atPath(pathConfigDef.path) .pipe(take(1)) .toPromise(), configService - .atPath('server') + .atPath(httpConfigDef.path) .pipe(take(1)) .toPromise(), ]); From 6c0c88083163a6c8e7f8689de7e8828dcf884064 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 12 Dec 2019 08:19:38 +0100 Subject: [PATCH 10/18] rename manageInstanceUuid to resolveInstanceUuid --- .../{manage_uuid.test.ts => resolve_uuid.test.ts} | 14 +++++++------- .../uuid/{manage_uuid.ts => resolve_uuid.ts} | 2 +- src/core/server/uuid/uuid_service.test.ts | 10 +++++----- src/core/server/uuid/uuid_service.ts | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) rename src/core/server/uuid/{manage_uuid.test.ts => resolve_uuid.test.ts} (91%) rename src/core/server/uuid/{manage_uuid.ts => resolve_uuid.ts} (96%) diff --git a/src/core/server/uuid/manage_uuid.test.ts b/src/core/server/uuid/resolve_uuid.test.ts similarity index 91% rename from src/core/server/uuid/manage_uuid.test.ts rename to src/core/server/uuid/resolve_uuid.test.ts index bea00256f2269..7d92587a9c983 100644 --- a/src/core/server/uuid/manage_uuid.test.ts +++ b/src/core/server/uuid/resolve_uuid.test.ts @@ -19,7 +19,7 @@ import { readFile, writeFile } from 'fs'; import { join } from 'path'; -import { manageInstanceUuid } from './manage_uuid'; +import { resolveInstanceUuid } from './resolve_uuid'; import { configServiceMock } from '../config/config_service.mock'; import { BehaviorSubject } from 'rxjs'; @@ -71,7 +71,7 @@ const getConfigService = (serverUuid: string | undefined) => { return configService; }; -describe('manageInstanceUUID', () => { +describe('resolveInstanceUuid', () => { let configService: ReturnType; beforeEach(() => { @@ -82,7 +82,7 @@ describe('manageInstanceUUID', () => { describe('when file is present and config property is set', () => { it('writes to file and returns the config uuid if they mismatch', async () => { - const uuid = await manageInstanceUuid(configService); + const uuid = await resolveInstanceUuid(configService); expect(uuid).toEqual(DEFAULT_CONFIG_UUID); expect(writeFile).toHaveBeenCalledWith( join('data-folder', 'uuid'), @@ -93,7 +93,7 @@ describe('manageInstanceUUID', () => { }); it('does not write to file if they match', async () => { mockReadFile({ uuid: DEFAULT_CONFIG_UUID }); - const uuid = await manageInstanceUuid(configService); + const uuid = await resolveInstanceUuid(configService); expect(uuid).toEqual(DEFAULT_CONFIG_UUID); expect(writeFile).not.toHaveBeenCalled(); }); @@ -102,7 +102,7 @@ describe('manageInstanceUUID', () => { describe('when file is not present and config property is set', () => { it('writes the uuid to file and returns the config uuid', async () => { mockReadFile({ error: fileNotFoundError }); - const uuid = await manageInstanceUuid(configService); + const uuid = await resolveInstanceUuid(configService); expect(uuid).toEqual(DEFAULT_CONFIG_UUID); expect(writeFile).toHaveBeenCalledWith( join('data-folder', 'uuid'), @@ -116,7 +116,7 @@ describe('manageInstanceUUID', () => { describe('when file is present and config property is not set', () => { it('does not write to file and returns the file uuid', async () => { configService = getConfigService(undefined); - const uuid = await manageInstanceUuid(configService); + const uuid = await resolveInstanceUuid(configService); expect(uuid).toEqual(DEFAULT_FILE_UUID); expect(writeFile).not.toHaveBeenCalled(); }); @@ -126,7 +126,7 @@ describe('manageInstanceUUID', () => { it('generates a new uuid and write it to file', async () => { configService = getConfigService(undefined); mockReadFile({ error: fileNotFoundError }); - const uuid = await manageInstanceUuid(configService); + const uuid = await resolveInstanceUuid(configService); expect(uuid).toEqual('NEW_UUID'); expect(writeFile).toHaveBeenCalledWith( join('data-folder', 'uuid'), diff --git a/src/core/server/uuid/manage_uuid.ts b/src/core/server/uuid/resolve_uuid.ts similarity index 96% rename from src/core/server/uuid/manage_uuid.ts rename to src/core/server/uuid/resolve_uuid.ts index 4e852fa7b9b75..8579e80a22735 100644 --- a/src/core/server/uuid/manage_uuid.ts +++ b/src/core/server/uuid/resolve_uuid.ts @@ -32,7 +32,7 @@ const FILE_NAME = 'uuid'; const readFile = promisify(fs.readFile); const writeFile = promisify(fs.writeFile); -export async function manageInstanceUuid(configService: IConfigService): Promise { +export async function resolveInstanceUuid(configService: IConfigService): Promise { const [pathConfig, serverConfig] = await Promise.all([ configService .atPath(pathConfigDef.path) diff --git a/src/core/server/uuid/uuid_service.test.ts b/src/core/server/uuid/uuid_service.test.ts index a286b140bd9f3..52e454841e64e 100644 --- a/src/core/server/uuid/uuid_service.test.ts +++ b/src/core/server/uuid/uuid_service.test.ts @@ -18,15 +18,15 @@ */ import { UuidService } from './uuid_service'; -import { manageInstanceUuid } from './manage_uuid'; +import { resolveInstanceUuid } from './resolve_uuid'; import { CoreContext } from '../core_context'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { mockCoreContext } from '../core_context.mock'; import { legacyServiceMock } from '../legacy/legacy_service.mock'; -jest.mock('./manage_uuid', () => ({ - manageInstanceUuid: jest.fn().mockResolvedValue('SOME_UUID'), +jest.mock('./resolve_uuid', () => ({ + resolveInstanceUuid: jest.fn().mockResolvedValue('SOME_UUID'), })); describe('UuidService', () => { @@ -46,8 +46,8 @@ describe('UuidService', () => { describe('#setup()', () => { it('calls manageInstanceUuid with core configuration service', async () => { await service.setup({ legacyPlugins: legacyDiscover }); - expect(manageInstanceUuid).toHaveBeenCalledTimes(1); - expect(manageInstanceUuid).toHaveBeenCalledWith(coreContext.configService); + expect(resolveInstanceUuid).toHaveBeenCalledTimes(1); + expect(resolveInstanceUuid).toHaveBeenCalledWith(coreContext.configService); }); it('logs a message containing the UUID', async () => { diff --git a/src/core/server/uuid/uuid_service.ts b/src/core/server/uuid/uuid_service.ts index de2e535c44848..0e415edf40922 100644 --- a/src/core/server/uuid/uuid_service.ts +++ b/src/core/server/uuid/uuid_service.ts @@ -17,7 +17,7 @@ * under the License. */ -import { manageInstanceUuid } from './manage_uuid'; +import { resolveInstanceUuid } from './resolve_uuid'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; import { IConfigService } from '../config'; @@ -49,7 +49,7 @@ export class UuidService { } public async setup({ legacyPlugins }: SetupDeps) { - this.uuid = await manageInstanceUuid(this.configService); + this.uuid = await resolveInstanceUuid(this.configService); this.log.info(`Kibana instance UUID: ${this.uuid}`); // propagate the instance uuid to the legacy config, as it was the legacy way to access it. From 6139832f83db460f8c1911c40f8d878aced89c9e Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 12 Dec 2019 09:21:37 +0100 Subject: [PATCH 11/18] moves legacy config uuid set from uuid to legacy service --- .../legacy/legacy_service.test.mocks.ts | 34 +++++++++ src/core/server/legacy/legacy_service.test.ts | 76 ++++++++++++++----- src/core/server/legacy/legacy_service.ts | 3 + src/core/server/uuid/uuid_service.test.ts | 17 +---- src/core/server/uuid/uuid_service.ts | 10 +-- src/legacy/server/config/schema.js | 2 +- 6 files changed, 100 insertions(+), 42 deletions(-) create mode 100644 src/core/server/legacy/legacy_service.test.mocks.ts diff --git a/src/core/server/legacy/legacy_service.test.mocks.ts b/src/core/server/legacy/legacy_service.test.mocks.ts new file mode 100644 index 0000000000000..e8d4a0ed0bd4d --- /dev/null +++ b/src/core/server/legacy/legacy_service.test.mocks.ts @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const findLegacyPluginSpecsMock = jest + .fn() + .mockImplementation((settings: Record) => ({ + pluginSpecs: [], + pluginExtendedConfig: { + has: jest.fn(), + get: jest.fn(() => settings), + set: jest.fn(), + }, + disabledPluginSpecs: [], + uiExports: [], + })); +jest.doMock('./plugins/find_legacy_plugin_specs.ts', () => ({ + findLegacyPluginSpecs: findLegacyPluginSpecsMock, +})); diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index fa2498a2e1d95..f1bdefd6dd9c0 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -17,32 +17,25 @@ * under the License. */ -import { BehaviorSubject, throwError } from 'rxjs'; - jest.mock('../../../legacy/server/kbn_server'); jest.mock('../../../cli/cluster/cluster_manager'); -jest.mock('./plugins/find_legacy_plugin_specs.ts', () => ({ - findLegacyPluginSpecs: (settings: Record) => ({ - pluginSpecs: [], - pluginExtendedConfig: settings, - disabledPluginSpecs: [], - uiExports: [], - }), -})); + +import { findLegacyPluginSpecsMock } from './legacy_service.test.mocks'; + +import { BehaviorSubject, throwError } from 'rxjs'; import { LegacyService, LegacyServiceSetupDeps, LegacyServiceStartDeps } from '.'; // @ts-ignore: implicit any for JS file import MockClusterManager from '../../../cli/cluster/cluster_manager'; import KbnServer from '../../../legacy/server/kbn_server'; import { Config, Env, ObjectToConfigAdapter } from '../config'; -import { contextServiceMock } from '../context/context_service.mock'; import { getEnvOptions } from '../config/__mocks__/env'; -import { configServiceMock } from '../config/config_service.mock'; - import { BasePathProxyServer } from '../http'; -import { loggingServiceMock } from '../logging/logging_service.mock'; import { DiscoveredPlugin } from '../plugins'; +import { configServiceMock } from '../config/config_service.mock'; +import { loggingServiceMock } from '../logging/logging_service.mock'; +import { contextServiceMock } from '../context/context_service.mock'; import { httpServiceMock } from '../http/http_service.mock'; import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock'; @@ -61,13 +54,17 @@ let startDeps: LegacyServiceStartDeps; const logger = loggingServiceMock.create(); let configService: ReturnType; +let uuidSetup: ReturnType; beforeEach(() => { coreId = Symbol(); env = Env.createDefault(getEnvOptions()); configService = configServiceMock.create(); + uuidSetup = uuidServiceMock.createSetupContract(); + findLegacyPluginSpecsMock.mockClear(); MockKbnServer.prototype.ready = jest.fn().mockReturnValue(Promise.resolve()); + MockKbnServer.prototype.listen = jest.fn(); setupDeps = { core: { @@ -90,7 +87,7 @@ beforeEach(() => { browserConfigs: new Map(), }, }, - uuid: uuidServiceMock.createSetupContract(), + uuid: uuidSetup, }, plugins: { 'plugin-id': 'plugin-value' }, }; @@ -135,11 +132,15 @@ describe('once LegacyService is set up with connection info', () => { expect(MockKbnServer).toHaveBeenCalledTimes(1); expect(MockKbnServer).toHaveBeenCalledWith( - { path: { autoListen: true }, server: { autoListen: true } }, { path: { autoListen: true }, server: { autoListen: true } }, // Because of the mock, path also gets the value + expect.objectContaining({ get: expect.any(Function) }), expect.any(Object), { disabledPluginSpecs: [], pluginSpecs: [], uiExports: [] } ); + expect(MockKbnServer.mock.calls[0][1].get()).toEqual({ + path: { autoListen: true }, + server: { autoListen: true }, + }); const [mockKbnServer] = MockKbnServer.mock.instances; expect(mockKbnServer.listen).toHaveBeenCalledTimes(1); @@ -162,10 +163,14 @@ describe('once LegacyService is set up with connection info', () => { expect(MockKbnServer).toHaveBeenCalledTimes(1); expect(MockKbnServer).toHaveBeenCalledWith( { path: { autoListen: false }, server: { autoListen: true } }, - { path: { autoListen: false }, server: { autoListen: true } }, + expect.objectContaining({ get: expect.any(Function) }), expect.any(Object), { disabledPluginSpecs: [], pluginSpecs: [], uiExports: [] } ); + expect(MockKbnServer.mock.calls[0][1].get()).toEqual({ + path: { autoListen: false }, + server: { autoListen: true }, + }); const [mockKbnServer] = MockKbnServer.mock.instances; expect(mockKbnServer.ready).toHaveBeenCalledTimes(1); @@ -299,10 +304,14 @@ describe('once LegacyService is set up without connection info', () => { expect(MockKbnServer).toHaveBeenCalledTimes(1); expect(MockKbnServer).toHaveBeenCalledWith( { path: {}, server: { autoListen: true } }, - { path: {}, server: { autoListen: true } }, + expect.objectContaining({ get: expect.any(Function) }), expect.any(Object), { disabledPluginSpecs: [], pluginSpecs: [], uiExports: [] } ); + expect(MockKbnServer.mock.calls[0][1].get()).toEqual({ + path: {}, + server: { autoListen: true }, + }); }); test('reconfigures logging configuration if new config is received.', async () => { @@ -384,3 +393,34 @@ test('Cannot start without setup phase', async () => { `"Legacy service is not setup yet."` ); }); + +test('Sets the server.uuid property on the legacy configuration', async () => { + configService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true })); + const legacyService = new LegacyService({ + coreId, + env, + logger, + configService: configService as any, + }); + + uuidSetup.getInstanceUuid.mockImplementation(() => 'UUID_FROM_SERVICE'); + + const configSetMock = jest.fn(); + + findLegacyPluginSpecsMock.mockImplementation((settings: Record) => ({ + pluginSpecs: [], + pluginExtendedConfig: { + has: jest.fn(), + get: jest.fn(() => settings), + set: configSetMock, + }, + disabledPluginSpecs: [], + uiExports: [], + })); + + await legacyService.discoverPlugins(); + await legacyService.setup(setupDeps); + + expect(configSetMock).toHaveBeenCalledTimes(1); + expect(configSetMock).toHaveBeenCalledWith('server.uuid', 'UUID_FROM_SERVICE'); +}); diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index a304d530c81e6..554744785bebd 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -184,6 +184,9 @@ export class LegacyService implements CoreService { 'Legacy service has not discovered legacy plugins yet. Ensure LegacyService.discoverPlugins() is called before LegacyService.setup()' ); } + // propagate the instance uuid to the legacy config, as it was the legacy way to access it. + this.legacyRawConfig.set('server.uuid', setupDeps.core.uuid.getInstanceUuid()); + this.setupDeps = setupDeps; } diff --git a/src/core/server/uuid/uuid_service.test.ts b/src/core/server/uuid/uuid_service.test.ts index 52e454841e64e..69b69a31f938a 100644 --- a/src/core/server/uuid/uuid_service.test.ts +++ b/src/core/server/uuid/uuid_service.test.ts @@ -23,7 +23,6 @@ import { CoreContext } from '../core_context'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { mockCoreContext } from '../core_context.mock'; -import { legacyServiceMock } from '../legacy/legacy_service.mock'; jest.mock('./resolve_uuid', () => ({ resolveInstanceUuid: jest.fn().mockResolvedValue('SOME_UUID'), @@ -31,7 +30,6 @@ jest.mock('./resolve_uuid', () => ({ describe('UuidService', () => { let logger: ReturnType; - let legacyDiscover: ReturnType; let coreContext: CoreContext; let service: UuidService; @@ -39,19 +37,18 @@ describe('UuidService', () => { jest.clearAllMocks(); logger = loggingServiceMock.create(); coreContext = mockCoreContext.create({ logger }); - legacyDiscover = legacyServiceMock.createDiscover(); service = new UuidService(coreContext); }); describe('#setup()', () => { it('calls manageInstanceUuid with core configuration service', async () => { - await service.setup({ legacyPlugins: legacyDiscover }); + await service.setup(); expect(resolveInstanceUuid).toHaveBeenCalledTimes(1); expect(resolveInstanceUuid).toHaveBeenCalledWith(coreContext.configService); }); it('logs a message containing the UUID', async () => { - await service.setup({ legacyPlugins: legacyDiscover }); + await service.setup(); expect(loggingServiceMock.collect(logger).info).toMatchInlineSnapshot(` Array [ Array [ @@ -62,16 +59,8 @@ describe('UuidService', () => { }); it('returns the uuid resolved from manageInstanceUuid', async () => { - const setup = await service.setup({ legacyPlugins: legacyDiscover }); + const setup = await service.setup(); expect(setup.getInstanceUuid()).toEqual('SOME_UUID'); }); - - it('sets the uuid value in the legacy config', async () => { - await service.setup({ legacyPlugins: legacyDiscover }); - expect(legacyDiscover.pluginExtendedConfig.set).toHaveBeenCalledWith( - 'server.uuid', - 'SOME_UUID' - ); - }); }); }); diff --git a/src/core/server/uuid/uuid_service.ts b/src/core/server/uuid/uuid_service.ts index 0e415edf40922..ed8442bb294d9 100644 --- a/src/core/server/uuid/uuid_service.ts +++ b/src/core/server/uuid/uuid_service.ts @@ -21,7 +21,6 @@ import { resolveInstanceUuid } from './resolve_uuid'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; import { IConfigService } from '../config'; -import { LegacyServiceDiscoverPlugins } from '../legacy/legacy_service'; /** * APIs to access the application's instance uuid. @@ -33,10 +32,6 @@ export interface UuidServiceSetup { getInstanceUuid(): string; } -interface SetupDeps { - legacyPlugins: LegacyServiceDiscoverPlugins; -} - /** @internal */ export class UuidService { private readonly log: Logger; @@ -48,13 +43,10 @@ export class UuidService { this.configService = core.configService; } - public async setup({ legacyPlugins }: SetupDeps) { + public async setup() { this.uuid = await resolveInstanceUuid(this.configService); this.log.info(`Kibana instance UUID: ${this.uuid}`); - // propagate the instance uuid to the legacy config, as it was the legacy way to access it. - legacyPlugins.pluginExtendedConfig.set('server.uuid', this.uuid); - return { getInstanceUuid: () => this.uuid, }; diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index a19a39da0f6dd..b58ef7c22caa0 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -75,7 +75,6 @@ export default () => Joi.object({ }), server: Joi.object({ - uuid: Joi.string().guid().default(), name: Joi.string().default(os.hostname()), defaultRoute: Joi.string().regex(/^\//, `start with a slash`), customResponseHeaders: Joi.object().unknown(true).default({}), @@ -106,6 +105,7 @@ export default () => Joi.object({ socketTimeout: HANDLED_IN_NEW_PLATFORM, ssl: HANDLED_IN_NEW_PLATFORM, compression: HANDLED_IN_NEW_PLATFORM, + uuid: HANDLED_IN_NEW_PLATFORM, }).default(), uiSettings: HANDLED_IN_NEW_PLATFORM, From 26719cb0ef4dde8ec916f9344ca2881c5fbc9cef Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Thu, 12 Dec 2019 10:05:40 +0100 Subject: [PATCH 12/18] log specific message depending on how uuid was resolved --- src/core/server/server.ts | 2 +- src/core/server/uuid/resolve_uuid.test.ts | 44 ++++++++++++++++++++--- src/core/server/uuid/resolve_uuid.ts | 17 +++++++-- src/core/server/uuid/uuid_service.test.ts | 16 +++------ src/core/server/uuid/uuid_service.ts | 3 +- 5 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/core/server/server.ts b/src/core/server/server.ts index d26319f859d69..f6ebbe1a2c9ed 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -100,7 +100,7 @@ export class Server { ]), }); - const uuidSetup = await this.uuid.setup({ legacyPlugins }); + const uuidSetup = await this.uuid.setup(); const httpSetup = await this.http.setup({ context: contextServiceSetup, diff --git a/src/core/server/uuid/resolve_uuid.test.ts b/src/core/server/uuid/resolve_uuid.test.ts index 7d92587a9c983..aefb1e4fa3df5 100644 --- a/src/core/server/uuid/resolve_uuid.test.ts +++ b/src/core/server/uuid/resolve_uuid.test.ts @@ -21,7 +21,9 @@ import { readFile, writeFile } from 'fs'; import { join } from 'path'; import { resolveInstanceUuid } from './resolve_uuid'; import { configServiceMock } from '../config/config_service.mock'; +import { loggingServiceMock } from '../logging/logging_service.mock'; import { BehaviorSubject } from 'rxjs'; +import { Logger } from '../logging'; jest.mock('uuid', () => ({ v4: () => 'NEW_UUID', @@ -73,16 +75,18 @@ const getConfigService = (serverUuid: string | undefined) => { describe('resolveInstanceUuid', () => { let configService: ReturnType; + let logger: jest.Mocked; beforeEach(() => { jest.clearAllMocks(); mockReadFile({ uuid: DEFAULT_FILE_UUID }); configService = getConfigService(DEFAULT_CONFIG_UUID); + logger = loggingServiceMock.create().get() as any; }); describe('when file is present and config property is set', () => { it('writes to file and returns the config uuid if they mismatch', async () => { - const uuid = await resolveInstanceUuid(configService); + const uuid = await resolveInstanceUuid(configService, logger); expect(uuid).toEqual(DEFAULT_CONFIG_UUID); expect(writeFile).toHaveBeenCalledWith( join('data-folder', 'uuid'), @@ -90,19 +94,31 @@ describe('resolveInstanceUuid', () => { expect.any(Object), expect.any(Function) ); + expect(logger.debug).toHaveBeenCalledTimes(1); + expect(logger.debug.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Updating Kibana instance UUID to: CONFIG_UUID (was: FILE_UUID)", + ] + `); }); it('does not write to file if they match', async () => { mockReadFile({ uuid: DEFAULT_CONFIG_UUID }); - const uuid = await resolveInstanceUuid(configService); + const uuid = await resolveInstanceUuid(configService, logger); expect(uuid).toEqual(DEFAULT_CONFIG_UUID); expect(writeFile).not.toHaveBeenCalled(); + expect(logger.debug).toHaveBeenCalledTimes(1); + expect(logger.debug.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Kibana instance UUID: CONFIG_UUID", + ] + `); }); }); describe('when file is not present and config property is set', () => { it('writes the uuid to file and returns the config uuid', async () => { mockReadFile({ error: fileNotFoundError }); - const uuid = await resolveInstanceUuid(configService); + const uuid = await resolveInstanceUuid(configService, logger); expect(uuid).toEqual(DEFAULT_CONFIG_UUID); expect(writeFile).toHaveBeenCalledWith( join('data-folder', 'uuid'), @@ -110,15 +126,27 @@ describe('resolveInstanceUuid', () => { expect.any(Object), expect.any(Function) ); + expect(logger.debug).toHaveBeenCalledTimes(1); + expect(logger.debug.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Setting new Kibana instance UUID: CONFIG_UUID", + ] + `); }); }); describe('when file is present and config property is not set', () => { it('does not write to file and returns the file uuid', async () => { configService = getConfigService(undefined); - const uuid = await resolveInstanceUuid(configService); + const uuid = await resolveInstanceUuid(configService, logger); expect(uuid).toEqual(DEFAULT_FILE_UUID); expect(writeFile).not.toHaveBeenCalled(); + expect(logger.debug).toHaveBeenCalledTimes(1); + expect(logger.debug.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Resuming persistent Kibana instance UUID: FILE_UUID", + ] + `); }); }); @@ -126,7 +154,7 @@ describe('resolveInstanceUuid', () => { it('generates a new uuid and write it to file', async () => { configService = getConfigService(undefined); mockReadFile({ error: fileNotFoundError }); - const uuid = await resolveInstanceUuid(configService); + const uuid = await resolveInstanceUuid(configService, logger); expect(uuid).toEqual('NEW_UUID'); expect(writeFile).toHaveBeenCalledWith( join('data-folder', 'uuid'), @@ -134,6 +162,12 @@ describe('resolveInstanceUuid', () => { expect.any(Object), expect.any(Function) ); + expect(logger.debug).toHaveBeenCalledTimes(1); + expect(logger.debug.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Setting new Kibana instance UUID: NEW_UUID", + ] + `); }); }); }); diff --git a/src/core/server/uuid/resolve_uuid.ts b/src/core/server/uuid/resolve_uuid.ts index 8579e80a22735..bc4d3d3e284c1 100644 --- a/src/core/server/uuid/resolve_uuid.ts +++ b/src/core/server/uuid/resolve_uuid.ts @@ -25,6 +25,7 @@ import { take } from 'rxjs/operators'; import { IConfigService } from '../config'; import { PathConfigType, config as pathConfigDef } from '../path'; import { HttpConfigType, config as httpConfigDef } from '../http'; +import { Logger } from '../logging'; const FILE_ENCODING = 'utf8'; const FILE_NAME = 'uuid'; @@ -32,7 +33,10 @@ const FILE_NAME = 'uuid'; const readFile = promisify(fs.readFile); const writeFile = promisify(fs.writeFile); -export async function resolveInstanceUuid(configService: IConfigService): Promise { +export async function resolveInstanceUuid( + configService: IConfigService, + logger: Logger +): Promise { const [pathConfig, serverConfig] = await Promise.all([ configService .atPath(pathConfigDef.path) @@ -52,19 +56,28 @@ export async function resolveInstanceUuid(configService: IConfigService): Promis if (uuidFromConfig) { if (uuidFromConfig === uuidFromFile) { // uuid matches, nothing to do + logger.debug(`Kibana instance UUID: ${uuidFromConfig}`); return uuidFromConfig; } else { // uuid in file don't match, or file was not present, we need to write it. + if (uuidFromFile === undefined) { + logger.debug(`Setting new Kibana instance UUID: ${uuidFromConfig}`); + } else { + logger.debug(`Updating Kibana instance UUID to: ${uuidFromConfig} (was: ${uuidFromFile})`); + } await writeUuidToFile(uuidFilePath, uuidFromConfig); return uuidFromConfig; } } if (uuidFromFile === undefined) { - // no uuid either in config or file, we need to generate and write it. const newUuid = uuid.v4(); + // no uuid either in config or file, we need to generate and write it. + logger.debug(`Setting new Kibana instance UUID: ${newUuid}`); await writeUuidToFile(uuidFilePath, newUuid); return newUuid; } + + logger.debug(`Resuming persistent Kibana instance UUID: ${uuidFromFile}`); return uuidFromFile; } diff --git a/src/core/server/uuid/uuid_service.test.ts b/src/core/server/uuid/uuid_service.test.ts index 69b69a31f938a..315df7af8aa19 100644 --- a/src/core/server/uuid/uuid_service.test.ts +++ b/src/core/server/uuid/uuid_service.test.ts @@ -44,18 +44,10 @@ describe('UuidService', () => { it('calls manageInstanceUuid with core configuration service', async () => { await service.setup(); expect(resolveInstanceUuid).toHaveBeenCalledTimes(1); - expect(resolveInstanceUuid).toHaveBeenCalledWith(coreContext.configService); - }); - - it('logs a message containing the UUID', async () => { - await service.setup(); - expect(loggingServiceMock.collect(logger).info).toMatchInlineSnapshot(` - Array [ - Array [ - "Kibana instance UUID: SOME_UUID", - ], - ] - `); + expect(resolveInstanceUuid).toHaveBeenCalledWith( + coreContext.configService, + logger.get('uuid') + ); }); it('returns the uuid resolved from manageInstanceUuid', async () => { diff --git a/src/core/server/uuid/uuid_service.ts b/src/core/server/uuid/uuid_service.ts index ed8442bb294d9..b5be5f2cd97a1 100644 --- a/src/core/server/uuid/uuid_service.ts +++ b/src/core/server/uuid/uuid_service.ts @@ -44,8 +44,7 @@ export class UuidService { } public async setup() { - this.uuid = await resolveInstanceUuid(this.configService); - this.log.info(`Kibana instance UUID: ${this.uuid}`); + this.uuid = await resolveInstanceUuid(this.configService, this.log); return { getInstanceUuid: () => this.uuid, From 8bcc718cf3aef42ad2ef70162ec98a1bb9e9bfff Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 13 Dec 2019 10:14:26 +0100 Subject: [PATCH 13/18] resolve merge conflicts --- src/core/server/legacy/legacy_service.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index 6816f046326c5..17ec1e9756432 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -45,7 +45,6 @@ import { capabilitiesServiceMock } from '../capabilities/capabilities_service.mo import { uuidServiceMock } from '../uuid/uuid_service.mock'; const MockKbnServer: jest.Mock = KbnServer as any; -const findLegacyPluginSpecsMock: jest.Mock = findLegacyPluginSpecs as any; let coreId: symbol; let env: Env; @@ -117,7 +116,6 @@ beforeEach(() => { afterEach(() => { jest.clearAllMocks(); - findLegacyPluginSpecsMock.mockReset(); }); describe('once LegacyService is set up with connection info', () => { From f4b11d843747daaa9994db448589cc3a6cbe12eb Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 13 Dec 2019 16:51:54 +0100 Subject: [PATCH 14/18] use fs.promises --- src/core/server/uuid/resolve_uuid.test.ts | 33 +++++++++++++---------- src/core/server/uuid/resolve_uuid.ts | 8 +++--- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/core/server/uuid/resolve_uuid.test.ts b/src/core/server/uuid/resolve_uuid.test.ts index aefb1e4fa3df5..e6d80316c5d0a 100644 --- a/src/core/server/uuid/resolve_uuid.test.ts +++ b/src/core/server/uuid/resolve_uuid.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { readFile, writeFile } from 'fs'; +import { promises } from 'fs'; import { join } from 'path'; import { resolveInstanceUuid } from './resolve_uuid'; import { configServiceMock } from '../config/config_service.mock'; @@ -25,15 +25,23 @@ import { loggingServiceMock } from '../logging/logging_service.mock'; import { BehaviorSubject } from 'rxjs'; import { Logger } from '../logging'; +const { readFile, writeFile } = promises; + jest.mock('uuid', () => ({ v4: () => 'NEW_UUID', })); -jest.mock('fs', () => ({ - ...jest.requireActual('fs'), - readFile: jest.fn((path, callback) => callback(null, Buffer.from(''))), - writeFile: jest.fn((path, value, options, callback) => callback(null, null)), -})); +jest.mock('fs', () => { + const actual = jest.requireActual('fs'); + return { + ...actual, + promises: { + ...actual.promises, + readFile: jest.fn(() => Promise.resolve('')), + writeFile: jest.fn(() => Promise.resolve('')), + }, + }; +}); const DEFAULT_FILE_UUID = 'FILE_UUID'; const DEFAULT_CONFIG_UUID = 'CONFIG_UUID'; @@ -48,9 +56,9 @@ const mockReadFile = ({ }>) => { ((readFile as unknown) as jest.Mock).mockImplementation((path, callback) => { if (error) { - callback(error, null); + return Promise.reject(error); } else { - callback(null, Buffer.from(uuid)); + return Promise.resolve(uuid); } }); }; @@ -91,8 +99,7 @@ describe('resolveInstanceUuid', () => { expect(writeFile).toHaveBeenCalledWith( join('data-folder', 'uuid'), DEFAULT_CONFIG_UUID, - expect.any(Object), - expect.any(Function) + expect.any(Object) ); expect(logger.debug).toHaveBeenCalledTimes(1); expect(logger.debug.mock.calls[0]).toMatchInlineSnapshot(` @@ -123,8 +130,7 @@ describe('resolveInstanceUuid', () => { expect(writeFile).toHaveBeenCalledWith( join('data-folder', 'uuid'), DEFAULT_CONFIG_UUID, - expect.any(Object), - expect.any(Function) + expect.any(Object) ); expect(logger.debug).toHaveBeenCalledTimes(1); expect(logger.debug.mock.calls[0]).toMatchInlineSnapshot(` @@ -159,8 +165,7 @@ describe('resolveInstanceUuid', () => { expect(writeFile).toHaveBeenCalledWith( join('data-folder', 'uuid'), 'NEW_UUID', - expect.any(Object), - expect.any(Function) + expect.any(Object) ); expect(logger.debug).toHaveBeenCalledTimes(1); expect(logger.debug.mock.calls[0]).toMatchInlineSnapshot(` diff --git a/src/core/server/uuid/resolve_uuid.ts b/src/core/server/uuid/resolve_uuid.ts index bc4d3d3e284c1..179e38756932b 100644 --- a/src/core/server/uuid/resolve_uuid.ts +++ b/src/core/server/uuid/resolve_uuid.ts @@ -18,8 +18,7 @@ */ import uuid from 'uuid'; -import { promisify } from 'util'; -import * as fs from 'fs'; +import { promises } from 'fs'; import { join } from 'path'; import { take } from 'rxjs/operators'; import { IConfigService } from '../config'; @@ -27,12 +26,11 @@ import { PathConfigType, config as pathConfigDef } from '../path'; import { HttpConfigType, config as httpConfigDef } from '../http'; import { Logger } from '../logging'; +const { readFile, writeFile } = promises; + const FILE_ENCODING = 'utf8'; const FILE_NAME = 'uuid'; -const readFile = promisify(fs.readFile); -const writeFile = promisify(fs.writeFile); - export async function resolveInstanceUuid( configService: IConfigService, logger: Logger From fa6ea9303b6e888a3911ae6f5c45237f3e63a229 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 13 Dec 2019 16:52:21 +0100 Subject: [PATCH 15/18] add forgotten @public in uuid contract --- src/core/server/server.api.md | 2 -- src/core/server/uuid/uuid_service.ts | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 1fac7f0c31eb6..0a412bef5d1dc 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1807,8 +1807,6 @@ export interface UserProvidedValues { userValue?: T; } -// Warning: (ae-missing-release-tag) "UuidServiceSetup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export interface UuidServiceSetup { getInstanceUuid(): string; diff --git a/src/core/server/uuid/uuid_service.ts b/src/core/server/uuid/uuid_service.ts index b5be5f2cd97a1..10104fa704936 100644 --- a/src/core/server/uuid/uuid_service.ts +++ b/src/core/server/uuid/uuid_service.ts @@ -24,6 +24,8 @@ import { IConfigService } from '../config'; /** * APIs to access the application's instance uuid. + * + * @public */ export interface UuidServiceSetup { /** From a6f59807fe863500447595425e7c6b93f505a37e Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 13 Dec 2019 17:30:54 +0100 Subject: [PATCH 16/18] add explicit errors and tests --- src/core/server/uuid/resolve_uuid.test.ts | 31 +++++++++++++++++++++++ src/core/server/uuid/resolve_uuid.ts | 16 ++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/core/server/uuid/resolve_uuid.test.ts b/src/core/server/uuid/resolve_uuid.test.ts index e6d80316c5d0a..4859ab2c89a70 100644 --- a/src/core/server/uuid/resolve_uuid.test.ts +++ b/src/core/server/uuid/resolve_uuid.test.ts @@ -46,6 +46,7 @@ jest.mock('fs', () => { const DEFAULT_FILE_UUID = 'FILE_UUID'; const DEFAULT_CONFIG_UUID = 'CONFIG_UUID'; const fileNotFoundError = { code: 'ENOENT' }; +const permissionError = { code: 'EACCES' }; const mockReadFile = ({ uuid = DEFAULT_FILE_UUID, @@ -63,6 +64,16 @@ const mockReadFile = ({ }); }; +const mockWriteFile = (error?: object) => { + ((writeFile as unknown) as jest.Mock).mockImplementation((path, callback) => { + if (error) { + return Promise.reject(error); + } else { + return Promise.resolve(); + } + }); +}; + const getConfigService = (serverUuid: string | undefined) => { const configService = configServiceMock.create(); configService.atPath.mockImplementation(path => { @@ -88,6 +99,7 @@ describe('resolveInstanceUuid', () => { beforeEach(() => { jest.clearAllMocks(); mockReadFile({ uuid: DEFAULT_FILE_UUID }); + mockWriteFile(); configService = getConfigService(DEFAULT_CONFIG_UUID); logger = loggingServiceMock.create().get() as any; }); @@ -175,4 +187,23 @@ describe('resolveInstanceUuid', () => { `); }); }); + + describe('when file access error occurs', () => { + it('throws an explicit error for file read errors', async () => { + mockReadFile({ error: permissionError }); + await expect( + resolveInstanceUuid(configService, logger) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unable to read Kibana UUID file, please check the uuid.server configurationvalue in kibana.yml and ensure Kibana has sufficient permissions to read / write to this file. Error was: EACCES"` + ); + }); + it('throws an explicit error for file write errors', async () => { + mockWriteFile({ error: permissionError }); + await expect( + resolveInstanceUuid(configService, logger) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unable to write Kibana UUID file, please check the uuid.server configurationvalue in kibana.yml and ensure Kibana has sufficient permissions to read / write to this file. Error was: undefined"` + ); + }); + }); }); diff --git a/src/core/server/uuid/resolve_uuid.ts b/src/core/server/uuid/resolve_uuid.ts index 179e38756932b..d6b8e3527c0af 100644 --- a/src/core/server/uuid/resolve_uuid.ts +++ b/src/core/server/uuid/resolve_uuid.ts @@ -88,10 +88,22 @@ async function readUuidFromFile(filepath: string): Promise { // non-existent uuid file is ok, we will create it. return undefined; } - throw e; + throw new Error( + 'Unable to read Kibana UUID file, please check the uuid.server configuration' + + 'value in kibana.yml and ensure Kibana has sufficient permissions to read / write to this file. ' + + `Error was: ${e.code}` + ); } } async function writeUuidToFile(filepath: string, uuidValue: string) { - return await writeFile(filepath, uuidValue, { encoding: FILE_ENCODING }); + try { + return await writeFile(filepath, uuidValue, { encoding: FILE_ENCODING }); + } catch (e) { + throw new Error( + 'Unable to write Kibana UUID file, please check the uuid.server configuration' + + 'value in kibana.yml and ensure Kibana has sufficient permissions to read / write to this file. ' + + `Error was: ${e.code}` + ); + } } From 83eb19f9382e5782c20ba68cdf44b461f731945f Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Fri, 13 Dec 2019 22:07:35 +0100 Subject: [PATCH 17/18] ensure uuid is valid in configuration --- src/core/server/http/http_config.test.ts | 9 +++++++++ src/core/server/http/http_config.ts | 7 ++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/core/server/http/http_config.test.ts b/src/core/server/http/http_config.test.ts index 1ee7e13d5e851..4725a48ed5370 100644 --- a/src/core/server/http/http_config.test.ts +++ b/src/core/server/http/http_config.test.ts @@ -17,6 +17,7 @@ * under the License. */ +import uuid from 'uuid'; import { config, HttpConfig } from '.'; import { Env } from '../config'; import { getEnvOptions } from '../config/__mocks__/env'; @@ -77,6 +78,14 @@ test('throws if basepath is not specified, but rewriteBasePath is set', () => { expect(() => httpSchema.validate(obj)).toThrowErrorMatchingSnapshot(); }); +test('accepts only valid uuids for server.uuid', () => { + const httpSchema = config.schema; + expect(() => httpSchema.validate({ uuid: uuid.v4() })).not.toThrow(); + expect(() => httpSchema.validate({ uuid: 'not an uuid' })).toThrowErrorMatchingInlineSnapshot( + `"[uuid]: must be a valid uuid"` + ); +}); + describe('with TLS', () => { test('throws if TLS is enabled but `key` is not specified', () => { const httpSchema = config.schema; diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index f8afe4da3b67d..b3b115e5563de 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -22,6 +22,7 @@ import { Env } from '../config'; import { SslConfig, sslSchema } from './ssl_config'; const validBasePathRegex = /(^$|^\/.*[^\/]$)/; +const uuidRegexp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i; const match = (regex: RegExp, errorMsg: string) => (str: string) => regex.test(str) ? undefined : errorMsg; @@ -91,7 +92,11 @@ export const config = { ) ), }), - uuid: schema.maybe(schema.string()), + uuid: schema.maybe( + schema.string({ + validate: match(uuidRegexp, 'must be a valid uuid'), + }) + ), }, { validate: rawConfig => { From 8767b7394dc6029932de69cd61f8dae9a87c28b9 Mon Sep 17 00:00:00 2001 From: pgayvallet Date: Mon, 16 Dec 2019 14:56:01 +0100 Subject: [PATCH 18/18] fix read/write tests --- src/core/server/uuid/resolve_uuid.test.ts | 11 ++++++----- src/core/server/uuid/resolve_uuid.ts | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/core/server/uuid/resolve_uuid.test.ts b/src/core/server/uuid/resolve_uuid.test.ts index 4859ab2c89a70..1ddd667eacdee 100644 --- a/src/core/server/uuid/resolve_uuid.test.ts +++ b/src/core/server/uuid/resolve_uuid.test.ts @@ -47,6 +47,7 @@ const DEFAULT_FILE_UUID = 'FILE_UUID'; const DEFAULT_CONFIG_UUID = 'CONFIG_UUID'; const fileNotFoundError = { code: 'ENOENT' }; const permissionError = { code: 'EACCES' }; +const isDirectoryError = { code: 'EISDIR' }; const mockReadFile = ({ uuid = DEFAULT_FILE_UUID, @@ -55,7 +56,7 @@ const mockReadFile = ({ uuid: string; error: any; }>) => { - ((readFile as unknown) as jest.Mock).mockImplementation((path, callback) => { + ((readFile as unknown) as jest.Mock).mockImplementation(() => { if (error) { return Promise.reject(error); } else { @@ -65,7 +66,7 @@ const mockReadFile = ({ }; const mockWriteFile = (error?: object) => { - ((writeFile as unknown) as jest.Mock).mockImplementation((path, callback) => { + ((writeFile as unknown) as jest.Mock).mockImplementation(() => { if (error) { return Promise.reject(error); } else { @@ -194,15 +195,15 @@ describe('resolveInstanceUuid', () => { await expect( resolveInstanceUuid(configService, logger) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unable to read Kibana UUID file, please check the uuid.server configurationvalue in kibana.yml and ensure Kibana has sufficient permissions to read / write to this file. Error was: EACCES"` + `"Unable to read Kibana UUID file, please check the uuid.server configuration value in kibana.yml and ensure Kibana has sufficient permissions to read / write to this file. Error was: EACCES"` ); }); it('throws an explicit error for file write errors', async () => { - mockWriteFile({ error: permissionError }); + mockWriteFile(isDirectoryError); await expect( resolveInstanceUuid(configService, logger) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unable to write Kibana UUID file, please check the uuid.server configurationvalue in kibana.yml and ensure Kibana has sufficient permissions to read / write to this file. Error was: undefined"` + `"Unable to write Kibana UUID file, please check the uuid.server configuration value in kibana.yml and ensure Kibana has sufficient permissions to read / write to this file. Error was: EISDIR"` ); }); }); diff --git a/src/core/server/uuid/resolve_uuid.ts b/src/core/server/uuid/resolve_uuid.ts index d6b8e3527c0af..17412bfa0544c 100644 --- a/src/core/server/uuid/resolve_uuid.ts +++ b/src/core/server/uuid/resolve_uuid.ts @@ -89,7 +89,7 @@ async function readUuidFromFile(filepath: string): Promise { return undefined; } throw new Error( - 'Unable to read Kibana UUID file, please check the uuid.server configuration' + + 'Unable to read Kibana UUID file, please check the uuid.server configuration ' + 'value in kibana.yml and ensure Kibana has sufficient permissions to read / write to this file. ' + `Error was: ${e.code}` ); @@ -101,7 +101,7 @@ async function writeUuidToFile(filepath: string, uuidValue: string) { return await writeFile(filepath, uuidValue, { encoding: FILE_ENCODING }); } catch (e) { throw new Error( - 'Unable to write Kibana UUID file, please check the uuid.server configuration' + + 'Unable to write Kibana UUID file, please check the uuid.server configuration ' + 'value in kibana.yml and ensure Kibana has sufficient permissions to read / write to this file. ' + `Error was: ${e.code}` );