mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* move and migrate uuid code to new platform * create and wire uuid service * handle legacy compatibility * update generated docs * add `set` to LegacyConfig interface * Fix types * fix config access * respect naming conventions for uuid * remove hardcoded config paths * rename manageInstanceUuid to resolveInstanceUuid * moves legacy config uuid set from uuid to legacy service * log specific message depending on how uuid was resolved * resolve merge conflicts * use fs.promises * add forgotten @public in uuid contract * add explicit errors and tests * ensure uuid is valid in configuration * fix read/write tests
This commit is contained in:
parent
e69183d9f6
commit
805179726f
33 changed files with 742 additions and 233 deletions
|
@ -22,4 +22,5 @@ export interface CoreSetup
|
|||
| [http](./kibana-plugin-server.coresetup.http.md) | <code>HttpServiceSetup</code> | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) |
|
||||
| [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md) | <code>SavedObjectsServiceSetup</code> | [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) |
|
||||
| [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) | <code>UiSettingsServiceSetup</code> | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) |
|
||||
| [uuid](./kibana-plugin-server.coresetup.uuid.md) | <code>UuidServiceSetup</code> | [UuidServiceSetup](./kibana-plugin-server.uuidservicesetup.md) |
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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)
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
uuid: UuidServiceSetup;
|
||||
```
|
|
@ -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
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
getInstanceUuid(): string;
|
||||
```
|
||||
<b>Returns:</b>
|
||||
|
||||
`string`
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[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.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface UuidServiceSetup
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| [getInstanceUuid()](./kibana-plugin-server.uuidservicesetup.getinstanceuuid.md) | Retrieve the Kibana instance uuid. |
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,6 +20,7 @@ Object {
|
|||
"keyPassphrase": "some-phrase",
|
||||
"someNewValue": "new",
|
||||
},
|
||||
"uuid": undefined,
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -43,6 +44,7 @@ Object {
|
|||
"enabled": false,
|
||||
"key": "key",
|
||||
},
|
||||
"uuid": undefined,
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
|
@ -73,6 +73,7 @@ export class LegacyObjectToConfigAdapter extends ObjectToConfigAdapter {
|
|||
keepaliveTimeout: configValue.keepaliveTimeout,
|
||||
socketTimeout: configValue.socketTimeout,
|
||||
compression: configValue.compression,
|
||||
uuid: configValue.uuid,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
export interface LegacyConfig {
|
||||
get<T>(key?: string): T;
|
||||
has(key: string): boolean;
|
||||
set(key: string, value: any): void;
|
||||
set(config: Record<string, any>): void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
39
src/core/server/legacy/legacy_service.mock.ts
Normal file
39
src/core/server/legacy/legacy_service.mock.ts
Normal file
|
@ -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<LegacyServiceDiscoverPlugins> = {
|
||||
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,
|
||||
};
|
34
src/core/server/legacy/legacy_service.test.mocks.ts
Normal file
34
src/core/server/legacy/legacy_service.test.mocks.ts
Normal file
|
@ -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<string, any>) => ({
|
||||
pluginSpecs: [],
|
||||
pluginExtendedConfig: {
|
||||
has: jest.fn(),
|
||||
get: jest.fn(() => settings),
|
||||
set: jest.fn(),
|
||||
},
|
||||
disabledPluginSpecs: [],
|
||||
uiExports: [],
|
||||
}));
|
||||
jest.doMock('./plugins/find_legacy_plugin_specs.ts', () => ({
|
||||
findLegacyPluginSpecs: findLegacyPluginSpecsMock,
|
||||
}));
|
|
@ -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> = KbnServer as any;
|
||||
const findLegacyPluginSpecsMock: jest.Mock<typeof findLegacyPluginSpecs> = findLegacyPluginSpecs as any;
|
||||
|
||||
let coreId: symbol;
|
||||
let env: Env;
|
||||
|
@ -58,23 +56,17 @@ let startDeps: LegacyServiceStartDeps;
|
|||
|
||||
const logger = loggingServiceMock.create();
|
||||
let configService: ReturnType<typeof configServiceMock.create>;
|
||||
let uuidSetup: ReturnType<typeof uuidServiceMock.createSetupContract>;
|
||||
|
||||
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<string, any>) => ({
|
||||
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');
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<T>(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;
|
||||
}
|
||||
|
|
|
@ -173,6 +173,9 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
|
|||
uiSettings: {
|
||||
register: deps.uiSettings.register,
|
||||
},
|
||||
uuid: {
|
||||
getInstanceUuid: deps.uuid.getInstanceUuid,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -564,6 +564,8 @@ export interface CoreSetup {
|
|||
savedObjects: SavedObjectsServiceSetup;
|
||||
// (undocumented)
|
||||
uiSettings: UiSettingsServiceSetup;
|
||||
// (undocumented)
|
||||
uuid: UuidServiceSetup;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
@ -1831,6 +1833,11 @@ export interface UserProvidedValues<T = any> {
|
|||
userValue?: T;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface UuidServiceSetup {
|
||||
getInstanceUuid(): string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const validBodyOutput: readonly ["data", "stream"];
|
||||
|
||||
|
|
|
@ -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),
|
||||
}));
|
||||
|
|
|
@ -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);
|
||||
|
|
20
src/core/server/uuid/index.ts
Normal file
20
src/core/server/uuid/index.ts
Normal file
|
@ -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';
|
210
src/core/server/uuid/resolve_uuid.test.ts
Normal file
210
src/core/server/uuid/resolve_uuid.test.ts
Normal file
|
@ -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<typeof configServiceMock.create>;
|
||||
let logger: jest.Mocked<Logger>;
|
||||
|
||||
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"`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
109
src/core/server/uuid/resolve_uuid.ts
Normal file
109
src/core/server/uuid/resolve_uuid.ts
Normal file
|
@ -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<string> {
|
||||
const [pathConfig, serverConfig] = await Promise.all([
|
||||
configService
|
||||
.atPath<PathConfigType>(pathConfigDef.path)
|
||||
.pipe(take(1))
|
||||
.toPromise(),
|
||||
configService
|
||||
.atPath<HttpConfigType>(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<string | undefined> {
|
||||
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}`
|
||||
);
|
||||
}
|
||||
}
|
41
src/core/server/uuid/uuid_service.mock.ts
Normal file
41
src/core/server/uuid/uuid_service.mock.ts
Normal file
|
@ -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<UuidServiceSetup> = {
|
||||
getInstanceUuid: jest.fn().mockImplementation(() => 'uuid'),
|
||||
};
|
||||
return setupContract;
|
||||
};
|
||||
|
||||
type UuidServiceContract = PublicMethodsOf<UuidService>;
|
||||
const createMock = () => {
|
||||
const mocked: jest.Mocked<UuidServiceContract> = {
|
||||
setup: jest.fn(),
|
||||
};
|
||||
mocked.setup.mockResolvedValue(createSetupContractMock());
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const uuidServiceMock = {
|
||||
create: createMock,
|
||||
createSetupContract: createSetupContractMock,
|
||||
};
|
58
src/core/server/uuid/uuid_service.test.ts
Normal file
58
src/core/server/uuid/uuid_service.test.ts
Normal file
|
@ -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<typeof loggingServiceMock.create>;
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
55
src/core/server/uuid/uuid_service.ts
Normal file
55
src/core/server/uuid/uuid_service.ts
Normal file
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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}`));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
|
@ -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,
|
||||
|
|
3
src/legacy/server/kbn_server.d.ts
vendored
3
src/legacy/server/kbn_server.d.ts
vendored
|
@ -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<LegacyConfig, 'get' | 'has'>;
|
||||
|
||||
export interface UiApp {
|
||||
getId(): string;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue