Migrate data folder creation from legacy to KP (#75527)

* rename uuid service to environment service

* adapt resolve_uuid to directly use the configurations

* move data folder creation to core

* update generated doc

* fix types

* fix monitoring tests

* move instanceUuid to plugin initializer context

* update generated doc
This commit is contained in:
Pierre Gayvallet 2020-08-26 21:40:03 +02:00 committed by GitHub
parent 61550b7ce0
commit eee139295d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 504 additions and 252 deletions

View file

@ -26,5 +26,4 @@ export interface CoreSetup<TPluginsStart extends object = object, TStart = unkno
| [savedObjects](./kibana-plugin-core-server.coresetup.savedobjects.md) | <code>SavedObjectsServiceSetup</code> | [SavedObjectsServiceSetup](./kibana-plugin-core-server.savedobjectsservicesetup.md) |
| [status](./kibana-plugin-core-server.coresetup.status.md) | <code>StatusServiceSetup</code> | [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) |
| [uiSettings](./kibana-plugin-core-server.coresetup.uisettings.md) | <code>UiSettingsServiceSetup</code> | [UiSettingsServiceSetup](./kibana-plugin-core-server.uisettingsservicesetup.md) |
| [uuid](./kibana-plugin-core-server.coresetup.uuid.md) | <code>UuidServiceSetup</code> | [UuidServiceSetup](./kibana-plugin-core-server.uuidservicesetup.md) |

View file

@ -1,13 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [CoreSetup](./kibana-plugin-core-server.coresetup.md) &gt; [uuid](./kibana-plugin-core-server.coresetup.uuid.md)
## CoreSetup.uuid property
[UuidServiceSetup](./kibana-plugin-core-server.uuidservicesetup.md)
<b>Signature:</b>
```typescript
uuid: UuidServiceSetup;
```

View file

@ -214,7 +214,6 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [UiSettingsServiceStart](./kibana-plugin-core-server.uisettingsservicestart.md) | |
| [URLMeaningfulParts](./kibana-plugin-core-server.urlmeaningfulparts.md) | We define our own typings because the current version of @<!-- -->types/node declares properties to be optional "hostname?: string". Although, parse call returns "hostname: null \| string". |
| [UserProvidedValues](./kibana-plugin-core-server.userprovidedvalues.md) | Describes the values explicitly set by user. |
| [UuidServiceSetup](./kibana-plugin-core-server.uuidservicesetup.md) | APIs to access the application's instance uuid. |
## Variables

View file

@ -10,5 +10,6 @@
env: {
mode: EnvironmentMode;
packageInfo: Readonly<PackageInfo>;
instanceUuid: string;
};
```

View file

@ -17,7 +17,7 @@ export interface PluginInitializerContext<ConfigSchema = unknown>
| Property | Type | Description |
| --- | --- | --- |
| [config](./kibana-plugin-core-server.plugininitializercontext.config.md) | <code>{</code><br/><code> legacy: {</code><br/><code> globalConfig$: Observable&lt;SharedGlobalConfig&gt;;</code><br/><code> };</code><br/><code> create: &lt;T = ConfigSchema&gt;() =&gt; Observable&lt;T&gt;;</code><br/><code> createIfExists: &lt;T = ConfigSchema&gt;() =&gt; Observable&lt;T &#124; undefined&gt;;</code><br/><code> }</code> | |
| [env](./kibana-plugin-core-server.plugininitializercontext.env.md) | <code>{</code><br/><code> mode: EnvironmentMode;</code><br/><code> packageInfo: Readonly&lt;PackageInfo&gt;;</code><br/><code> }</code> | |
| [env](./kibana-plugin-core-server.plugininitializercontext.env.md) | <code>{</code><br/><code> mode: EnvironmentMode;</code><br/><code> packageInfo: Readonly&lt;PackageInfo&gt;;</code><br/><code> instanceUuid: string;</code><br/><code> }</code> | |
| [logger](./kibana-plugin-core-server.plugininitializercontext.logger.md) | <code>LoggerFactory</code> | |
| [opaqueId](./kibana-plugin-core-server.plugininitializercontext.opaqueid.md) | <code>PluginOpaqueId</code> | |

View file

@ -1,17 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [UuidServiceSetup](./kibana-plugin-core-server.uuidservicesetup.md) &gt; [getInstanceUuid](./kibana-plugin-core-server.uuidservicesetup.getinstanceuuid.md)
## UuidServiceSetup.getInstanceUuid() method
Retrieve the Kibana instance uuid.
<b>Signature:</b>
```typescript
getInstanceUuid(): string;
```
<b>Returns:</b>
`string`

View file

@ -1,20 +0,0 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [UuidServiceSetup](./kibana-plugin-core-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-core-server.uuidservicesetup.getinstanceuuid.md) | Retrieve the Kibana instance uuid. |

View file

@ -0,0 +1,79 @@
/*
* 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 { PathConfigType } from '../path';
import { createDataFolder } from './create_data_folder';
import { mkdir } from './fs';
import { loggingSystemMock } from '../logging/logging_system.mock';
jest.mock('./fs', () => ({
mkdir: jest.fn(() => Promise.resolve('')),
}));
const mkdirMock = mkdir as jest.Mock;
describe('createDataFolder', () => {
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
let pathConfig: PathConfigType;
beforeEach(() => {
logger = loggingSystemMock.createLogger();
pathConfig = {
data: '/path/to/data/folder',
};
mkdirMock.mockResolvedValue(undefined);
});
afterEach(() => {
jest.clearAllMocks();
});
it('calls `mkdir` with the correct parameters', async () => {
await createDataFolder({ pathConfig, logger });
expect(mkdirMock).toHaveBeenCalledTimes(1);
expect(mkdirMock).toHaveBeenCalledWith(pathConfig.data, { recursive: true });
});
it('does not log error if the `mkdir` call is successful', async () => {
await createDataFolder({ pathConfig, logger });
expect(logger.error).not.toHaveBeenCalled();
});
it('throws an error if the `mkdir` call fails', async () => {
mkdirMock.mockRejectedValue('some-error');
await expect(() => createDataFolder({ pathConfig, logger })).rejects.toMatchInlineSnapshot(
`"some-error"`
);
});
it('logs an error message if the `mkdir` call fails', async () => {
mkdirMock.mockRejectedValue('some-error');
try {
await createDataFolder({ pathConfig, logger });
} catch (e) {
/* trap */
}
expect(logger.error).toHaveBeenCalledTimes(1);
expect(logger.error.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"Error trying to create data folder at /path/to/data/folder: some-error",
]
`);
});
});

View file

@ -0,0 +1,40 @@
/*
* 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 { mkdir } from './fs';
import { Logger } from '../logging';
import { PathConfigType } from '../path';
export async function createDataFolder({
pathConfig,
logger,
}: {
pathConfig: PathConfigType;
logger: Logger;
}): Promise<void> {
const dataFolder = pathConfig.data;
try {
// Create the data directory (recursively, if the a parent dir doesn't exist).
// If it already exists, does nothing.
await mkdir(dataFolder, { recursive: true });
} catch (e) {
logger.error(`Error trying to create data folder at ${dataFolder}: ${e}`);
throw e;
}
}

View file

@ -17,25 +17,25 @@
* under the License.
*/
import { UuidService, UuidServiceSetup } from './uuid_service';
import { EnvironmentService, InternalEnvironmentServiceSetup } from './environment_service';
const createSetupContractMock = () => {
const setupContract: jest.Mocked<UuidServiceSetup> = {
getInstanceUuid: jest.fn().mockImplementation(() => 'uuid'),
const setupContract: jest.Mocked<InternalEnvironmentServiceSetup> = {
instanceUuid: 'uuid',
};
return setupContract;
};
type UuidServiceContract = PublicMethodsOf<UuidService>;
type EnvironmentServiceContract = PublicMethodsOf<EnvironmentService>;
const createMock = () => {
const mocked: jest.Mocked<UuidServiceContract> = {
const mocked: jest.Mocked<EnvironmentServiceContract> = {
setup: jest.fn(),
};
mocked.setup.mockResolvedValue(createSetupContractMock());
return mocked;
};
export const uuidServiceMock = {
export const environmentServiceMock = {
create: createMock,
createSetupContract: createSetupContractMock,
};

View file

@ -17,10 +17,13 @@
* under the License.
*/
import { UuidService } from './uuid_service';
import { BehaviorSubject } from 'rxjs';
import { EnvironmentService } from './environment_service';
import { resolveInstanceUuid } from './resolve_uuid';
import { createDataFolder } from './create_data_folder';
import { CoreContext } from '../core_context';
import { configServiceMock } from '../config/config_service.mock';
import { loggingSystemMock } from '../logging/logging_system.mock';
import { mockCoreContext } from '../core_context.mock';
@ -28,31 +31,69 @@ jest.mock('./resolve_uuid', () => ({
resolveInstanceUuid: jest.fn().mockResolvedValue('SOME_UUID'),
}));
jest.mock('./create_data_folder', () => ({
createDataFolder: jest.fn(),
}));
const pathConfig = {
data: 'data-folder',
};
const serverConfig = {
uuid: 'SOME_UUID',
};
const getConfigService = () => {
const configService = configServiceMock.create();
configService.atPath.mockImplementation((path) => {
if (path === 'path') {
return new BehaviorSubject(pathConfig);
}
if (path === 'server') {
return new BehaviorSubject(serverConfig);
}
return new BehaviorSubject({});
});
return configService;
};
describe('UuidService', () => {
let logger: ReturnType<typeof loggingSystemMock.create>;
let configService: ReturnType<typeof configServiceMock.create>;
let coreContext: CoreContext;
beforeEach(() => {
jest.clearAllMocks();
logger = loggingSystemMock.create();
coreContext = mockCoreContext.create({ logger });
configService = getConfigService();
coreContext = mockCoreContext.create({ logger, configService });
});
describe('#setup()', () => {
it('calls resolveInstanceUuid with core configuration service', async () => {
const service = new UuidService(coreContext);
it('calls resolveInstanceUuid with correct parameters', async () => {
const service = new EnvironmentService(coreContext);
await service.setup();
expect(resolveInstanceUuid).toHaveBeenCalledTimes(1);
expect(resolveInstanceUuid).toHaveBeenCalledWith({
configService: coreContext.configService,
pathConfig,
serverConfig,
logger: logger.get('uuid'),
});
});
it('calls createDataFolder with correct parameters', async () => {
const service = new EnvironmentService(coreContext);
await service.setup();
expect(createDataFolder).toHaveBeenCalledTimes(1);
expect(createDataFolder).toHaveBeenCalledWith({
pathConfig,
logger: logger.get('uuid'),
});
});
it('returns the uuid resolved from resolveInstanceUuid', async () => {
const service = new UuidService(coreContext);
const service = new EnvironmentService(coreContext);
const setup = await service.setup();
expect(setup.getInstanceUuid()).toEqual('SOME_UUID');
expect(setup.instanceUuid).toEqual('SOME_UUID');
});
});
});

View file

@ -17,25 +17,27 @@
* under the License.
*/
import { resolveInstanceUuid } from './resolve_uuid';
import { take } from 'rxjs/operators';
import { CoreContext } from '../core_context';
import { Logger } from '../logging';
import { IConfigService } from '../config';
import { PathConfigType, config as pathConfigDef } from '../path';
import { HttpConfigType, config as httpConfigDef } from '../http';
import { resolveInstanceUuid } from './resolve_uuid';
import { createDataFolder } from './create_data_folder';
/**
* APIs to access the application's instance uuid.
*
* @public
* @internal
*/
export interface UuidServiceSetup {
export interface InternalEnvironmentServiceSetup {
/**
* Retrieve the Kibana instance uuid.
*/
getInstanceUuid(): string;
instanceUuid: string;
}
/** @internal */
export class UuidService {
export class EnvironmentService {
private readonly log: Logger;
private readonly configService: IConfigService;
private uuid: string = '';
@ -46,13 +48,21 @@ export class UuidService {
}
public async setup() {
const [pathConfig, serverConfig] = await Promise.all([
this.configService.atPath<PathConfigType>(pathConfigDef.path).pipe(take(1)).toPromise(),
this.configService.atPath<HttpConfigType>(httpConfigDef.path).pipe(take(1)).toPromise(),
]);
await createDataFolder({ pathConfig, logger: this.log });
this.uuid = await resolveInstanceUuid({
configService: this.configService,
pathConfig,
serverConfig,
logger: this.log,
});
return {
getInstanceUuid: () => this.uuid,
instanceUuid: this.uuid,
};
}
}

View file

@ -22,3 +22,4 @@ import { promisify } from 'util';
export const readFile = promisify(Fs.readFile);
export const writeFile = promisify(Fs.writeFile);
export const mkdir = promisify(Fs.mkdir);

View file

@ -17,4 +17,4 @@
* under the License.
*/
export { UuidService, UuidServiceSetup } from './uuid_service';
export { EnvironmentService, InternalEnvironmentServiceSetup } from './environment_service';

View file

@ -18,12 +18,11 @@
*/
import { join } from 'path';
import { loggingSystemMock } from '../logging/logging_system.mock';
import { readFile, writeFile } from './fs';
import { resolveInstanceUuid, UUID_7_6_0_BUG } from './resolve_uuid';
import { configServiceMock } from '../config/config_service.mock';
import { loggingSystemMock } from '../logging/logging_system.mock';
import { BehaviorSubject } from 'rxjs';
import { Logger } from '../logging';
import { PathConfigType } from '../path';
import { HttpConfigType } from '../http';
jest.mock('uuid', () => ({
v4: () => 'NEW_UUID',
@ -66,40 +65,34 @@ const mockWriteFile = (error?: object) => {
});
};
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;
const createServerConfig = (serverUuid: string | undefined) => {
return {
uuid: serverUuid,
} as HttpConfigType;
};
describe('resolveInstanceUuid', () => {
let configService: ReturnType<typeof configServiceMock.create>;
let logger: jest.Mocked<Logger>;
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
let pathConfig: PathConfigType;
let serverConfig: HttpConfigType;
beforeEach(() => {
jest.clearAllMocks();
mockReadFile({ uuid: DEFAULT_FILE_UUID });
mockWriteFile();
configService = getConfigService(DEFAULT_CONFIG_UUID);
logger = loggingSystemMock.create().get() as any;
pathConfig = {
data: 'data-folder',
};
serverConfig = createServerConfig(DEFAULT_CONFIG_UUID);
logger = loggingSystemMock.createLogger();
});
describe('when file is present and config property is set', () => {
describe('when they mismatch', () => {
it('writes to file and returns the config uuid', async () => {
const uuid = await resolveInstanceUuid({ configService, logger });
const uuid = await resolveInstanceUuid({ pathConfig, serverConfig, logger });
expect(uuid).toEqual(DEFAULT_CONFIG_UUID);
expect(writeFile).toHaveBeenCalledWith(
join('data-folder', 'uuid'),
@ -118,7 +111,7 @@ describe('resolveInstanceUuid', () => {
describe('when they match', () => {
it('does not write to file', async () => {
mockReadFile({ uuid: DEFAULT_CONFIG_UUID });
const uuid = await resolveInstanceUuid({ configService, logger });
const uuid = await resolveInstanceUuid({ pathConfig, serverConfig, logger });
expect(uuid).toEqual(DEFAULT_CONFIG_UUID);
expect(writeFile).not.toHaveBeenCalled();
expect(logger.debug).toHaveBeenCalledTimes(1);
@ -134,7 +127,7 @@ describe('resolveInstanceUuid', () => {
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 });
const uuid = await resolveInstanceUuid({ pathConfig, serverConfig, logger });
expect(uuid).toEqual(DEFAULT_CONFIG_UUID);
expect(writeFile).toHaveBeenCalledWith(
join('data-folder', 'uuid'),
@ -152,8 +145,8 @@ describe('resolveInstanceUuid', () => {
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 });
serverConfig = createServerConfig(undefined);
const uuid = await resolveInstanceUuid({ pathConfig, serverConfig, logger });
expect(uuid).toEqual(DEFAULT_FILE_UUID);
expect(writeFile).not.toHaveBeenCalled();
expect(logger.debug).toHaveBeenCalledTimes(1);
@ -169,8 +162,8 @@ describe('resolveInstanceUuid', () => {
describe('when config property is not set', () => {
it('writes new uuid to file and returns new uuid', async () => {
mockReadFile({ uuid: UUID_7_6_0_BUG });
configService = getConfigService(undefined);
const uuid = await resolveInstanceUuid({ configService, logger });
serverConfig = createServerConfig(undefined);
const uuid = await resolveInstanceUuid({ pathConfig, serverConfig, logger });
expect(uuid).not.toEqual(UUID_7_6_0_BUG);
expect(uuid).toEqual('NEW_UUID');
expect(writeFile).toHaveBeenCalledWith(
@ -195,8 +188,8 @@ describe('resolveInstanceUuid', () => {
describe('when config property is set', () => {
it('writes config uuid to file and returns config uuid', async () => {
mockReadFile({ uuid: UUID_7_6_0_BUG });
configService = getConfigService(DEFAULT_CONFIG_UUID);
const uuid = await resolveInstanceUuid({ configService, logger });
serverConfig = createServerConfig(DEFAULT_CONFIG_UUID);
const uuid = await resolveInstanceUuid({ pathConfig, serverConfig, logger });
expect(uuid).not.toEqual(UUID_7_6_0_BUG);
expect(uuid).toEqual(DEFAULT_CONFIG_UUID);
expect(writeFile).toHaveBeenCalledWith(
@ -221,9 +214,9 @@ describe('resolveInstanceUuid', () => {
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);
serverConfig = createServerConfig(undefined);
mockReadFile({ error: fileNotFoundError });
const uuid = await resolveInstanceUuid({ configService, logger });
const uuid = await resolveInstanceUuid({ pathConfig, serverConfig, logger });
expect(uuid).toEqual('NEW_UUID');
expect(writeFile).toHaveBeenCalledWith(
join('data-folder', 'uuid'),
@ -243,7 +236,7 @@ describe('resolveInstanceUuid', () => {
it('throws an explicit error for file read errors', async () => {
mockReadFile({ error: permissionError });
await expect(
resolveInstanceUuid({ configService, logger })
resolveInstanceUuid({ pathConfig, serverConfig, 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"`
);
@ -251,7 +244,7 @@ describe('resolveInstanceUuid', () => {
it('throws an explicit error for file write errors', async () => {
mockWriteFile(isDirectoryError);
await expect(
resolveInstanceUuid({ configService, logger })
resolveInstanceUuid({ pathConfig, serverConfig, 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"`
);

View file

@ -19,11 +19,9 @@
import uuid from 'uuid';
import { join } from 'path';
import { take } from 'rxjs/operators';
import { readFile, writeFile } from './fs';
import { IConfigService } from '../config';
import { PathConfigType, config as pathConfigDef } from '../path';
import { HttpConfigType, config as httpConfigDef } from '../http';
import { PathConfigType } from '../path';
import { HttpConfigType } from '../http';
import { Logger } from '../logging';
const FILE_ENCODING = 'utf8';
@ -35,19 +33,15 @@ const FILE_NAME = 'uuid';
export const UUID_7_6_0_BUG = `ce42b997-a913-4d58-be46-bb1937feedd6`;
export async function resolveInstanceUuid({
configService,
pathConfig,
serverConfig,
logger,
}: {
configService: IConfigService;
pathConfig: PathConfigType;
serverConfig: HttpConfigType;
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, logger);
const uuidFromConfig = serverConfig.uuid;

View file

@ -60,7 +60,6 @@ import {
SavedObjectsServiceStart,
} from './saved_objects';
import { CapabilitiesSetup, CapabilitiesStart } from './capabilities';
import { UuidServiceSetup } from './uuid';
import { MetricsServiceStart } from './metrics';
import { StatusServiceSetup } from './status';
import { Auditor, AuditTrailSetup, AuditTrailStart } from './audit_trail';
@ -432,8 +431,6 @@ export interface CoreSetup<TPluginsStart extends object = object, TStart = unkno
status: StatusServiceSetup;
/** {@link UiSettingsServiceSetup} */
uiSettings: UiSettingsServiceSetup;
/** {@link UuidServiceSetup} */
uuid: UuidServiceSetup;
/** {@link StartServicesAccessor} */
getStartServices: StartServicesAccessor<TPluginsStart, TStart>;
/** {@link AuditTrailSetup} */
@ -483,7 +480,6 @@ export {
PluginsServiceSetup,
PluginsServiceStart,
PluginOpaqueId,
UuidServiceSetup,
AuditTrailStart,
};

View file

@ -32,7 +32,7 @@ import {
InternalSavedObjectsServiceStart,
} from './saved_objects';
import { InternalUiSettingsServiceSetup, InternalUiSettingsServiceStart } from './ui_settings';
import { UuidServiceSetup } from './uuid';
import { InternalEnvironmentServiceSetup } from './environment';
import { InternalMetricsServiceStart } from './metrics';
import { InternalRenderingServiceSetup } from './rendering';
import { InternalHttpResourcesSetup } from './http_resources';
@ -49,7 +49,7 @@ export interface InternalCoreSetup {
savedObjects: InternalSavedObjectsServiceSetup;
status: InternalStatusServiceSetup;
uiSettings: InternalUiSettingsServiceSetup;
uuid: UuidServiceSetup;
environment: InternalEnvironmentServiceSetup;
rendering: InternalRenderingServiceSetup;
httpResources: InternalHttpResourcesSetup;
auditTrail: AuditTrailSetup;

View file

@ -45,7 +45,7 @@ import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.
import { capabilitiesServiceMock } from '../capabilities/capabilities_service.mock';
import { httpResourcesMock } from '../http_resources/http_resources_service.mock';
import { setupMock as renderingServiceMock } from '../rendering/__mocks__/rendering_service';
import { uuidServiceMock } from '../uuid/uuid_service.mock';
import { environmentServiceMock } from '../environment/environment_service.mock';
import { findLegacyPluginSpecs } from './plugins';
import { LegacyVars, LegacyServiceSetupDeps, LegacyServiceStartDeps } from './types';
import { LegacyService } from './legacy_service';
@ -66,13 +66,13 @@ let startDeps: LegacyServiceStartDeps;
const logger = loggingSystemMock.create();
let configService: ReturnType<typeof configServiceMock.create>;
let uuidSetup: ReturnType<typeof uuidServiceMock.createSetupContract>;
let environmentSetup: ReturnType<typeof environmentServiceMock.createSetupContract>;
beforeEach(() => {
coreId = Symbol();
env = Env.createDefault(getEnvOptions());
configService = configServiceMock.create();
uuidSetup = uuidServiceMock.createSetupContract();
environmentSetup = environmentServiceMock.createSetupContract();
findLegacyPluginSpecsMock.mockClear();
MockKbnServer.prototype.ready = jest.fn().mockReturnValue(Promise.resolve());
@ -97,7 +97,7 @@ beforeEach(() => {
contracts: new Map([['plugin-id', 'plugin-value']]),
},
rendering: renderingServiceMock,
uuid: uuidSetup,
environment: environmentSetup,
status: statusServiceMock.createInternalSetupContract(),
auditTrail: auditTrailServiceMock.createSetupContract(),
logging: loggingServiceMock.createInternalSetupContract(),
@ -523,7 +523,7 @@ test('Sets the server.uuid property on the legacy configuration', async () => {
configService: configService as any,
});
uuidSetup.getInstanceUuid.mockImplementation(() => 'UUID_FROM_SERVICE');
environmentSetup.instanceUuid = 'UUID_FROM_SERVICE';
const configSetMock = jest.fn();

View file

@ -188,7 +188,7 @@ export class LegacyService implements CoreService {
}
// 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.legacyRawConfig!.set('server.uuid', setupDeps.core.environment.instanceUuid);
this.setupDeps = setupDeps;
this.legacyInternals = new LegacyInternals(
this.legacyPlugins.uiExports,
@ -327,9 +327,6 @@ export class LegacyService implements CoreService {
uiSettings: {
register: setupDeps.core.uiSettings.register,
},
uuid: {
getInstanceUuid: setupDeps.core.uuid.getInstanceUuid,
},
auditTrail: setupDeps.core.auditTrail,
getStartServices: () => Promise.resolve([coreStart, startDeps.plugins, {}]),
};

View file

@ -33,7 +33,7 @@ import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock';
import { SharedGlobalConfig } from './plugins';
import { capabilitiesServiceMock } from './capabilities/capabilities_service.mock';
import { metricsServiceMock } from './metrics/metrics_service.mock';
import { uuidServiceMock } from './uuid/uuid_service.mock';
import { environmentServiceMock } from './environment/environment_service.mock';
import { statusServiceMock } from './status/status_service.mock';
import { auditTrailServiceMock } from './audit_trail/audit_trail_service.mock';
@ -94,6 +94,7 @@ function pluginInitializerContextMock<T>(config: T = {} as T) {
buildSha: 'buildSha',
dist: false,
},
instanceUuid: 'instance-uuid',
},
config: pluginInitializerContextConfigMock<T>(config),
};
@ -130,7 +131,6 @@ function createCoreSetupMock({
savedObjects: savedObjectsServiceMock.createInternalSetupContract(),
status: statusServiceMock.createSetupContract(),
uiSettings: uiSettingsMock,
uuid: uuidServiceMock.createSetupContract(),
auditTrail: auditTrailServiceMock.createSetupContract(),
logging: loggingServiceMock.createSetupContract(),
getStartServices: jest
@ -163,7 +163,7 @@ function createInternalCoreSetupMock() {
http: httpServiceMock.createInternalSetupContract(),
savedObjects: savedObjectsServiceMock.createInternalSetupContract(),
status: statusServiceMock.createInternalSetupContract(),
uuid: uuidServiceMock.createSetupContract(),
environment: environmentServiceMock.createSetupContract(),
httpResources: httpResourcesMock.createSetupContract(),
rendering: renderingMock.createSetupContract(),
uiSettings: uiSettingsServiceMock.createSetupContract(),

View file

@ -26,6 +26,7 @@ import { resolve } from 'path';
import { ConfigService, Env } from '../../config';
import { getEnvOptions } from '../../config/__mocks__/env';
import { PluginsConfig, PluginsConfigType, config } from '../plugins_config';
import type { InstanceInfo } from '../plugin_context';
import { discover } from './plugins_discovery';
import { rawConfigServiceMock } from '../../config/raw_config_service.mock';
import { CoreContext } from '../../core_context';
@ -77,6 +78,7 @@ const manifestPath = (...pluginPath: string[]) =>
describe('plugins discovery system', () => {
let logger: ReturnType<typeof loggingSystemMock.create>;
let instanceInfo: InstanceInfo;
let env: Env;
let configService: ConfigService;
let pluginConfig: PluginsConfigType;
@ -87,6 +89,10 @@ describe('plugins discovery system', () => {
mockPackage.raw = packageMock;
instanceInfo = {
uuid: 'instance-uuid',
};
env = Env.createDefault(
getEnvOptions({
cliArgs: { envName: 'development' },
@ -127,7 +133,7 @@ describe('plugins discovery system', () => {
});
it('discovers plugins in the search locations', async () => {
const { plugin$ } = discover(new PluginsConfig(pluginConfig, env), coreContext);
const { plugin$ } = discover(new PluginsConfig(pluginConfig, env), coreContext, instanceInfo);
mockFs(
{
@ -146,7 +152,11 @@ describe('plugins discovery system', () => {
});
it('return errors when the manifest is invalid or incompatible', async () => {
const { plugin$, error$ } = discover(new PluginsConfig(pluginConfig, env), coreContext);
const { plugin$, error$ } = discover(
new PluginsConfig(pluginConfig, env),
coreContext,
instanceInfo
);
mockFs(
{
@ -184,7 +194,11 @@ describe('plugins discovery system', () => {
});
it('return errors when the plugin search path is not accessible', async () => {
const { plugin$, error$ } = discover(new PluginsConfig(pluginConfig, env), coreContext);
const { plugin$, error$ } = discover(
new PluginsConfig(pluginConfig, env),
coreContext,
instanceInfo
);
mockFs(
{
@ -219,7 +233,11 @@ describe('plugins discovery system', () => {
});
it('return an error when the manifest file is not accessible', async () => {
const { plugin$, error$ } = discover(new PluginsConfig(pluginConfig, env), coreContext);
const { plugin$, error$ } = discover(
new PluginsConfig(pluginConfig, env),
coreContext,
instanceInfo
);
mockFs(
{
@ -250,7 +268,11 @@ describe('plugins discovery system', () => {
});
it('discovers plugins in nested directories', async () => {
const { plugin$, error$ } = discover(new PluginsConfig(pluginConfig, env), coreContext);
const { plugin$, error$ } = discover(
new PluginsConfig(pluginConfig, env),
coreContext,
instanceInfo
);
mockFs(
{
@ -287,7 +309,7 @@ describe('plugins discovery system', () => {
});
it('does not discover plugins nested inside another plugin', async () => {
const { plugin$ } = discover(new PluginsConfig(pluginConfig, env), coreContext);
const { plugin$ } = discover(new PluginsConfig(pluginConfig, env), coreContext, instanceInfo);
mockFs(
{
@ -306,7 +328,7 @@ describe('plugins discovery system', () => {
});
it('stops scanning when reaching `maxDepth`', async () => {
const { plugin$ } = discover(new PluginsConfig(pluginConfig, env), coreContext);
const { plugin$ } = discover(new PluginsConfig(pluginConfig, env), coreContext, instanceInfo);
mockFs(
{
@ -332,7 +354,7 @@ describe('plugins discovery system', () => {
});
it('works with symlinks', async () => {
const { plugin$ } = discover(new PluginsConfig(pluginConfig, env), coreContext);
const { plugin$ } = discover(new PluginsConfig(pluginConfig, env), coreContext, instanceInfo);
const pluginFolder = resolve(KIBANA_ROOT, '..', 'ext-plugins');
@ -365,12 +387,16 @@ describe('plugins discovery system', () => {
})
);
discover(new PluginsConfig({ ...pluginConfig, paths: [extraPluginTestPath] }, env), {
coreId: Symbol(),
configService,
env,
logger,
});
discover(
new PluginsConfig({ ...pluginConfig, paths: [extraPluginTestPath] }, env),
{
coreId: Symbol(),
configService,
env,
logger,
},
instanceInfo
);
expect(loggingSystemMock.collect(logger).warn).toEqual([
[
@ -388,12 +414,16 @@ describe('plugins discovery system', () => {
})
);
discover(new PluginsConfig({ ...pluginConfig, paths: [extraPluginTestPath] }, env), {
coreId: Symbol(),
configService,
env,
logger,
});
discover(
new PluginsConfig({ ...pluginConfig, paths: [extraPluginTestPath] }, env),
{
coreId: Symbol(),
configService,
env,
logger,
},
instanceInfo
);
expect(loggingSystemMock.collect(logger).warn).toEqual([]);
});

View file

@ -24,7 +24,7 @@ import { catchError, filter, map, mergeMap, shareReplay } from 'rxjs/operators';
import { CoreContext } from '../../core_context';
import { Logger } from '../../logging';
import { PluginWrapper } from '../plugin';
import { createPluginInitializerContext } from '../plugin_context';
import { createPluginInitializerContext, InstanceInfo } from '../plugin_context';
import { PluginsConfig } from '../plugins_config';
import { PluginDiscoveryError } from './plugin_discovery_error';
import { parseManifest } from './plugin_manifest_parser';
@ -49,7 +49,11 @@ interface PluginSearchPathEntry {
* @param coreContext Kibana core values.
* @internal
*/
export function discover(config: PluginsConfig, coreContext: CoreContext) {
export function discover(
config: PluginsConfig,
coreContext: CoreContext,
instanceInfo: InstanceInfo
) {
const log = coreContext.logger.get('plugins-discovery');
log.debug('Discovering plugins...');
@ -65,7 +69,7 @@ export function discover(config: PluginsConfig, coreContext: CoreContext) {
).pipe(
mergeMap((pluginPathOrError) => {
return typeof pluginPathOrError === 'string'
? createPlugin$(pluginPathOrError, log, coreContext)
? createPlugin$(pluginPathOrError, log, coreContext, instanceInfo)
: [pluginPathOrError];
}),
shareReplay()
@ -180,7 +184,12 @@ function mapSubdirectories(
* @param log Plugin discovery logger instance.
* @param coreContext Kibana core context.
*/
function createPlugin$(path: string, log: Logger, coreContext: CoreContext) {
function createPlugin$(
path: string,
log: Logger,
coreContext: CoreContext,
instanceInfo: InstanceInfo
) {
return from(parseManifest(path, coreContext.env.packageInfo, log)).pipe(
map((manifest) => {
log.debug(`Successfully discovered plugin "${manifest.id}" at "${path}"`);
@ -189,7 +198,12 @@ function createPlugin$(path: string, log: Logger, coreContext: CoreContext) {
path,
manifest,
opaqueId,
initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest),
initializerContext: createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
),
});
}),
catchError((err) => [err])

View file

@ -28,12 +28,14 @@ import { BehaviorSubject, from } from 'rxjs';
import { rawConfigServiceMock } from '../../config/raw_config_service.mock';
import { config } from '../plugins_config';
import { loggingSystemMock } from '../../logging/logging_system.mock';
import { environmentServiceMock } from '../../environment/environment_service.mock';
import { coreMock } from '../../mocks';
import { Plugin } from '../types';
import { PluginWrapper } from '../plugin';
describe('PluginsService', () => {
const logger = loggingSystemMock.create();
const environmentSetup = environmentServiceMock.createSetupContract();
let pluginsService: PluginsService;
const createPlugin = (
@ -158,7 +160,7 @@ describe('PluginsService', () => {
}
);
await pluginsService.discover();
await pluginsService.discover({ environment: environmentSetup });
const setupDeps = coreMock.createInternalSetup();
await pluginsService.setup(setupDeps);

View file

@ -30,7 +30,11 @@ import { loggingSystemMock } from '../logging/logging_system.mock';
import { PluginWrapper } from './plugin';
import { PluginManifest } from './types';
import { createPluginInitializerContext, createPluginSetupContext } from './plugin_context';
import {
createPluginInitializerContext,
createPluginSetupContext,
InstanceInfo,
} from './plugin_context';
const mockPluginInitializer = jest.fn();
const logger = loggingSystemMock.create();
@ -67,12 +71,16 @@ configService.atPath.mockReturnValue(new BehaviorSubject({ initialize: true }));
let coreId: symbol;
let env: Env;
let coreContext: CoreContext;
let instanceInfo: InstanceInfo;
const setupDeps = coreMock.createInternalSetup();
beforeEach(() => {
coreId = Symbol('core');
env = Env.createDefault(getEnvOptions());
instanceInfo = {
uuid: 'instance-uuid',
};
coreContext = { coreId, env, logger, configService: configService as any };
});
@ -88,7 +96,12 @@ test('`constructor` correctly initializes plugin instance', () => {
path: 'some-plugin-path',
manifest,
opaqueId,
initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest),
initializerContext: createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
),
});
expect(plugin.name).toBe('some-plugin-id');
@ -105,7 +118,12 @@ test('`setup` fails if `plugin` initializer is not exported', async () => {
path: 'plugin-without-initializer-path',
manifest,
opaqueId,
initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest),
initializerContext: createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
),
});
await expect(
@ -122,7 +140,12 @@ test('`setup` fails if plugin initializer is not a function', async () => {
path: 'plugin-with-wrong-initializer-path',
manifest,
opaqueId,
initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest),
initializerContext: createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
),
});
await expect(
@ -139,7 +162,12 @@ test('`setup` fails if initializer does not return object', async () => {
path: 'plugin-with-initializer-path',
manifest,
opaqueId,
initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest),
initializerContext: createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
),
});
mockPluginInitializer.mockReturnValue(null);
@ -158,7 +186,12 @@ test('`setup` fails if object returned from initializer does not define `setup`
path: 'plugin-with-initializer-path',
manifest,
opaqueId,
initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest),
initializerContext: createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
),
});
const mockPluginInstance = { run: jest.fn() };
@ -174,7 +207,12 @@ test('`setup` fails if object returned from initializer does not define `setup`
test('`setup` initializes plugin and calls appropriate lifecycle hook', async () => {
const manifest = createPluginManifest();
const opaqueId = Symbol();
const initializerContext = createPluginInitializerContext(coreContext, opaqueId, manifest);
const initializerContext = createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
);
const plugin = new PluginWrapper({
path: 'plugin-with-initializer-path',
manifest,
@ -203,7 +241,12 @@ test('`start` fails if setup is not called first', async () => {
path: 'some-plugin-path',
manifest,
opaqueId,
initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest),
initializerContext: createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
),
});
await expect(plugin.start({} as any, {} as any)).rejects.toThrowErrorMatchingInlineSnapshot(
@ -218,7 +261,12 @@ test('`start` calls plugin.start with context and dependencies', async () => {
path: 'plugin-with-initializer-path',
manifest,
opaqueId,
initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest),
initializerContext: createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
),
});
const context = { any: 'thing' } as any;
const deps = { otherDep: 'value' };
@ -247,7 +295,12 @@ test("`start` resolves `startDependencies` Promise after plugin's start", async
path: 'plugin-with-initializer-path',
manifest,
opaqueId,
initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest),
initializerContext: createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
),
});
const startContext = { any: 'thing' } as any;
const pluginDeps = { someDep: 'value' };
@ -286,7 +339,12 @@ test('`stop` fails if plugin is not set up', async () => {
path: 'plugin-with-initializer-path',
manifest,
opaqueId,
initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest),
initializerContext: createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
),
});
const mockPluginInstance = { setup: jest.fn(), stop: jest.fn() };
@ -305,7 +363,12 @@ test('`stop` does nothing if plugin does not define `stop` function', async () =
path: 'plugin-with-initializer-path',
manifest,
opaqueId,
initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest),
initializerContext: createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
),
});
mockPluginInitializer.mockReturnValue({ setup: jest.fn() });
@ -321,7 +384,12 @@ test('`stop` calls `stop` defined by the plugin instance', async () => {
path: 'plugin-with-initializer-path',
manifest,
opaqueId,
initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest),
initializerContext: createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
),
});
const mockPluginInstance = { setup: jest.fn(), stop: jest.fn() };
@ -351,7 +419,12 @@ describe('#getConfigSchema()', () => {
path: 'plugin-with-schema',
manifest,
opaqueId,
initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest),
initializerContext: createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
),
});
expect(plugin.getConfigDescriptor()).toBe(configDescriptor);
@ -365,7 +438,12 @@ describe('#getConfigSchema()', () => {
path: 'plugin-with-no-definition',
manifest,
opaqueId,
initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest),
initializerContext: createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
),
});
expect(plugin.getConfigDescriptor()).toBe(null);
});
@ -377,7 +455,12 @@ describe('#getConfigSchema()', () => {
path: 'plugin-with-no-definition',
manifest,
opaqueId,
initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest),
initializerContext: createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
),
});
expect(plugin.getConfigDescriptor()).toBe(null);
});
@ -400,7 +483,12 @@ describe('#getConfigSchema()', () => {
path: 'plugin-invalid-schema',
manifest,
opaqueId,
initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest),
initializerContext: createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
),
});
expect(() => plugin.getConfigDescriptor()).toThrowErrorMatchingInlineSnapshot(
`"Configuration schema expected to be an instance of Type"`

View file

@ -19,7 +19,7 @@
import { duration } from 'moment';
import { first } from 'rxjs/operators';
import { createPluginInitializerContext } from './plugin_context';
import { createPluginInitializerContext, InstanceInfo } from './plugin_context';
import { CoreContext } from '../core_context';
import { Env } from '../config';
import { loggingSystemMock } from '../logging/logging_system.mock';
@ -35,6 +35,7 @@ let coreId: symbol;
let env: Env;
let coreContext: CoreContext;
let server: Server;
let instanceInfo: InstanceInfo;
function createPluginManifest(manifestProps: Partial<PluginManifest> = {}): PluginManifest {
return {
@ -51,9 +52,12 @@ function createPluginManifest(manifestProps: Partial<PluginManifest> = {}): Plug
};
}
describe('Plugin Context', () => {
describe('createPluginInitializerContext', () => {
beforeEach(async () => {
coreId = Symbol('core');
instanceInfo = {
uuid: 'instance-uuid',
};
env = Env.createDefault(getEnvOptions());
const config$ = rawConfigServiceMock.create({ rawConfig: {} });
server = new Server(config$, env, logger);
@ -67,7 +71,8 @@ describe('Plugin Context', () => {
const pluginInitializerContext = createPluginInitializerContext(
coreContext,
opaqueId,
manifest
manifest,
instanceInfo
);
expect(pluginInitializerContext.config.legacy.globalConfig$).toBeDefined();
@ -90,4 +95,19 @@ describe('Plugin Context', () => {
path: { data: fromRoot('data') },
});
});
it('allow to access the provided instance uuid', () => {
const manifest = createPluginManifest();
const opaqueId = Symbol();
instanceInfo = {
uuid: 'kibana-uuid',
};
const pluginInitializerContext = createPluginInitializerContext(
coreContext,
opaqueId,
manifest,
instanceInfo
);
expect(pluginInitializerContext.env.instanceUuid).toBe('kibana-uuid');
});
});

View file

@ -37,6 +37,10 @@ import {
import { pick, deepFreeze } from '../../utils';
import { CoreSetup, CoreStart } from '..';
export interface InstanceInfo {
uuid: string;
}
/**
* This returns a facade for `CoreContext` that will be exposed to the plugin initializer.
* This facade should be safe to use across entire plugin lifespan.
@ -53,7 +57,8 @@ import { CoreSetup, CoreStart } from '..';
export function createPluginInitializerContext(
coreContext: CoreContext,
opaqueId: PluginOpaqueId,
pluginManifest: PluginManifest
pluginManifest: PluginManifest,
instanceInfo: InstanceInfo
): PluginInitializerContext {
return {
opaqueId,
@ -64,6 +69,7 @@ export function createPluginInitializerContext(
env: {
mode: coreContext.env.mode,
packageInfo: coreContext.env.packageInfo,
instanceUuid: instanceInfo.uuid,
},
/**
@ -183,9 +189,6 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
uiSettings: {
register: deps.uiSettings.register,
},
uuid: {
getInstanceUuid: deps.uuid.getInstanceUuid,
},
getStartServices: () => plugin.startDependencies,
auditTrail: deps.auditTrail,
};

View file

@ -29,6 +29,7 @@ import { rawConfigServiceMock } from '../config/raw_config_service.mock';
import { getEnvOptions } from '../config/__mocks__/env';
import { coreMock } from '../mocks';
import { loggingSystemMock } from '../logging/logging_system.mock';
import { environmentServiceMock } from '../environment/environment_service.mock';
import { PluginDiscoveryError } from './discovery';
import { PluginWrapper } from './plugin';
import { PluginsService } from './plugins_service';
@ -45,6 +46,7 @@ let configService: ConfigService;
let coreId: symbol;
let env: Env;
let mockPluginSystem: jest.Mocked<PluginsSystem>;
let environmentSetup: ReturnType<typeof environmentServiceMock.createSetupContract>;
const setupDeps = coreMock.createInternalSetup();
const logger = loggingSystemMock.create();
@ -124,6 +126,8 @@ describe('PluginsService', () => {
[mockPluginSystem] = MockPluginsSystem.mock.instances as any;
mockPluginSystem.uiPlugins.mockReturnValue(new Map());
environmentSetup = environmentServiceMock.createSetupContract();
});
afterEach(() => {
@ -137,7 +141,8 @@ describe('PluginsService', () => {
plugin$: from([]),
});
await expect(pluginsService.discover()).rejects.toMatchInlineSnapshot(`
await expect(pluginsService.discover({ environment: environmentSetup })).rejects
.toMatchInlineSnapshot(`
[Error: Failed to initialize plugins:
Invalid JSON (invalid-manifest, path-1)]
`);
@ -158,7 +163,8 @@ describe('PluginsService', () => {
plugin$: from([]),
});
await expect(pluginsService.discover()).rejects.toMatchInlineSnapshot(`
await expect(pluginsService.discover({ environment: environmentSetup })).rejects
.toMatchInlineSnapshot(`
[Error: Failed to initialize plugins:
Incompatible version (incompatible-version, path-3)]
`);
@ -192,7 +198,9 @@ describe('PluginsService', () => {
]),
});
await expect(pluginsService.discover()).rejects.toMatchInlineSnapshot(
await expect(
pluginsService.discover({ environment: environmentSetup })
).rejects.toMatchInlineSnapshot(
`[Error: Plugin with id "conflicting-id" is already registered!]`
);
@ -253,7 +261,7 @@ describe('PluginsService', () => {
]),
});
await pluginsService.discover();
await pluginsService.discover({ environment: environmentSetup });
const setup = await pluginsService.setup(setupDeps);
expect(setup.contracts).toBeInstanceOf(Map);
@ -300,7 +308,7 @@ describe('PluginsService', () => {
plugin$: from([firstPlugin, secondPlugin]),
});
const { pluginTree } = await pluginsService.discover();
const { pluginTree } = await pluginsService.discover({ environment: environmentSetup });
expect(pluginTree).toBeUndefined();
expect(mockDiscover).toHaveBeenCalledTimes(1);
@ -336,7 +344,7 @@ describe('PluginsService', () => {
plugin$: from([firstPlugin, secondPlugin, thirdPlugin, lastPlugin, missingDepsPlugin]),
});
const { pluginTree } = await pluginsService.discover();
const { pluginTree } = await pluginsService.discover({ environment: environmentSetup });
expect(pluginTree).toBeUndefined();
expect(mockDiscover).toHaveBeenCalledTimes(1);
@ -369,7 +377,7 @@ describe('PluginsService', () => {
plugin$: from([firstPlugin, secondPlugin]),
});
await pluginsService.discover();
await pluginsService.discover({ environment: environmentSetup });
expect(mockPluginSystem.addPlugin).toHaveBeenCalledTimes(2);
expect(mockPluginSystem.addPlugin).toHaveBeenCalledWith(firstPlugin);
expect(mockPluginSystem.addPlugin).toHaveBeenCalledWith(secondPlugin);
@ -386,7 +394,8 @@ describe('PluginsService', () => {
resolve(process.cwd(), '..', 'kibana-extra'),
],
},
{ coreId, env, logger, configService }
{ coreId, env, logger, configService },
{ uuid: 'uuid' }
);
const logs = loggingSystemMock.collect(logger);
@ -417,7 +426,7 @@ describe('PluginsService', () => {
}),
]),
});
await pluginsService.discover();
await pluginsService.discover({ environment: environmentSetup });
expect(configService.setSchema).toBeCalledWith('path', configSchema);
});
@ -448,7 +457,7 @@ describe('PluginsService', () => {
}),
]),
});
await pluginsService.discover();
await pluginsService.discover({ environment: environmentSetup });
expect(configService.addDeprecationProvider).toBeCalledWith(
'config-path',
deprecationProvider
@ -496,7 +505,7 @@ describe('PluginsService', () => {
});
mockPluginSystem.uiPlugins.mockReturnValue(new Map([pluginToDiscoveredEntry(plugin)]));
const { uiPlugins } = await pluginsService.discover();
const { uiPlugins } = await pluginsService.discover({ environment: environmentSetup });
const uiConfig$ = uiPlugins.browserConfigs.get('plugin-with-expose');
expect(uiConfig$).toBeDefined();
@ -532,7 +541,7 @@ describe('PluginsService', () => {
});
mockPluginSystem.uiPlugins.mockReturnValue(new Map([pluginToDiscoveredEntry(plugin)]));
const { uiPlugins } = await pluginsService.discover();
const { uiPlugins } = await pluginsService.discover({ environment: environmentSetup });
expect([...uiPlugins.browserConfigs.entries()]).toHaveLength(0);
});
});
@ -561,7 +570,7 @@ describe('PluginsService', () => {
describe('uiPlugins.internal', () => {
it('includes disabled plugins', async () => {
config$.next({ plugins: { initialize: true }, plugin1: { enabled: false } });
const { uiPlugins } = await pluginsService.discover();
const { uiPlugins } = await pluginsService.discover({ environment: environmentSetup });
expect(uiPlugins.internal).toMatchInlineSnapshot(`
Map {
"plugin-1" => Object {
@ -582,7 +591,7 @@ describe('PluginsService', () => {
describe('plugin initialization', () => {
it('does initialize if plugins.initialize is true', async () => {
config$.next({ plugins: { initialize: true } });
await pluginsService.discover();
await pluginsService.discover({ environment: environmentSetup });
const { initialized } = await pluginsService.setup(setupDeps);
expect(mockPluginSystem.setupPlugins).toHaveBeenCalled();
expect(initialized).toBe(true);
@ -590,7 +599,7 @@ describe('PluginsService', () => {
it('does not initialize if plugins.initialize is false', async () => {
config$.next({ plugins: { initialize: false } });
await pluginsService.discover();
await pluginsService.discover({ environment: environmentSetup });
const { initialized } = await pluginsService.setup(setupDeps);
expect(mockPluginSystem.setupPlugins).not.toHaveBeenCalled();
expect(initialized).toBe(false);

View file

@ -32,6 +32,7 @@ import { PluginsSystem } from './plugins_system';
import { InternalCoreSetup, InternalCoreStart } from '../internal_types';
import { IConfigService } from '../config';
import { pick } from '../../utils';
import { InternalEnvironmentServiceSetup } from '../environment';
/** @internal */
export interface PluginsServiceSetup {
@ -72,6 +73,11 @@ export type PluginsServiceSetupDeps = InternalCoreSetup;
/** @internal */
export type PluginsServiceStartDeps = InternalCoreStart;
/** @internal */
export interface PluginsServiceDiscoverDeps {
environment: InternalEnvironmentServiceSetup;
}
/** @internal */
export class PluginsService implements CoreService<PluginsServiceSetup, PluginsServiceStart> {
private readonly log: Logger;
@ -90,12 +96,14 @@ export class PluginsService implements CoreService<PluginsServiceSetup, PluginsS
.pipe(map((rawConfig) => new PluginsConfig(rawConfig, coreContext.env)));
}
public async discover() {
public async discover({ environment }: PluginsServiceDiscoverDeps) {
this.log.debug('Discovering plugins');
const config = await this.config$.pipe(first()).toPromise();
const { error$, plugin$ } = discover(config, this.coreContext);
const { error$, plugin$ } = discover(config, this.coreContext, {
uuid: environment.instanceUuid,
});
await this.handleDiscoveryErrors(error$);
await this.handleDiscoveredPlugins(plugin$);

View file

@ -278,6 +278,7 @@ export interface PluginInitializerContext<ConfigSchema = unknown> {
env: {
mode: EnvironmentMode;
packageInfo: Readonly<PackageInfo>;
instanceUuid: string;
};
logger: LoggerFactory;
config: {

View file

@ -499,8 +499,6 @@ export interface CoreSetup<TPluginsStart extends object = object, TStart = unkno
status: StatusServiceSetup;
// (undocumented)
uiSettings: UiSettingsServiceSetup;
// (undocumented)
uuid: UuidServiceSetup;
}
// @public
@ -1778,6 +1776,7 @@ export interface PluginInitializerContext<ConfigSchema = unknown> {
env: {
mode: EnvironmentMode;
packageInfo: Readonly<PackageInfo>;
instanceUuid: string;
};
// (undocumented)
logger: LoggerFactory;
@ -2883,11 +2882,6 @@ export interface UserProvidedValues<T = any> {
userValue?: T;
}
// @public
export interface UuidServiceSetup {
getInstanceUuid(): string;
}
// @public
export const validBodyOutput: readonly ["data", "stream"];

View file

@ -74,10 +74,10 @@ import { RenderingService, mockRenderingService } from './rendering/__mocks__/re
export { mockRenderingService };
jest.doMock('./rendering/rendering_service', () => ({ RenderingService }));
import { uuidServiceMock } from './uuid/uuid_service.mock';
export const mockUuidService = uuidServiceMock.create();
jest.doMock('./uuid/uuid_service', () => ({
UuidService: jest.fn(() => mockUuidService),
import { environmentServiceMock } from './environment/environment_service.mock';
export const mockEnvironmentService = environmentServiceMock.create();
jest.doMock('./environment/environment_service', () => ({
EnvironmentService: jest.fn(() => mockEnvironmentService),
}));
import { metricsServiceMock } from './metrics/metrics_service.mock';

View file

@ -31,7 +31,7 @@ import { PluginsService, config as pluginsConfig } from './plugins';
import { SavedObjectsService } from '../server/saved_objects';
import { MetricsService, opsConfig } from './metrics';
import { CapabilitiesService } from './capabilities';
import { UuidService } from './uuid';
import { EnvironmentService } from './environment';
import { StatusService } from './status/status_service';
import { config as cspConfig } from './csp';
@ -64,7 +64,7 @@ export class Server {
private readonly plugins: PluginsService;
private readonly savedObjects: SavedObjectsService;
private readonly uiSettings: UiSettingsService;
private readonly uuid: UuidService;
private readonly environment: EnvironmentService;
private readonly metrics: MetricsService;
private readonly httpResources: HttpResourcesService;
private readonly status: StatusService;
@ -95,7 +95,7 @@ export class Server {
this.savedObjects = new SavedObjectsService(core);
this.uiSettings = new UiSettingsService(core);
this.capabilities = new CapabilitiesService(core);
this.uuid = new UuidService(core);
this.environment = new EnvironmentService(core);
this.metrics = new MetricsService(core);
this.status = new StatusService(core);
this.coreApp = new CoreApp(core);
@ -107,8 +107,12 @@ export class Server {
public async setup() {
this.log.debug('setting up server');
const environmentSetup = await this.environment.setup();
// Discover any plugins before continuing. This allows other systems to utilize the plugin dependency graph.
const { pluginTree, uiPlugins } = await this.plugins.discover();
const { pluginTree, uiPlugins } = await this.plugins.discover({
environment: environmentSetup,
});
const legacyPlugins = await this.legacy.discoverPlugins();
// Immediately terminate in case of invalid configuration
@ -124,7 +128,6 @@ export class Server {
});
const auditTrailSetup = this.auditTrail.setup();
const uuidSetup = await this.uuid.setup();
const httpSetup = await this.http.setup({
context: contextServiceSetup,
@ -174,11 +177,11 @@ export class Server {
capabilities: capabilitiesSetup,
context: contextServiceSetup,
elasticsearch: elasticsearchServiceSetup,
environment: environmentSetup,
http: httpSetup,
savedObjects: savedObjectsSetup,
status: statusSetup,
uiSettings: uiSettingsSetup,
uuid: uuidSetup,
rendering: renderingSetup,
httpResources: httpResourcesSetup,
auditTrail: auditTrailSetup,

View file

@ -17,13 +17,8 @@
* under the License.
*/
import Fs from 'fs';
import { promisify } from 'util';
import { getUiSettingDefaults } from './server/ui_setting_defaults';
const mkdirAsync = promisify(Fs.mkdir);
export default function (kibana) {
return new kibana.Plugin({
id: 'kibana',
@ -40,17 +35,5 @@ export default function (kibana) {
uiExports: {
uiSettingDefaults: getUiSettingDefaults(),
},
preInit: async function (server) {
try {
// Create the data directory (recursively, if the a parent dir doesn't exist).
// If it already exists, does nothing.
await mkdirAsync(server.config().get('path.data'), { recursive: true });
} catch (err) {
server.log(['error', 'init'], err);
// Stop the server startup with a fatal error
throw err;
}
},
});
}

View file

@ -85,7 +85,7 @@ export class Plugin implements CorePlugin<IEventLogService, IEventLogClientServi
config,
esContext: this.esContext,
systemLogger: this.systemLogger,
kibanaUUID: core.uuid.getInstanceUuid(),
kibanaUUID: this.context.env.instanceUuid,
savedObjectProviderRegistry: this.savedObjectProviderRegistry,
});

View file

@ -65,9 +65,6 @@ describe('Monitoring plugin', () => {
serverBasePath: '',
},
},
uuid: {
getInstanceUuid: jest.fn(),
},
elasticsearch: {
legacy: {
client: {},

View file

@ -92,7 +92,7 @@ export class Plugin {
const router = core.http.createRouter();
this.legacyShimDependencies = {
router,
instanceUuid: core.uuid.getInstanceUuid(),
instanceUuid: this.initializerContext.env.instanceUuid,
esDataClient: core.elasticsearch.legacy.client,
kibanaStatsCollector: plugins.usageCollection?.getCollectorByType(
KIBANA_STATS_TYPE_MONITORING
@ -159,7 +159,7 @@ export class Plugin {
config,
log: kibanaMonitoringLog,
kibanaStats: {
uuid: core.uuid.getInstanceUuid(),
uuid: this.initializerContext.env.instanceUuid,
name: serverInfo.name,
index: get(legacyConfig, 'kibana.index'),
host: serverInfo.hostname,

View file

@ -75,7 +75,7 @@ export const buildConfig = async (
host: serverInfo.hostname,
name: serverInfo.name,
port: serverInfo.port,
uuid: core.uuid.getInstanceUuid(),
uuid: initContext.env.instanceUuid,
protocol: serverInfo.protocol,
},
};

View file

@ -42,7 +42,7 @@ export class TaskManagerPlugin
.toPromise();
setupSavedObjects(core.savedObjects, this.config);
this.taskManagerId = core.uuid.getInstanceUuid();
this.taskManagerId = this.initContext.env.instanceUuid;
return {
addMiddleware: (middleware: Middleware) => {