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 e97ecbcfaf739..ea5ca6502b076 100644
--- a/docs/development/core/server/kibana-plugin-server.md
+++ b/docs/development/core/server/kibana-plugin-server.md
@@ -138,6 +138,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/http/http_config.test.ts b/src/core/server/http/http_config.test.ts
index 888313e1478cb..9b6fab8f3daec 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 912459c83df6e..ef6a9c0a5f1a5 100644
--- a/src/core/server/http/http_config.ts
+++ b/src/core/server/http/http_config.ts
@@ -23,6 +23,7 @@ import { CspConfigType, CspConfig, ICspConfig } from '../csp';
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;
@@ -92,6 +93,11 @@ export const config = {
)
),
}),
+ uuid: schema.maybe(
+ schema.string({
+ validate: match(uuidRegexp, 'must be a valid uuid'),
+ })
+ ),
},
{
validate: rawConfig => {
diff --git a/src/core/server/index.ts b/src/core/server/index.ts
index 835c5872d51a3..2aaa8306e871f 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';
@@ -269,6 +270,8 @@ export interface CoreSetup {
savedObjects: SavedObjectsServiceSetup;
/** {@link UiSettingsServiceSetup} */
uiSettings: UiSettingsServiceSetup;
+ /** {@link UuidServiceSetup} */
+ uuid: UuidServiceSetup;
}
/**
@@ -290,4 +293,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/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 75e1813f8c1f6..ffcbfda4e024d 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
@@ -73,6 +73,7 @@ export class LegacyObjectToConfigAdapter extends ObjectToConfigAdapter {
keepaliveTimeout: configValue.keepaliveTimeout,
socketTimeout: configValue.socketTimeout,
compression: configValue.compression,
+ uuid: configValue.uuid,
};
}
diff --git a/src/core/server/legacy/config/types.ts b/src/core/server/legacy/config/types.ts
index 24869e361c39c..cac1002d6c244 100644
--- a/src/core/server/legacy/config/types.ts
+++ b/src/core/server/legacy/config/types.ts
@@ -25,6 +25,8 @@
export interface LegacyConfig {
get(key?: string): T;
has(key: string): boolean;
+ set(key: string, value: any): void;
+ set(config: Record): void;
}
/**
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..ac0319cdf4eb5
--- /dev/null
+++ b/src/core/server/legacy/legacy_service.mock.ts
@@ -0,0 +1,39 @@
+/*
+ * 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: [],
+ disabledPluginSpecs: [],
+ uiExports: {} as any,
+ settings: {},
+ pluginExtendedConfig: {
+ get: jest.fn(),
+ has: jest.fn(),
+ set: jest.fn(),
+ } as any,
+ };
+ return setupContract;
+};
+
+export const legacyServiceMock = {
+ createDiscover: createDiscoverMock,
+};
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 d4360c577d24c..17ec1e9756432 100644
--- a/src/core/server/legacy/legacy_service.test.ts
+++ b/src/core/server/legacy/legacy_service.test.ts
@@ -17,36 +17,34 @@
* 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');
jest.mock('./config/legacy_deprecation_adapters', () => ({
convertLegacyDeprecationProvider: (provider: any) => Promise.resolve(provider),
}));
+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 { findLegacyPluginSpecs } from './plugins/find_legacy_plugin_specs';
+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';
import { capabilitiesServiceMock } from '../capabilities/capabilities_service.mock';
-import { findLegacyPluginSpecs } from './plugins/find_legacy_plugin_specs';
+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;
@@ -58,23 +56,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.mockImplementation(
- settings =>
- Promise.resolve({
- pluginSpecs: [],
- pluginExtendedConfig: settings,
- disabledPluginSpecs: [],
- uiExports: [],
- }) as any
- );
-
+ findLegacyPluginSpecsMock.mockClear();
MockKbnServer.prototype.ready = jest.fn().mockReturnValue(Promise.resolve());
+ MockKbnServer.prototype.listen = jest.fn();
setupDeps = {
core: {
@@ -97,6 +89,7 @@ beforeEach(() => {
browserConfigs: new Map(),
},
},
+ uuid: uuidSetup,
},
plugins: { 'plugin-id': 'plugin-value' },
};
@@ -123,7 +116,6 @@ beforeEach(() => {
afterEach(() => {
jest.clearAllMocks();
- findLegacyPluginSpecsMock.mockReset();
});
describe('once LegacyService is set up with connection info', () => {
@@ -142,11 +134,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);
@@ -169,10 +165,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);
@@ -306,10 +306,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 () => {
@@ -440,3 +444,34 @@ describe('#discoverPlugins()', () => {
expect(configService.addDeprecationProvider).toHaveBeenCalledWith('', 'providerB');
});
});
+
+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 662cc0bdf2f3a..1bba38433d7f4 100644
--- a/src/core/server/legacy/legacy_service.ts
+++ b/src/core/server/legacy/legacy_service.ts
@@ -200,6 +200,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;
}
@@ -300,6 +303,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 07b60e771d643..3a68b18409b0a 100644
--- a/src/core/server/mocks.ts
+++ b/src/core/server/mocks.ts
@@ -38,6 +38,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 = {
@@ -110,6 +111,7 @@ function createCoreSetupMock() {
http: httpMock,
savedObjects: savedObjectsServiceMock.createSetupContract(),
uiSettings: uiSettingsMock,
+ uuid: uuidServiceMock.createSetupContract(),
};
return mock;
@@ -132,6 +134,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 26c65baf95535..04a7547fd3747 100644
--- a/src/core/server/plugins/plugin_context.ts
+++ b/src/core/server/plugins/plugin_context.ts
@@ -173,6 +173,9 @@ export function createPluginSetupContext(
uiSettings: {
register: deps.uiSettings.register,
},
+ uuid: {
+ getInstanceUuid: deps.uuid.getInstanceUuid,
+ },
};
}
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/core/server/server.api.md b/src/core/server/server.api.md
index 780a1532a859e..4e6493a17aea1 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -564,6 +564,8 @@ export interface CoreSetup {
savedObjects: SavedObjectsServiceSetup;
// (undocumented)
uiSettings: UiSettingsServiceSetup;
+ // (undocumented)
+ uuid: UuidServiceSetup;
}
// @public
@@ -1831,6 +1833,11 @@ export interface UserProvidedValues {
userValue?: T;
}
+// @public
+export interface UuidServiceSetup {
+ getInstanceUuid(): string;
+}
+
// @public
export const validBodyOutput: readonly ["data", "stream"];
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 725a45f131992..89d99d6c4a4ec 100644
--- a/src/core/server/server.ts
+++ b/src/core/server/server.ts
@@ -49,6 +49,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');
const rootConfigPath = '';
@@ -64,6 +65,7 @@ export class Server {
private readonly plugins: PluginsService;
private readonly savedObjects: SavedObjectsService;
private readonly uiSettings: UiSettingsService;
+ private readonly uuid: UuidService;
constructor(
rawConfigProvider: RawConfigurationProvider,
@@ -82,6 +84,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() {
@@ -106,6 +109,8 @@ export class Server {
]),
});
+ const uuidSetup = await this.uuid.setup();
+
const httpSetup = await this.http.setup({
context: contextServiceSetup,
});
@@ -134,6 +139,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/resolve_uuid.test.ts b/src/core/server/uuid/resolve_uuid.test.ts
new file mode 100644
index 0000000000000..1ddd667eacdee
--- /dev/null
+++ b/src/core/server/uuid/resolve_uuid.test.ts
@@ -0,0 +1,210 @@
+/*
+ * 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 { promises } 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';
+
+const { readFile, writeFile } = promises;
+
+jest.mock('uuid', () => ({
+ v4: () => 'NEW_UUID',
+}));
+
+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';
+const fileNotFoundError = { code: 'ENOENT' };
+const permissionError = { code: 'EACCES' };
+const isDirectoryError = { code: 'EISDIR' };
+
+const mockReadFile = ({
+ uuid = DEFAULT_FILE_UUID,
+ error = null,
+}: Partial<{
+ uuid: string;
+ error: any;
+}>) => {
+ ((readFile as unknown) as jest.Mock).mockImplementation(() => {
+ if (error) {
+ return Promise.reject(error);
+ } else {
+ return Promise.resolve(uuid);
+ }
+ });
+};
+
+const mockWriteFile = (error?: object) => {
+ ((writeFile as unknown) as jest.Mock).mockImplementation(() => {
+ if (error) {
+ return Promise.reject(error);
+ } else {
+ return Promise.resolve();
+ }
+ });
+};
+
+const getConfigService = (serverUuid: string | undefined) => {
+ const configService = configServiceMock.create();
+ configService.atPath.mockImplementation(path => {
+ if (path === 'path') {
+ return new BehaviorSubject({
+ data: 'data-folder',
+ });
+ }
+ if (path === 'server') {
+ return new BehaviorSubject({
+ uuid: serverUuid,
+ });
+ }
+ return new BehaviorSubject({});
+ });
+ return configService;
+};
+
+describe('resolveInstanceUuid', () => {
+ let configService: ReturnType;
+ let logger: jest.Mocked;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockReadFile({ uuid: DEFAULT_FILE_UUID });
+ mockWriteFile();
+ 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, logger);
+ expect(uuid).toEqual(DEFAULT_CONFIG_UUID);
+ expect(writeFile).toHaveBeenCalledWith(
+ join('data-folder', 'uuid'),
+ DEFAULT_CONFIG_UUID,
+ expect.any(Object)
+ );
+ 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, 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, logger);
+ expect(uuid).toEqual(DEFAULT_CONFIG_UUID);
+ expect(writeFile).toHaveBeenCalledWith(
+ join('data-folder', 'uuid'),
+ DEFAULT_CONFIG_UUID,
+ expect.any(Object)
+ );
+ 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, 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",
+ ]
+ `);
+ });
+ });
+
+ 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 resolveInstanceUuid(configService, logger);
+ expect(uuid).toEqual('NEW_UUID');
+ expect(writeFile).toHaveBeenCalledWith(
+ join('data-folder', 'uuid'),
+ 'NEW_UUID',
+ expect.any(Object)
+ );
+ expect(logger.debug).toHaveBeenCalledTimes(1);
+ expect(logger.debug.mock.calls[0]).toMatchInlineSnapshot(`
+ Array [
+ "Setting new Kibana instance UUID: NEW_UUID",
+ ]
+ `);
+ });
+ });
+
+ 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 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(isDirectoryError);
+ await expect(
+ resolveInstanceUuid(configService, logger)
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
+ `"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
new file mode 100644
index 0000000000000..17412bfa0544c
--- /dev/null
+++ b/src/core/server/uuid/resolve_uuid.ts
@@ -0,0 +1,109 @@
+/*
+ * 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 { promises } from 'fs';
+import { join } from 'path';
+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 { readFile, writeFile } = promises;
+
+const FILE_ENCODING = 'utf8';
+const FILE_NAME = 'uuid';
+
+export async function resolveInstanceUuid(
+ configService: IConfigService,
+ logger: Logger
+): Promise {
+ const [pathConfig, serverConfig] = await Promise.all([
+ configService
+ .atPath(pathConfigDef.path)
+ .pipe(take(1))
+ .toPromise(),
+ configService
+ .atPath(httpConfigDef.path)
+ .pipe(take(1))
+ .toPromise(),
+ ]);
+
+ const uuidFilePath = join(pathConfig.data, FILE_NAME);
+
+ const uuidFromFile = await readUuidFromFile(uuidFilePath);
+ const uuidFromConfig = serverConfig.uuid;
+
+ 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) {
+ 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;
+}
+
+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 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) {
+ 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}`
+ );
+ }
+}
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..315df7af8aa19
--- /dev/null
+++ b/src/core/server/uuid/uuid_service.test.ts
@@ -0,0 +1,58 @@
+/*
+ * 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 { resolveInstanceUuid } from './resolve_uuid';
+import { CoreContext } from '../core_context';
+
+import { loggingServiceMock } from '../logging/logging_service.mock';
+import { mockCoreContext } from '../core_context.mock';
+
+jest.mock('./resolve_uuid', () => ({
+ resolveInstanceUuid: 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('calls manageInstanceUuid with core configuration service', async () => {
+ await service.setup();
+ expect(resolveInstanceUuid).toHaveBeenCalledTimes(1);
+ expect(resolveInstanceUuid).toHaveBeenCalledWith(
+ coreContext.configService,
+ logger.get('uuid')
+ );
+ });
+
+ it('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..10104fa704936
--- /dev/null
+++ b/src/core/server/uuid/uuid_service.ts
@@ -0,0 +1,55 @@
+/*
+ * 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 { resolveInstanceUuid } from './resolve_uuid';
+import { CoreContext } from '../core_context';
+import { Logger } from '../logging';
+import { IConfigService } from '../config';
+
+/**
+ * APIs to access the application's instance uuid.
+ *
+ * @public
+ */
+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 resolveInstanceUuid(this.configService, this.log);
+
+ return {
+ getInstanceUuid: () => this.uuid,
+ };
+ }
+}
diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js
index 93167cb0e6bf9..8dc470e20c619 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';
@@ -326,8 +325,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 852e75876ad15..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 adae8517287d6..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);
-}
diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js
index d52ac6845d99e..f886fd598f5c9 100644
--- a/src/legacy/server/config/schema.js
+++ b/src/legacy/server/config/schema.js
@@ -69,9 +69,6 @@ export default () =>
}),
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()
@@ -111,6 +108,7 @@ export default () =>
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,
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;