Remove legacy plugins support (#77599) (#78252)

* remove ALL the things.

* adapt some types and tests

* restore ensureValidConfiguration

* fix legacy service tests

* adapt uiRender mixin

* remove legacy types

* update generated doc

* restore legacy plugin schema

* update generated doc

* remove remaining code of x-pack/legacy

* adapt imports due to merge

* cleanup CODEOWNERS

* cleanup gitignore & i18nrc

* cleanup tsconfig.json

* remove unused i18n keys

* add back `"legacy/plugins/**/*",` to tsconfig until legacy space plugin is deleted

* fix create_jest_config

* remove references from eslintrc

* more eslint cleanup

* remove `x-pack/index.js`

* fix xpack gulp scripts

* fix bug with default + named imports from boom

* remove rules from eslintrc

* remove LegacyInternals

* review comments

* update generated doc

* cleanup legacy metadatas

* revert changes to eslintrc

* update generated doc
# Conflicts:
#	.github/CODEOWNERS
#	x-pack/.i18nrc.json
#	x-pack/dev-tools/jest/create_jest_config.js
#	x-pack/legacy/plugins/xpack_main/index.js
#	x-pack/legacy/server/lib/constants/index.ts
#	x-pack/legacy/server/lib/key_case_converter.js
#	x-pack/legacy/server/lib/watch_status_and_license_to_initialize.js
This commit is contained in:
Pierre Gayvallet 2020-09-23 11:44:58 +02:00 committed by GitHub
parent 2da221e818
commit 873941d6e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
219 changed files with 280 additions and 9648 deletions

View file

@ -17,9 +17,6 @@
* under the License.
*/
const { readdirSync } = require('fs');
const { resolve } = require('path');
const APACHE_2_0_LICENSE_HEADER = `
/*
* Licensed to Elasticsearch B.V. under one or more contributor
@ -294,7 +291,7 @@ module.exports = {
},
{
target: [
'(src|x-pack)/legacy/**/*',
'src/legacy/**/*',
'(src|x-pack)/plugins/**/(public|server)/**/*',
'examples/**/*',
],
@ -325,14 +322,11 @@ module.exports = {
},
{
target: [
'(src|x-pack)/legacy/**/*',
'src/legacy/**/*',
'(src|x-pack)/plugins/**/(public|server)/**/*',
'examples/**/*',
'!(src|x-pack)/**/*.test.*',
'!(x-pack/)?test/**/*',
// next folder contains legacy browser tests which can't be migrated to jest
// which import np files
'!src/legacy/core_plugins/kibana/public/__tests__/**/*',
],
from: [
'(src|x-pack)/plugins/**/(public|server)/**/*',
@ -347,14 +341,6 @@ module.exports = {
'(src|x-pack)/plugins/**/*',
'!(src|x-pack)/plugins/**/server/**/*',
'src/legacy/core_plugins/**/*',
'!src/legacy/core_plugins/**/server/**/*',
'!src/legacy/core_plugins/**/index.{js,mjs,ts,tsx}',
'x-pack/legacy/plugins/**/*',
'!x-pack/legacy/plugins/**/server/**/*',
'!x-pack/legacy/plugins/**/index.{js,mjs,ts,tsx}',
'examples/**/*',
'!examples/**/server/**/*',
],
@ -374,12 +360,7 @@ module.exports = {
},
{
target: ['src/core/**/*'],
from: [
'plugins/**/*',
'src/plugins/**/*',
'src/legacy/core_plugins/**/*',
'src/legacy/ui/**/*',
],
from: ['plugins/**/*', 'src/plugins/**/*', 'src/legacy/ui/**/*'],
errorMessage: 'The core cannot depend on any plugins.',
},
{
@ -392,12 +373,6 @@ module.exports = {
target: [
'test/plugin_functional/plugins/**/public/np_ready/**/*',
'test/plugin_functional/plugins/**/server/np_ready/**/*',
'src/legacy/core_plugins/**/public/np_ready/**/*',
'src/legacy/core_plugins/vis_type_*/public/**/*',
'!src/legacy/core_plugins/vis_type_*/public/legacy*',
'src/legacy/core_plugins/**/server/np_ready/**/*',
'x-pack/legacy/plugins/**/public/np_ready/**/*',
'x-pack/legacy/plugins/**/server/np_ready/**/*',
],
allowSameFolder: true,
errorMessage:
@ -447,22 +422,14 @@ module.exports = {
settings: {
// instructs import/no-extraneous-dependencies to treat certain modules
// as core modules, even if they aren't listed in package.json
'import/core-modules': ['plugins', 'legacy/ui'],
'import/core-modules': ['plugins'],
'import/resolver': {
'@kbn/eslint-import-resolver-kibana': {
forceNode: false,
rootPackageName: 'kibana',
kibanaPath: '.',
pluginMap: readdirSync(resolve(__dirname, 'x-pack/legacy/plugins')).reduce(
(acc, name) => {
if (!name.startsWith('_')) {
acc[name] = `x-pack/legacy/plugins/${name}`;
}
return acc;
},
{}
),
pluginMap: {},
},
},
},
@ -768,16 +735,6 @@ module.exports = {
},
},
/**
* GIS overrides
*/
{
files: ['x-pack/legacy/plugins/maps/**/*.js'],
rules: {
'react/prefer-stateless-function': [0, { ignorePureComponents: false }],
},
},
/**
* ML overrides
*/
@ -816,7 +773,7 @@ module.exports = {
},
{
// typescript only for front and back end
files: ['x-pack/{,legacy/}plugins/security_solution/**/*.{ts,tsx}'],
files: ['x-pack/plugins/security_solution/**/*.{ts,tsx}'],
rules: {
// This will be turned on after bug fixes are complete
// '@typescript-eslint/explicit-member-accessibility': 'warn',
@ -862,7 +819,7 @@ module.exports = {
// },
{
// typescript and javascript for front and back end
files: ['x-pack/{,legacy/}plugins/security_solution/**/*.{js,mjs,ts,tsx}'],
files: ['x-pack/plugins/security_solution/**/*.{js,mjs,ts,tsx}'],
plugins: ['eslint-plugin-node', 'react'],
env: {
mocha: true,
@ -1093,7 +1050,7 @@ module.exports = {
{
// typescript only for front and back end
files: [
'x-pack/{,legacy/}plugins/{alerts,alerting_builtins,actions,task_manager,event_log}/**/*.{ts,tsx}',
'x-pack/plugins/{alerts,alerting_builtins,actions,task_manager,event_log}/**/*.{ts,tsx}',
],
rules: {
'@typescript-eslint/no-explicit-any': 'error',
@ -1242,10 +1199,7 @@ module.exports = {
* TSVB overrides
*/
{
files: [
'src/plugins/vis_type_timeseries/**/*.{js,mjs,ts,tsx}',
'src/legacy/core_plugins/vis_type_timeseries/**/*.{js,mjs,ts,tsx}',
],
files: ['src/plugins/vis_type_timeseries/**/*.{js,mjs,ts,tsx}'],
rules: {
'import/no-default-export': 'error',
},

5
kibana.d.ts vendored
View file

@ -28,7 +28,6 @@ export { Public, Server };
/**
* All exports from TS ambient definitions (where types are added for JS source in a .d.ts file).
*/
import * as LegacyKibanaPluginSpec from './src/legacy/plugin_discovery/plugin_spec/plugin_spec_options';
import * as LegacyKibanaServer from './src/legacy/server/kbn_server';
/**
@ -39,8 +38,4 @@ export namespace Legacy {
export type Request = LegacyKibanaServer.Request;
export type ResponseToolkit = LegacyKibanaServer.ResponseToolkit;
export type Server = LegacyKibanaServer.Server;
export type InitPluginFunction = LegacyKibanaPluginSpec.InitPluginFunction;
export type UiExports = LegacyKibanaPluginSpec.UiExports;
export type PluginSpecOptions = LegacyKibanaPluginSpec.PluginSpecOptions;
}

View file

@ -48,11 +48,6 @@ const CAN_CLUSTER = canRequire(CLUSTER_MANAGER_PATH);
const REPL_PATH = resolve(__dirname, '../repl');
const CAN_REPL = canRequire(REPL_PATH);
// xpack is installed in both dev and the distributable, it's optional if
// install is a link to the source, not an actual install
const XPACK_DIR = resolve(__dirname, '../../../x-pack');
const XPACK_INSTALLED = canRequire(XPACK_DIR);
const pathCollector = function () {
const paths = [];
return function (path) {
@ -137,16 +132,7 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) {
if (opts.logFile) set('logging.dest', opts.logFile);
set('plugins.scanDirs', _.compact([].concat(get('plugins.scanDirs'), opts.pluginDir)));
set(
'plugins.paths',
_.compact(
[].concat(
get('plugins.paths'),
opts.pluginPath,
XPACK_INSTALLED && !opts.oss ? [XPACK_DIR] : []
)
)
);
set('plugins.paths', _.compact([].concat(get('plugins.paths'), opts.pluginPath)));
merge(extraCliOptions);
merge(readKeystore());

View file

@ -58,19 +58,6 @@ export interface InjectedMetadataParams {
uiPlugins: InjectedPluginMetadata[];
anonymousStatusPage: boolean;
legacyMetadata: {
app: {
id: string;
title: string;
};
bundleId: string;
version: string;
branch: string;
buildNum: number;
buildSha: string;
basePath: string;
serverName: string;
devMode: boolean;
category?: AppCategory;
uiSettings: {
defaults: Record<string, UiSettingsParams>;
user?: Record<string, UserProvidedValues>;
@ -167,18 +154,6 @@ export interface InjectedMetadataSetup {
getPlugins: () => InjectedPluginMetadata[];
getAnonymousStatusPage: () => boolean;
getLegacyMetadata: () => {
app: {
id: string;
title: string;
};
bundleId: string;
version: string;
branch: string;
buildNum: number;
buildSha: string;
basePath: string;
serverName: string;
devMode: boolean;
uiSettings: {
defaults: Record<string, UiSettingsParams>;
user?: Record<string, UserProvidedValues> | undefined;

View file

@ -339,14 +339,7 @@ export {
SavedObjectsMigrationVersion,
} from './types';
export {
LegacyServiceSetupDeps,
LegacyServiceStartDeps,
LegacyServiceDiscoverPlugins,
LegacyConfig,
LegacyUiExports,
LegacyInternals,
} from './legacy';
export { LegacyServiceSetupDeps, LegacyServiceStartDeps, LegacyConfig } from './legacy';
export {
CoreStatus,

View file

@ -39,17 +39,12 @@ describe('ensureValidConfiguration', () => {
configService as any,
{
settings: 'settings',
pluginSpecs: 'pluginSpecs',
disabledPluginSpecs: 'disabledPluginSpecs',
pluginExtendedConfig: 'pluginExtendedConfig',
uiExports: 'uiExports',
legacyConfig: 'pluginExtendedConfig',
} as any
);
expect(getUnusedConfigKeys).toHaveBeenCalledTimes(1);
expect(getUnusedConfigKeys).toHaveBeenCalledWith({
coreHandledConfigPaths: ['core', 'elastic'],
pluginSpecs: 'pluginSpecs',
disabledPluginSpecs: 'disabledPluginSpecs',
settings: 'settings',
legacyConfig: 'pluginExtendedConfig',
});

View file

@ -19,19 +19,17 @@
import { getUnusedConfigKeys } from './get_unused_config_keys';
import { ConfigService } from '../../config';
import { LegacyServiceDiscoverPlugins } from '../types';
import { CriticalError } from '../../errors';
import { LegacyServiceSetupConfig } from '../types';
export async function ensureValidConfiguration(
configService: ConfigService,
{ pluginSpecs, disabledPluginSpecs, pluginExtendedConfig, settings }: LegacyServiceDiscoverPlugins
{ legacyConfig, settings }: LegacyServiceSetupConfig
) {
const unusedConfigKeys = await getUnusedConfigKeys({
coreHandledConfigPaths: await configService.getUsedPaths(),
pluginSpecs,
disabledPluginSpecs,
settings,
legacyConfig: pluginExtendedConfig,
legacyConfig,
});
if (unusedConfigKeys.length > 0) {

View file

@ -17,7 +17,7 @@
* under the License.
*/
import { LegacyPluginSpec, LegacyConfig, LegacyVars } from '../types';
import { LegacyConfig, LegacyVars } from '../types';
import { getUnusedConfigKeys } from './get_unused_config_keys';
describe('getUnusedConfigKeys', () => {
@ -35,8 +35,6 @@ describe('getUnusedConfigKeys', () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: [],
pluginSpecs: [],
disabledPluginSpecs: [],
settings: {},
legacyConfig: getConfig(),
})
@ -47,8 +45,6 @@ describe('getUnusedConfigKeys', () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: [],
pluginSpecs: [],
disabledPluginSpecs: [],
settings: {
presentInBoth: true,
alsoInBoth: 'someValue',
@ -65,8 +61,6 @@ describe('getUnusedConfigKeys', () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: [],
pluginSpecs: [],
disabledPluginSpecs: [],
settings: {
presentInBoth: true,
},
@ -82,8 +76,6 @@ describe('getUnusedConfigKeys', () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: [],
pluginSpecs: [],
disabledPluginSpecs: [],
settings: {
presentInBoth: true,
onlyInSetting: 'value',
@ -99,8 +91,6 @@ describe('getUnusedConfigKeys', () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: [],
pluginSpecs: [],
disabledPluginSpecs: [],
settings: {
elasticsearch: {
username: 'foo',
@ -121,8 +111,6 @@ describe('getUnusedConfigKeys', () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: [],
pluginSpecs: [],
disabledPluginSpecs: [],
settings: {
env: 'development',
},
@ -139,8 +127,6 @@ describe('getUnusedConfigKeys', () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: [],
pluginSpecs: [],
disabledPluginSpecs: [],
settings: {
prop: ['a', 'b', 'c'],
},
@ -152,40 +138,10 @@ describe('getUnusedConfigKeys', () => {
});
});
it('ignores config for plugins that are disabled', async () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: [],
pluginSpecs: [],
disabledPluginSpecs: [
({
id: 'foo',
getConfigPrefix: () => 'foo.bar',
} as unknown) as LegacyPluginSpec,
],
settings: {
foo: {
bar: {
unused: true,
},
},
plugin: {
missingProp: false,
},
},
legacyConfig: getConfig({
prop: 'a',
}),
})
).toEqual(['plugin.missingProp']);
});
it('ignores properties managed by the new platform', async () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: ['core', 'foo.bar'],
pluginSpecs: [],
disabledPluginSpecs: [],
settings: {
core: {
prop: 'value',
@ -204,8 +160,6 @@ describe('getUnusedConfigKeys', () => {
expect(
await getUnusedConfigKeys({
coreHandledConfigPaths: ['core', 'array'],
pluginSpecs: [],
disabledPluginSpecs: [],
settings: {
core: {
prop: 'value',

View file

@ -19,30 +19,20 @@
import { difference } from 'lodash';
import { getFlattenedObject } from '@kbn/std';
import { unset } from '../../../../legacy/utils';
import { hasConfigPathIntersection } from '../../config';
import { LegacyPluginSpec, LegacyConfig, LegacyVars } from '../types';
import { LegacyConfig, LegacyVars } from '../types';
const getFlattenedKeys = (object: object) => Object.keys(getFlattenedObject(object));
export async function getUnusedConfigKeys({
coreHandledConfigPaths,
pluginSpecs,
disabledPluginSpecs,
settings,
legacyConfig,
}: {
coreHandledConfigPaths: string[];
pluginSpecs: LegacyPluginSpec[];
disabledPluginSpecs: LegacyPluginSpec[];
settings: LegacyVars;
legacyConfig: LegacyConfig;
}) {
// remove config values from disabled plugins
for (const spec of disabledPluginSpecs) {
unset(settings, spec.getConfigPrefix());
}
const inputKeys = getFlattenedKeys(settings);
const appliedKeys = getFlattenedKeys(legacyConfig.get());

View file

@ -20,8 +20,6 @@
/** @internal */
export { ensureValidConfiguration } from './config';
/** @internal */
export { LegacyInternals } from './legacy_internals';
/** @internal */
export { LegacyService, ILegacyService } from './legacy_service';
/** @internal */
export * from './types';

View file

@ -1,211 +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 { Server } from 'hapi';
import { configMock } from '../config/mocks';
import { httpServiceMock } from '../http/http_service.mock';
import { httpServerMock } from '../http/http_server.mocks';
import { findLegacyPluginSpecsMock } from './legacy_service.test.mocks';
import { LegacyInternals } from './legacy_internals';
import { ILegacyInternals, LegacyConfig, LegacyVars, LegacyUiExports } from './types';
function varsProvider(vars: LegacyVars, configValue?: any) {
return {
fn: jest.fn().mockReturnValue(vars),
pluginSpec: {
readConfigValue: jest.fn().mockReturnValue(configValue),
},
};
}
describe('LegacyInternals', () => {
describe('getInjectedUiAppVars()', () => {
let uiExports: LegacyUiExports;
let config: LegacyConfig;
let server: Server;
let legacyInternals: ILegacyInternals;
beforeEach(async () => {
uiExports = findLegacyPluginSpecsMock().uiExports;
config = configMock.create() as any;
server = httpServiceMock.createInternalSetupContract().server;
legacyInternals = new LegacyInternals(uiExports, config, server);
});
it('gets with no injectors', async () => {
await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot(
`Object {}`
);
});
it('gets with no matching injectors', async () => {
const injector = jest.fn().mockResolvedValue({ not: 'core' });
legacyInternals.injectUiAppVars('not-core', injector);
await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot(
`Object {}`
);
expect(injector).not.toHaveBeenCalled();
});
it('gets with single matching injector', async () => {
const injector = jest.fn().mockResolvedValue({ is: 'core' });
legacyInternals.injectUiAppVars('core', injector);
await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot(`
Object {
"is": "core",
}
`);
expect(injector).toHaveBeenCalled();
});
it('gets with multiple matching injectors', async () => {
const injectors = [
jest.fn().mockResolvedValue({ is: 'core' }),
jest.fn().mockReturnValue({ sync: 'injector' }),
jest.fn().mockResolvedValue({ is: 'merged-core' }),
];
injectors.forEach((injector) => legacyInternals.injectUiAppVars('core', injector));
await expect(legacyInternals.getInjectedUiAppVars('core')).resolves.toMatchInlineSnapshot(`
Object {
"is": "merged-core",
"sync": "injector",
}
`);
expect(injectors[0]).toHaveBeenCalled();
expect(injectors[1]).toHaveBeenCalled();
expect(injectors[2]).toHaveBeenCalled();
});
});
describe('getVars()', () => {
let uiExports: LegacyUiExports;
let config: LegacyConfig;
let server: Server;
let legacyInternals: LegacyInternals;
beforeEach(async () => {
uiExports = findLegacyPluginSpecsMock().uiExports;
config = configMock.create() as any;
server = httpServiceMock.createInternalSetupContract().server;
legacyInternals = new LegacyInternals(uiExports, config, server);
});
it('gets: no default injectors, no injected vars replacers, no ui app injectors, no inject arg', async () => {
const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest());
expect(vars).toMatchInlineSnapshot(`Object {}`);
});
it('gets: with default injectors, no injected vars replacers, no ui app injectors, no inject arg', async () => {
uiExports.defaultInjectedVarProviders = [
varsProvider({ alpha: 'alpha' }),
varsProvider({ gamma: 'gamma' }),
varsProvider({ alpha: 'beta' }),
];
const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest());
expect(vars).toMatchInlineSnapshot(`
Object {
"alpha": "beta",
"gamma": "gamma",
}
`);
});
it('gets: no default injectors, with injected vars replacers, with ui app injectors, no inject arg', async () => {
uiExports.injectedVarsReplacers = [
jest.fn(async (vars) => ({ ...vars, added: 'key' })),
jest.fn((vars) => vars),
jest.fn((vars) => ({ replaced: 'all' })),
jest.fn(async (vars) => ({ ...vars, added: 'last-key' })),
];
const request = httpServerMock.createRawRequest();
const vars = await legacyInternals.getVars('core', request);
expect(vars).toMatchInlineSnapshot(`
Object {
"added": "last-key",
"replaced": "all",
}
`);
});
it('gets: no default injectors, no injected vars replacers, with ui app injectors, no inject arg', async () => {
legacyInternals.injectUiAppVars('core', async () => ({ is: 'core' }));
legacyInternals.injectUiAppVars('core', () => ({ sync: 'injector' }));
legacyInternals.injectUiAppVars('core', async () => ({ is: 'merged-core' }));
const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest());
expect(vars).toMatchInlineSnapshot(`
Object {
"is": "merged-core",
"sync": "injector",
}
`);
});
it('gets: no default injectors, no injected vars replacers, no ui app injectors, with inject arg', async () => {
const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest(), {
injected: 'arg',
});
expect(vars).toMatchInlineSnapshot(`
Object {
"injected": "arg",
}
`);
});
it('gets: with default injectors, with injected vars replacers, with ui app injectors, with inject arg', async () => {
uiExports.defaultInjectedVarProviders = [
varsProvider({ alpha: 'alpha' }),
varsProvider({ gamma: 'gamma' }),
varsProvider({ alpha: 'beta' }),
];
uiExports.injectedVarsReplacers = [jest.fn(async (vars) => ({ ...vars, gamma: 'delta' }))];
legacyInternals.injectUiAppVars('core', async () => ({ is: 'core' }));
legacyInternals.injectUiAppVars('core', () => ({ sync: 'injector' }));
legacyInternals.injectUiAppVars('core', async () => ({ is: 'merged-core' }));
const vars = await legacyInternals.getVars('core', httpServerMock.createRawRequest(), {
injected: 'arg',
sync: 'arg',
});
expect(vars).toMatchInlineSnapshot(`
Object {
"alpha": "beta",
"gamma": "delta",
"injected": "arg",
"is": "merged-core",
"sync": "arg",
}
`);
});
});
});

View file

@ -1,93 +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 { Server } from 'hapi';
import { KibanaRequest, LegacyRequest } from '../http';
import { ensureRawRequest } from '../http/router';
import { mergeVars } from './merge_vars';
import { ILegacyInternals, LegacyVars, VarsInjector, LegacyConfig, LegacyUiExports } from './types';
/**
* @internal
* @deprecated
*/
export class LegacyInternals implements ILegacyInternals {
private readonly injectors = new Map<string, Set<VarsInjector>>();
private cachedDefaultVars?: LegacyVars;
constructor(
private readonly uiExports: LegacyUiExports,
private readonly config: LegacyConfig,
private readonly server: Server
) {}
private get defaultVars(): LegacyVars {
if (this.cachedDefaultVars) {
return this.cachedDefaultVars;
}
const { defaultInjectedVarProviders = [] } = this.uiExports;
return (this.cachedDefaultVars = defaultInjectedVarProviders.reduce(
(vars, { fn, pluginSpec }) =>
mergeVars(vars, fn(this.server, pluginSpec.readConfigValue(this.config, []))),
{}
));
}
private replaceVars(vars: LegacyVars, request: KibanaRequest | LegacyRequest) {
const { injectedVarsReplacers = [] } = this.uiExports;
return injectedVarsReplacers.reduce(
async (injected, replacer) =>
replacer(await injected, ensureRawRequest(request), this.server),
Promise.resolve(vars)
);
}
public injectUiAppVars(id: string, injector: VarsInjector) {
if (!this.injectors.has(id)) {
this.injectors.set(id, new Set());
}
this.injectors.get(id)!.add(injector);
}
public getInjectedUiAppVars(id: string) {
return [...(this.injectors.get(id) || [])].reduce(
async (promise, injector) => ({
...(await promise),
...(await injector()),
}),
Promise.resolve<LegacyVars>({})
);
}
public async getVars(
id: string,
request: KibanaRequest | LegacyRequest,
injected: LegacyVars = {}
) {
return this.replaceVars(
mergeVars(this.defaultVars, await this.getInjectedUiAppVars(id), injected),
request
);
}
}

View file

@ -18,26 +18,13 @@
*/
import type { PublicMethodsOf } from '@kbn/utility-types';
import { LegacyService } from './legacy_service';
import { LegacyConfig, LegacyServiceDiscoverPlugins, LegacyServiceSetupDeps } from './types';
import { LegacyConfig, LegacyServiceSetupDeps } from './types';
type LegacyServiceMock = jest.Mocked<PublicMethodsOf<LegacyService> & { legacyId: symbol }>;
const createDiscoverPluginsMock = (): LegacyServiceDiscoverPlugins => ({
pluginSpecs: [],
uiExports: {},
navLinks: [],
pluginExtendedConfig: {
get: jest.fn(),
has: jest.fn(),
set: jest.fn(),
},
disabledPluginSpecs: [],
settings: {},
});
const createLegacyServiceMock = (): LegacyServiceMock => ({
legacyId: Symbol(),
discoverPlugins: jest.fn().mockResolvedValue(createDiscoverPluginsMock()),
setupLegacyConfig: jest.fn(),
setup: jest.fn(),
start: jest.fn(),
stop: jest.fn(),
@ -52,6 +39,5 @@ const createLegacyConfigMock = (): jest.Mocked<LegacyConfig> => ({
export const legacyServiceMock = {
create: createLegacyServiceMock,
createSetupContract: (deps: LegacyServiceSetupDeps) => createLegacyServiceMock().setup(deps),
createDiscoverPlugins: createDiscoverPluginsMock,
createLegacyConfig: createLegacyConfigMock,
};

View file

@ -1,40 +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 { LegacyVars } from './types';
export const findLegacyPluginSpecsMock = jest.fn().mockImplementation((settings: LegacyVars) => ({
pluginSpecs: [],
pluginExtendedConfig: {
has: jest.fn(),
get: jest.fn().mockReturnValue(settings),
set: jest.fn(),
},
disabledPluginSpecs: [],
uiExports: {},
navLinks: [],
}));
jest.doMock('./plugins/find_legacy_plugin_specs', () => ({
findLegacyPluginSpecs: findLegacyPluginSpecsMock,
}));
export const logLegacyThirdPartyPluginDeprecationWarningMock = jest.fn();
jest.doMock('./plugins/log_legacy_plugins_warning', () => ({
logLegacyThirdPartyPluginDeprecationWarning: logLegacyThirdPartyPluginDeprecationWarningMock,
}));

View file

@ -19,10 +19,6 @@
jest.mock('../../../legacy/server/kbn_server');
jest.mock('./cluster_manager');
import {
findLegacyPluginSpecsMock,
logLegacyThirdPartyPluginDeprecationWarningMock,
} from './legacy_service.test.mocks';
import { BehaviorSubject, throwError } from 'rxjs';
import { REPO_ROOT } from '@kbn/dev-utils';
@ -44,8 +40,7 @@ import { capabilitiesServiceMock } from '../capabilities/capabilities_service.mo
import { httpResourcesMock } from '../http_resources/http_resources_service.mock';
import { setupMock as renderingServiceMock } from '../rendering/__mocks__/rendering_service';
import { environmentServiceMock } from '../environment/environment_service.mock';
import { findLegacyPluginSpecs } from './plugins';
import { LegacyVars, LegacyServiceSetupDeps, LegacyServiceStartDeps } from './types';
import { LegacyServiceSetupDeps, LegacyServiceStartDeps } from './types';
import { LegacyService } from './legacy_service';
import { coreMock } from '../mocks';
import { statusServiceMock } from '../status/status_service.mock';
@ -73,7 +68,6 @@ beforeEach(() => {
configService = configServiceMock.create();
environmentSetup = environmentServiceMock.createSetupContract();
findLegacyPluginSpecsMock.mockClear();
MockKbnServer.prototype.ready = jest.fn().mockReturnValue(Promise.resolve());
MockKbnServer.prototype.listen = jest.fn();
@ -149,10 +143,10 @@ describe('once LegacyService is set up with connection info', () => {
coreId,
env,
logger,
configService: configService as any,
configService,
});
await legacyService.discoverPlugins();
await legacyService.setupLegacyConfig();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
@ -160,13 +154,14 @@ describe('once LegacyService is set up with connection info', () => {
expect(MockKbnServer).toHaveBeenCalledWith(
{ 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: {}, navLinks: [] }
expect.any(Object)
);
expect(MockKbnServer.mock.calls[0][1].get()).toEqual(
expect.objectContaining({
path: expect.objectContaining({ autoListen: true }),
server: expect.objectContaining({ autoListen: true }),
})
);
expect(MockKbnServer.mock.calls[0][1].get()).toEqual({
path: { autoListen: true },
server: { autoListen: true },
});
const [mockKbnServer] = MockKbnServer.mock.instances;
expect(mockKbnServer.listen).toHaveBeenCalledTimes(1);
@ -182,7 +177,7 @@ describe('once LegacyService is set up with connection info', () => {
logger,
configService: configService as any,
});
await legacyService.discoverPlugins();
await legacyService.setupLegacyConfig();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
@ -190,13 +185,12 @@ describe('once LegacyService is set up with connection info', () => {
expect(MockKbnServer).toHaveBeenCalledWith(
{ path: { autoListen: false }, server: { autoListen: true } },
expect.objectContaining({ get: expect.any(Function) }),
expect.any(Object),
{ disabledPluginSpecs: [], pluginSpecs: [], uiExports: {}, navLinks: [] }
expect.any(Object)
);
expect(MockKbnServer.mock.calls[0][1].get()).toEqual({
path: { autoListen: false },
server: { autoListen: true },
});
const legacyConfig = MockKbnServer.mock.calls[0][1].get();
expect(legacyConfig.path.autoListen).toBe(false);
expect(legacyConfig.server.autoListen).toBe(true);
const [mockKbnServer] = MockKbnServer.mock.instances;
expect(mockKbnServer.ready).toHaveBeenCalledTimes(1);
@ -214,7 +208,7 @@ describe('once LegacyService is set up with connection info', () => {
configService: configService as any,
});
await legacyService.discoverPlugins();
await legacyService.setupLegacyConfig();
await legacyService.setup(setupDeps);
await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingInlineSnapshot(
`"something failed"`
@ -234,11 +228,11 @@ describe('once LegacyService is set up with connection info', () => {
configService: configService as any,
});
await expect(legacyService.discoverPlugins()).rejects.toThrowErrorMatchingInlineSnapshot(
await expect(legacyService.setupLegacyConfig()).rejects.toThrowErrorMatchingInlineSnapshot(
`"something failed"`
);
await expect(legacyService.setup(setupDeps)).rejects.toThrowErrorMatchingInlineSnapshot(
`"Legacy service has not discovered legacy plugins yet. Ensure LegacyService.discoverPlugins() is called before LegacyService.setup()"`
`"Legacy config not initialized yet. Ensure LegacyService.setupLegacyConfig() is called before LegacyService.setup()"`
);
await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingInlineSnapshot(
`"Legacy service is not setup yet."`
@ -255,7 +249,7 @@ describe('once LegacyService is set up with connection info', () => {
logger,
configService: configService as any,
});
await legacyService.discoverPlugins();
await legacyService.setupLegacyConfig();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
@ -276,7 +270,7 @@ describe('once LegacyService is set up with connection info', () => {
logger,
configService: configService as any,
});
await legacyService.discoverPlugins();
await legacyService.setupLegacyConfig();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
@ -301,7 +295,7 @@ describe('once LegacyService is set up with connection info', () => {
logger,
configService: configService as any,
});
await legacyService.discoverPlugins();
await legacyService.setupLegacyConfig();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
@ -321,7 +315,7 @@ describe('once LegacyService is set up without connection info', () => {
let legacyService: LegacyService;
beforeEach(async () => {
legacyService = new LegacyService({ coreId, env, logger, configService: configService as any });
await legacyService.discoverPlugins();
await legacyService.setupLegacyConfig();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
});
@ -331,13 +325,13 @@ describe('once LegacyService is set up without connection info', () => {
expect(MockKbnServer).toHaveBeenCalledWith(
{ path: {}, server: { autoListen: true } },
expect.objectContaining({ get: expect.any(Function) }),
expect.any(Object),
{ disabledPluginSpecs: [], pluginSpecs: [], uiExports: {}, navLinks: [] }
expect.any(Object)
);
expect(MockKbnServer.mock.calls[0][1].get()).toEqual(
expect.objectContaining({
server: expect.objectContaining({ autoListen: true }),
})
);
expect(MockKbnServer.mock.calls[0][1].get()).toEqual({
path: {},
server: { autoListen: true },
});
});
test('reconfigures logging configuration if new config is received.', async () => {
@ -375,7 +369,7 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => {
configService: configService as any,
});
await devClusterLegacyService.discoverPlugins();
await devClusterLegacyService.setupLegacyConfig();
await devClusterLegacyService.setup(setupDeps);
await devClusterLegacyService.start(startDeps);
@ -404,7 +398,7 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => {
configService: configService as any,
});
await devClusterLegacyService.discoverPlugins();
await devClusterLegacyService.setupLegacyConfig();
await devClusterLegacyService.setup(setupDeps);
await devClusterLegacyService.start(startDeps);
@ -434,50 +428,6 @@ describe('start', () => {
});
});
describe('#discoverPlugins()', () => {
it('calls findLegacyPluginSpecs with correct parameters', async () => {
const legacyService = new LegacyService({
coreId,
env,
logger,
configService: configService as any,
});
await legacyService.discoverPlugins();
expect(findLegacyPluginSpecs).toHaveBeenCalledTimes(1);
expect(findLegacyPluginSpecs).toHaveBeenCalledWith(expect.any(Object), logger, env.packageInfo);
});
it(`logs deprecations for legacy third party plugins`, async () => {
const pluginSpecs = [{ getId: () => 'pluginA' }, { getId: () => 'pluginB' }];
findLegacyPluginSpecsMock.mockImplementation(
(settings) =>
Promise.resolve({
pluginSpecs,
pluginExtendedConfig: settings,
disabledPluginSpecs: [],
uiExports: {},
navLinks: [],
}) as any
);
const legacyService = new LegacyService({
coreId,
env,
logger,
configService: configService as any,
});
await legacyService.discoverPlugins();
expect(logLegacyThirdPartyPluginDeprecationWarningMock).toHaveBeenCalledTimes(1);
expect(logLegacyThirdPartyPluginDeprecationWarningMock).toHaveBeenCalledWith({
specs: pluginSpecs,
log: expect.any(Object),
});
});
});
test('Sets the server.uuid property on the legacy configuration', async () => {
configService.atPath.mockReturnValue(new BehaviorSubject({ autoListen: true }));
const legacyService = new LegacyService({
@ -489,23 +439,8 @@ test('Sets the server.uuid property on the legacy configuration', async () => {
environmentSetup.instanceUuid = 'UUID_FROM_SERVICE';
const configSetMock = jest.fn();
findLegacyPluginSpecsMock.mockImplementation((settings: LegacyVars) => ({
pluginSpecs: [],
pluginExtendedConfig: {
has: jest.fn(),
get: jest.fn().mockReturnValue(settings),
set: configSetMock,
},
disabledPluginSpecs: [],
uiExports: {},
navLinks: [],
}));
await legacyService.discoverPlugins();
const { legacyConfig } = await legacyService.setupLegacyConfig();
await legacyService.setup(setupDeps);
expect(configSetMock).toHaveBeenCalledTimes(1);
expect(configSetMock).toHaveBeenCalledWith('server.uuid', 'UUID_FROM_SERVICE');
expect(legacyConfig.get('server.uuid')).toBe('UUID_FROM_SERVICE');
});

View file

@ -16,11 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
import type { PublicMethodsOf } from '@kbn/utility-types';
import { combineLatest, ConnectableObservable, EMPTY, Observable, Subscription } from 'rxjs';
import { first, map, publishReplay, tap } from 'rxjs/operators';
import type { PublicMethodsOf } from '@kbn/utility-types';
import { PathConfigType } from '@kbn/utils';
// @ts-expect-error legacy config class
import { Config as LegacyConfigClass } from '../../../legacy/server/config';
import { CoreService } from '../../types';
import { Config } from '../config';
import { CoreContext } from '../core_context';
@ -28,17 +31,7 @@ import { CspConfigType, config as cspConfig } from '../csp';
import { DevConfig, DevConfigType, config as devConfig } from '../dev';
import { BasePathProxyServer, HttpConfig, HttpConfigType, config as httpConfig } from '../http';
import { Logger } from '../logging';
import { findLegacyPluginSpecs, logLegacyThirdPartyPluginDeprecationWarning } from './plugins';
import {
ILegacyInternals,
LegacyServiceSetupDeps,
LegacyServiceStartDeps,
LegacyPlugins,
LegacyServiceDiscoverPlugins,
LegacyConfig,
LegacyVars,
} from './types';
import { LegacyInternals } from './legacy_internals';
import { LegacyServiceSetupDeps, LegacyServiceStartDeps, LegacyConfig, LegacyVars } from './types';
import { CoreSetup, CoreStart } from '..';
interface LegacyKbnServer {
@ -80,9 +73,7 @@ export class LegacyService implements CoreService {
private setupDeps?: LegacyServiceSetupDeps;
private update$?: ConnectableObservable<[Config, PathConfigType]>;
private legacyRawConfig?: LegacyConfig;
private legacyPlugins?: LegacyPlugins;
private settings?: LegacyVars;
public legacyInternals?: ILegacyInternals;
constructor(private readonly coreContext: CoreContext) {
const { logger, configService } = coreContext;
@ -97,11 +88,11 @@ export class LegacyService implements CoreService {
).pipe(map(([http, csp]) => new HttpConfig(http, csp)));
}
public async discoverPlugins(): Promise<LegacyServiceDiscoverPlugins> {
this.update$ = combineLatest(
public async setupLegacyConfig() {
this.update$ = combineLatest([
this.coreContext.configService.getConfig$(),
this.coreContext.configService.atPath<PathConfigType>('path')
).pipe(
this.coreContext.configService.atPath<PathConfigType>('path'),
]).pipe(
tap(([config, pathConfig]) => {
if (this.kbnServer !== undefined) {
this.kbnServer.applyLoggingConfiguration(getLegacyRawConfig(config, pathConfig));
@ -120,74 +111,33 @@ export class LegacyService implements CoreService {
)
.toPromise();
const {
pluginSpecs,
pluginExtendedConfig,
disabledPluginSpecs,
uiExports,
navLinks,
} = await findLegacyPluginSpecs(
this.settings,
this.coreContext.logger,
this.coreContext.env.packageInfo
);
logLegacyThirdPartyPluginDeprecationWarning({
specs: pluginSpecs,
log: this.log,
});
this.legacyPlugins = {
pluginSpecs,
disabledPluginSpecs,
uiExports,
navLinks,
};
this.legacyRawConfig = pluginExtendedConfig;
// check for unknown uiExport types
if (uiExports.unknown && uiExports.unknown.length > 0) {
throw new Error(
`Unknown uiExport types: ${uiExports.unknown
.map(({ pluginSpec, type }) => `${type} from ${pluginSpec.getId()}`)
.join(', ')}`
);
}
this.legacyRawConfig = LegacyConfigClass.withDefaultSchema(this.settings);
return {
pluginSpecs,
disabledPluginSpecs,
uiExports,
navLinks,
pluginExtendedConfig,
settings: this.settings,
legacyConfig: this.legacyRawConfig!,
};
}
public async setup(setupDeps: LegacyServiceSetupDeps) {
this.log.debug('setting up legacy service');
if (!this.legacyPlugins) {
if (!this.legacyRawConfig) {
throw new Error(
'Legacy service has not discovered legacy plugins yet. Ensure LegacyService.discoverPlugins() is called before LegacyService.setup()'
'Legacy config not initialized yet. Ensure LegacyService.setupLegacyConfig() 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.environment.instanceUuid);
this.setupDeps = setupDeps;
this.legacyInternals = new LegacyInternals(
this.legacyPlugins.uiExports,
this.legacyRawConfig!,
setupDeps.core.http.server
);
}
public async start(startDeps: LegacyServiceStartDeps) {
const { setupDeps } = this;
if (!setupDeps || !this.legacyPlugins) {
if (!setupDeps || !this.legacyRawConfig) {
throw new Error('Legacy service is not setup yet.');
}
@ -201,8 +151,7 @@ export class LegacyService implements CoreService {
this.settings!,
this.legacyRawConfig!,
setupDeps,
startDeps,
this.legacyPlugins!
startDeps
);
}
}
@ -245,8 +194,7 @@ export class LegacyService implements CoreService {
settings: LegacyVars,
config: LegacyConfig,
setupDeps: LegacyServiceSetupDeps,
startDeps: LegacyServiceStartDeps,
legacyPlugins: LegacyPlugins
startDeps: LegacyServiceStartDeps
) {
const coreStart: CoreStart = {
capabilities: startDeps.core.capabilities,
@ -337,36 +285,26 @@ export class LegacyService implements CoreService {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const KbnServer = require('../../../legacy/server/kbn_server');
const kbnServer: LegacyKbnServer = new KbnServer(
settings,
config,
{
env: {
mode: this.coreContext.env.mode,
packageInfo: this.coreContext.env.packageInfo,
},
setupDeps: {
core: coreSetup,
plugins: setupDeps.plugins,
},
startDeps: {
core: coreStart,
plugins: startDeps.plugins,
},
__internals: {
http: {
registerStaticDir: setupDeps.core.http.registerStaticDir,
},
hapiServer: setupDeps.core.http.server,
uiPlugins: setupDeps.uiPlugins,
elasticsearch: setupDeps.core.elasticsearch,
rendering: setupDeps.core.rendering,
legacy: this.legacyInternals,
},
logger: this.coreContext.logger,
const kbnServer: LegacyKbnServer = new KbnServer(settings, config, {
env: {
mode: this.coreContext.env.mode,
packageInfo: this.coreContext.env.packageInfo,
},
legacyPlugins
);
setupDeps: {
core: coreSetup,
plugins: setupDeps.plugins,
},
startDeps: {
core: coreStart,
plugins: startDeps.plugins,
},
__internals: {
hapiServer: setupDeps.core.http.server,
uiPlugins: setupDeps.uiPlugins,
rendering: setupDeps.core.rendering,
},
logger: this.coreContext.logger,
});
// The kbnWorkerType check is necessary to prevent the repl
// from being started multiple times in different processes.

View file

@ -1,21 +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.
*/
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
export { collectUiExports } from '../../../../legacy/ui/ui_exports/collect_ui_exports';

View file

@ -1,135 +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 { Observable, merge, forkJoin } from 'rxjs';
import { toArray, tap, distinct, map } from 'rxjs/operators';
import {
findPluginSpecs,
defaultConfig,
// @ts-expect-error
} from '../../../../legacy/plugin_discovery/find_plugin_specs.js';
// @ts-expect-error
import { collectUiExports as collectLegacyUiExports } from './collect_ui_exports';
import { LoggerFactory } from '../../logging';
import { PackageInfo } from '../../config';
import { LegacyUiExports, LegacyPluginSpec, LegacyPluginPack, LegacyConfig } from '../types';
export async function findLegacyPluginSpecs(
settings: unknown,
loggerFactory: LoggerFactory,
packageInfo: PackageInfo
) {
const configToMutate: LegacyConfig = defaultConfig(settings);
const {
pack$,
invalidDirectoryError$,
invalidPackError$,
otherError$,
deprecation$,
invalidVersionSpec$,
spec$,
disabledSpec$,
}: {
pack$: Observable<LegacyPluginPack>;
invalidDirectoryError$: Observable<{ path: string }>;
invalidPackError$: Observable<{ path: string }>;
otherError$: Observable<unknown>;
deprecation$: Observable<{ spec: LegacyPluginSpec; message: string }>;
invalidVersionSpec$: Observable<LegacyPluginSpec>;
spec$: Observable<LegacyPluginSpec>;
disabledSpec$: Observable<LegacyPluginSpec>;
} = findPluginSpecs(settings, configToMutate) as any;
const logger = loggerFactory.get('legacy-plugins');
const log$ = merge(
pack$.pipe(
tap((definition) => {
const path = definition.getPath();
logger.debug(`Found plugin at ${path}`, { path });
})
),
invalidDirectoryError$.pipe(
tap((error) => {
logger.warn(`Unable to scan directory for plugins "${error.path}"`, {
err: error,
dir: error.path,
});
})
),
invalidPackError$.pipe(
tap((error) => {
logger.warn(`Skipping non-plugin directory at ${error.path}`, {
path: error.path,
});
})
),
otherError$.pipe(
tap((error) => {
// rethrow unhandled errors, which will fail the server
throw error;
})
),
invalidVersionSpec$.pipe(
map((spec) => {
const name = spec.getId();
const pluginVersion = spec.getExpectedKibanaVersion();
const kibanaVersion = packageInfo.version;
return `Plugin "${name}" was disabled because it expected Kibana version "${pluginVersion}", and found "${kibanaVersion}".`;
}),
distinct(),
tap((message) => {
logger.warn(message);
})
),
deprecation$.pipe(
tap(({ spec, message }) => {
const deprecationLogger = loggerFactory.get(
'plugins',
spec.getConfigPrefix(),
'config',
'deprecation'
);
deprecationLogger.warn(message);
})
)
);
const [disabledPluginSpecs, pluginSpecs] = await forkJoin(
disabledSpec$.pipe(toArray()),
spec$.pipe(toArray()),
log$.pipe(toArray())
).toPromise();
const uiExports: LegacyUiExports = collectLegacyUiExports(pluginSpecs);
return {
disabledPluginSpecs,
pluginSpecs,
pluginExtendedConfig: configToMutate,
uiExports,
navLinks: [],
};
}

View file

@ -1,21 +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.
*/
export { findLegacyPluginSpecs } from './find_legacy_plugin_specs';
export { logLegacyThirdPartyPluginDeprecationWarning } from './log_legacy_plugins_warning';

View file

@ -1,89 +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 { loggerMock } from '../../logging/logger.mock';
import { logLegacyThirdPartyPluginDeprecationWarning } from './log_legacy_plugins_warning';
import { LegacyPluginSpec } from '../types';
const createPluginSpec = ({ id, path }: { id: string; path: string }): LegacyPluginSpec => {
return {
getId: () => id,
getExpectedKibanaVersion: () => 'kibana',
getConfigPrefix: () => 'plugin.config',
getPack: () => ({
getPath: () => path,
}),
};
};
describe('logLegacyThirdPartyPluginDeprecationWarning', () => {
let log: ReturnType<typeof loggerMock.create>;
beforeEach(() => {
log = loggerMock.create();
});
it('logs warning for third party plugins', () => {
logLegacyThirdPartyPluginDeprecationWarning({
specs: [createPluginSpec({ id: 'plugin', path: '/some-external-path' })],
log,
});
expect(log.warn).toHaveBeenCalledTimes(1);
expect(log.warn.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"Some installed third party plugin(s) [plugin] are using the legacy plugin format and will no longer work in a future Kibana release. Please refer to https://ela.st/kibana-breaking-changes-8-0 for a list of breaking changes and https://ela.st/kibana-platform-migration for documentation on how to migrate legacy plugins.",
]
`);
});
it('lists all the deprecated plugins and only log once', () => {
logLegacyThirdPartyPluginDeprecationWarning({
specs: [
createPluginSpec({ id: 'pluginA', path: '/abs/path/to/pluginA' }),
createPluginSpec({ id: 'pluginB', path: '/abs/path/to/pluginB' }),
createPluginSpec({ id: 'pluginC', path: '/abs/path/to/pluginC' }),
],
log,
});
expect(log.warn).toHaveBeenCalledTimes(1);
expect(log.warn.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"Some installed third party plugin(s) [pluginA, pluginB, pluginC] are using the legacy plugin format and will no longer work in a future Kibana release. Please refer to https://ela.st/kibana-breaking-changes-8-0 for a list of breaking changes and https://ela.st/kibana-platform-migration for documentation on how to migrate legacy plugins.",
]
`);
});
it('does not log warning for internal legacy plugins', () => {
logLegacyThirdPartyPluginDeprecationWarning({
specs: [
createPluginSpec({
id: 'plugin',
path: '/absolute/path/to/kibana/src/legacy/core_plugins',
}),
createPluginSpec({
id: 'plugin',
path: '/absolute/path/to/kibana/x-pack',
}),
],
log,
});
expect(log.warn).not.toHaveBeenCalled();
});
});

View file

@ -1,53 +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 { Logger } from '../../logging';
import { LegacyPluginSpec } from '../types';
const internalPaths = ['/src/legacy/core_plugins', '/x-pack'];
// Use shortened URLs so destinations can be updated if/when documentation moves
// All platform team members have access to edit these
const breakingChangesUrl = 'https://ela.st/kibana-breaking-changes-8-0';
const migrationGuideUrl = 'https://ela.st/kibana-platform-migration';
export const logLegacyThirdPartyPluginDeprecationWarning = ({
specs,
log,
}: {
specs: LegacyPluginSpec[];
log: Logger;
}) => {
const thirdPartySpecs = specs.filter(isThirdPartyPluginSpec);
if (thirdPartySpecs.length > 0) {
const pluginIds = thirdPartySpecs.map((spec) => spec.getId());
log.warn(
`Some installed third party plugin(s) [${pluginIds.join(
', '
)}] are using the legacy plugin format and will no longer work in a future Kibana release. ` +
`Please refer to ${breakingChangesUrl} for a list of breaking changes ` +
`and ${migrationGuideUrl} for documentation on how to migrate legacy plugins.`
);
}
};
const isThirdPartyPluginSpec = (spec: LegacyPluginSpec): boolean => {
const pluginPath = spec.getPack().getPath();
return !internalPaths.some((internalPath) => pluginPath.indexOf(internalPath) > -1);
};

View file

@ -17,10 +17,6 @@
* under the License.
*/
import { Server } from 'hapi';
import { ChromeNavLink } from '../../public';
import { KibanaRequest, LegacyRequest } from '../http';
import { InternalCoreSetup, InternalCoreStart } from '../internal_types';
import { PluginsServiceSetup, PluginsServiceStart, UiPlugins } from '../plugins';
import { InternalRenderingServiceSetup } from '../rendering';
@ -50,91 +46,6 @@ export interface LegacyConfig {
set(config: LegacyVars): void;
}
/**
* @internal
* @deprecated
*/
export interface LegacyPluginPack {
getPath(): string;
}
/**
* @internal
* @deprecated
*/
export interface LegacyPluginSpec {
getId: () => unknown;
getExpectedKibanaVersion: () => string;
getConfigPrefix: () => string;
getPack: () => LegacyPluginPack;
}
/**
* @internal
* @deprecated
*/
export interface VarsProvider {
fn: (server: Server, configValue: any) => LegacyVars;
pluginSpec: {
readConfigValue(config: any, key: string | string[]): any;
};
}
/**
* @internal
* @deprecated
*/
export type VarsInjector = () => LegacyVars;
/**
* @internal
* @deprecated
*/
export type VarsReplacer = (
vars: LegacyVars,
request: LegacyRequest,
server: Server
) => LegacyVars | Promise<LegacyVars>;
/**
* @internal
* @deprecated
*/
export type LegacyNavLinkSpec = Partial<LegacyNavLink> & {
id: string;
title: string;
url: string;
};
/**
* @internal
* @deprecated
*/
export type LegacyAppSpec = Partial<LegacyNavLink> & {
pluginId?: string;
listed?: boolean;
};
/**
* @internal
* @deprecated
*/
export type LegacyNavLink = Omit<ChromeNavLink, 'baseUrl' | 'legacy' | 'order' | 'href'> & {
order: number;
};
/**
* @internal
* @deprecated
*/
export interface LegacyUiExports {
defaultInjectedVarProviders?: VarsProvider[];
injectedVarsReplacers?: VarsReplacer[];
navLinkSpecs?: LegacyNavLinkSpec[] | null;
uiAppSpecs?: Array<LegacyAppSpec | undefined>;
unknown?: [{ pluginSpec: LegacyPluginSpec; type: unknown }];
}
/**
* @public
* @deprecated
@ -158,43 +69,7 @@ export interface LegacyServiceStartDeps {
* @internal
* @deprecated
*/
export interface ILegacyInternals {
/**
* Inject UI app vars for a particular plugin
*/
injectUiAppVars(id: string, injector: VarsInjector): void;
/**
* Get all the merged injected UI app vars for a particular plugin
*/
getInjectedUiAppVars(id: string): Promise<LegacyVars>;
/**
* Get the metadata vars for a particular plugin
*/
getVars(
id: string,
request: KibanaRequest | LegacyRequest,
injected?: LegacyVars
): Promise<LegacyVars>;
}
/**
* @internal
* @deprecated
*/
export interface LegacyPlugins {
disabledPluginSpecs: LegacyPluginSpec[];
pluginSpecs: LegacyPluginSpec[];
uiExports: LegacyUiExports;
navLinks: LegacyNavLink[];
}
/**
* @internal
* @deprecated
*/
export interface LegacyServiceDiscoverPlugins extends LegacyPlugins {
pluginExtendedConfig: LegacyConfig;
export interface LegacyServiceSetupConfig {
legacyConfig: LegacyConfig;
settings: LegacyVars;
}

View file

@ -20,19 +20,16 @@
import { mockCoreContext } from '../../core_context.mock';
import { httpServiceMock } from '../../http/http_service.mock';
import { pluginServiceMock } from '../../plugins/plugins_service.mock';
import { legacyServiceMock } from '../../legacy/legacy_service.mock';
import { statusServiceMock } from '../../status/status_service.mock';
const context = mockCoreContext.create();
const http = httpServiceMock.createInternalSetupContract();
const uiPlugins = pluginServiceMock.createUiPlugins();
const legacyPlugins = legacyServiceMock.createDiscoverPlugins();
const status = statusServiceMock.createInternalSetupContract();
export const mockRenderingServiceParams = context;
export const mockRenderingSetupDeps = {
http,
legacyPlugins,
uiPlugins,
status,
};

View file

@ -27,11 +27,9 @@ export const setupMock: jest.Mocked<InternalRenderingServiceSetup> = {
render: jest.fn(),
};
export const mockSetup = jest.fn().mockResolvedValue(setupMock);
export const mockStart = jest.fn();
export const mockStop = jest.fn();
export const mockRenderingService: jest.Mocked<IRenderingService> = {
setup: mockSetup,
start: mockStart,
stop: mockStop,
};
export const RenderingService = jest.fn<IRenderingService, [typeof mockRenderingServiceParams]>(

View file

@ -27,15 +27,6 @@ Object {
"translationsUrl": "/mock-server-basepath/translations/en.json",
},
"legacyMetadata": Object {
"app": Object {},
"basePath": "/mock-server-basepath",
"branch": Any<String>,
"buildNum": Any<Number>,
"buildSha": Any<String>,
"bundleId": "app:core",
"devMode": true,
"nav": Array [],
"serverName": "http-server-test",
"uiSettings": Object {
"defaults": Object {
"registered": Object {
@ -44,7 +35,6 @@ Object {
},
"user": Object {},
},
"version": Any<String>,
},
"serverBasePath": "/mock-server-basepath",
"uiPlugins": Array [],
@ -80,15 +70,6 @@ Object {
"translationsUrl": "/mock-server-basepath/translations/en.json",
},
"legacyMetadata": Object {
"app": Object {},
"basePath": "/mock-server-basepath",
"branch": Any<String>,
"buildNum": Any<Number>,
"buildSha": Any<String>,
"bundleId": "app:core",
"devMode": true,
"nav": Array [],
"serverName": "http-server-test",
"uiSettings": Object {
"defaults": Object {
"registered": Object {
@ -97,7 +78,6 @@ Object {
},
"user": Object {},
},
"version": Any<String>,
},
"serverBasePath": "/mock-server-basepath",
"uiPlugins": Array [],
@ -133,15 +113,6 @@ Object {
"translationsUrl": "/mock-server-basepath/translations/en.json",
},
"legacyMetadata": Object {
"app": Object {},
"basePath": "/mock-server-basepath",
"branch": Any<String>,
"buildNum": Any<Number>,
"buildSha": Any<String>,
"bundleId": "app:core",
"devMode": true,
"nav": Array [],
"serverName": "http-server-test",
"uiSettings": Object {
"defaults": Object {
"registered": Object {
@ -154,7 +125,6 @@ Object {
},
},
},
"version": Any<String>,
},
"serverBasePath": "/mock-server-basepath",
"uiPlugins": Array [],
@ -190,15 +160,6 @@ Object {
"translationsUrl": "/translations/en.json",
},
"legacyMetadata": Object {
"app": Object {},
"basePath": "",
"branch": Any<String>,
"buildNum": Any<Number>,
"buildSha": Any<String>,
"bundleId": "app:core",
"devMode": true,
"nav": Array [],
"serverName": "http-server-test",
"uiSettings": Object {
"defaults": Object {
"registered": Object {
@ -207,7 +168,6 @@ Object {
},
"user": Object {},
},
"version": Any<String>,
},
"serverBasePath": "/mock-server-basepath",
"uiPlugins": Array [],
@ -243,15 +203,6 @@ Object {
"translationsUrl": "/mock-server-basepath/translations/en.json",
},
"legacyMetadata": Object {
"app": Object {},
"basePath": "/mock-server-basepath",
"branch": Any<String>,
"buildNum": Any<Number>,
"buildSha": Any<String>,
"bundleId": "app:core",
"devMode": true,
"nav": Array [],
"serverName": "http-server-test",
"uiSettings": Object {
"defaults": Object {
"registered": Object {
@ -260,7 +211,6 @@ Object {
},
"user": Object {},
},
"version": Any<String>,
},
"serverBasePath": "/mock-server-basepath",
"uiPlugins": Array [],

View file

@ -43,12 +43,6 @@ const INJECTED_METADATA = {
version: expect.any(String),
},
},
legacyMetadata: {
branch: expect.any(String),
buildNum: expect.any(Number),
buildSha: expect.any(String),
version: expect.any(String),
},
};
const { createKibanaRequest, createRawRequest } = httpServerMock;
@ -72,13 +66,6 @@ describe('RenderingService', () => {
registered: { name: 'title' },
});
render = (await service.setup(mockRenderingSetupDeps)).render;
await service.start({
legacy: {
legacyInternals: {
getVars: () => ({}),
},
},
} as any);
});
it('renders "core" page', async () => {

View file

@ -20,14 +20,11 @@
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { take } from 'rxjs/operators';
import { i18n } from '@kbn/i18n';
import { UiPlugins } from '../plugins';
import { CoreService } from '../../types';
import { CoreContext } from '../core_context';
import { Template } from './views';
import { LegacyService } from '../legacy';
import {
IRenderOptions,
RenderingSetupDeps,
@ -36,25 +33,20 @@ import {
} from './types';
/** @internal */
export class RenderingService implements CoreService<InternalRenderingServiceSetup> {
private legacyInternals?: LegacyService['legacyInternals'];
export class RenderingService {
constructor(private readonly coreContext: CoreContext) {}
public async setup({
http,
status,
legacyPlugins,
uiPlugins,
}: RenderingSetupDeps): Promise<InternalRenderingServiceSetup> {
return {
render: async (
request,
uiSettings,
{ app = { getId: () => 'core' }, includeUserSettings = true, vars }: IRenderOptions = {}
{ includeUserSettings = true, vars }: IRenderOptions = {}
) => {
if (!this.legacyInternals) {
throw new Error('Cannot render before "start"');
}
const env = {
mode: this.coreContext.env.mode,
packageInfo: this.coreContext.env.packageInfo,
@ -65,7 +57,6 @@ export class RenderingService implements CoreService<InternalRenderingServiceSet
defaults: uiSettings.getRegistered(),
user: includeUserSettings ? await uiSettings.getUserProvided() : {},
};
const appId = app.getId();
const metadata: RenderingMetadata = {
strictCsp: http.csp.strict,
uiPublicUrl: `${basePath}/ui`,
@ -87,7 +78,7 @@ export class RenderingService implements CoreService<InternalRenderingServiceSet
translationsUrl: `${basePath}/translations/${i18n.getLocale()}.json`,
},
csp: { warnLegacyBrowsers: http.csp.warnLegacyBrowsers },
vars: vars ?? (await this.legacyInternals!.getVars('core', request)),
vars: vars ?? {},
uiPlugins: await Promise.all(
[...uiPlugins.public].map(async ([id, plugin]) => ({
id,
@ -96,16 +87,6 @@ export class RenderingService implements CoreService<InternalRenderingServiceSet
}))
),
legacyMetadata: {
app,
bundleId: `app:${appId}`,
nav: legacyPlugins.navLinks,
version: env.packageInfo.version,
branch: env.packageInfo.branch,
buildNum: env.packageInfo.buildNum,
buildSha: env.packageInfo.buildSha,
serverName: http.server.name,
devMode: env.mode.dev,
basePath,
uiSettings: settings,
},
},
@ -116,10 +97,6 @@ export class RenderingService implements CoreService<InternalRenderingServiceSet
};
}
public async start({ legacy }: { legacy: LegacyService }) {
this.legacyInternals = legacy.legacyInternals;
}
public async stop() {}
private async getUiConfig(uiPlugins: UiPlugins, pluginId: string) {

View file

@ -22,7 +22,6 @@ import { i18n } from '@kbn/i18n';
import { EnvironmentMode, PackageInfo } from '../config';
import { ICspConfig } from '../csp';
import { InternalHttpServiceSetup, KibanaRequest, LegacyRequest } from '../http';
import { LegacyNavLink, LegacyServiceDiscoverPlugins } from '../legacy';
import { UiPlugins, DiscoveredPlugin } from '../plugins';
import { IUiSettingsClient, UserProvidedValues } from '../ui_settings';
import type { InternalStatusServiceSetup } from '../status';
@ -57,16 +56,6 @@ export interface RenderingMetadata {
config?: Record<string, unknown>;
}>;
legacyMetadata: {
app: { getId(): string };
bundleId: string;
nav: LegacyNavLink[];
version: string;
branch: string;
buildNum: number;
buildSha: string;
serverName: string;
devMode: boolean;
basePath: string;
uiSettings: {
defaults: Record<string, any>;
user: Record<string, UserProvidedValues<any>>;
@ -78,7 +67,6 @@ export interface RenderingMetadata {
/** @internal */
export interface RenderingSetupDeps {
http: InternalHttpServiceSetup;
legacyPlugins: LegacyServiceDiscoverPlugins;
status: InternalStatusServiceSetup;
uiPlugins: UiPlugins;
}
@ -91,14 +79,6 @@ export interface IRenderOptions {
*/
includeUserSettings?: boolean;
/**
* Render the bootstrapped HTML content for an optional legacy application.
* Defaults to `core`.
* @deprecated for legacy use only, remove with ui_render_mixin
* @internal
*/
app?: { getId(): string };
/**
* Inject custom vars into the page metadata.
* @deprecated for legacy use only, remove with ui_render_mixin

View file

@ -864,10 +864,6 @@ export interface IndexSettingsDeprecationInfo {
// @public (undocumented)
export interface IRenderOptions {
// @internal @deprecated
app?: {
getId(): string;
};
includeUserSettings?: boolean;
// @internal @deprecated
vars?: Record<string, any>;
@ -1286,21 +1282,6 @@ export class LegacyElasticsearchErrorHelpers {
static isNotAuthorizedError(error: any): error is LegacyElasticsearchError;
}
// Warning: (ae-forgotten-export) The symbol "ILegacyInternals" needs to be exported by the entry point index.d.ts
//
// @internal @deprecated (undocumented)
export class LegacyInternals implements ILegacyInternals {
constructor(uiExports: LegacyUiExports, config: LegacyConfig, server: Server);
// (undocumented)
getInjectedUiAppVars(id: string): Promise<Record<string, any>>;
// (undocumented)
getVars(id: string, request: KibanaRequest | LegacyRequest, injected?: LegacyVars): Promise<Record<string, any>>;
// Warning: (ae-forgotten-export) The symbol "VarsInjector" needs to be exported by the entry point index.d.ts
//
// (undocumented)
injectUiAppVars(id: string, injector: VarsInjector): void;
}
// @public @deprecated (undocumented)
export interface LegacyRequest extends Request {
}
@ -1312,16 +1293,6 @@ export class LegacyScopedClusterClient implements ILegacyScopedClusterClient {
callAsInternalUser(endpoint: string, clientParams?: Record<string, any>, options?: LegacyCallAPIOptions): Promise<any>;
}
// Warning: (ae-forgotten-export) The symbol "LegacyPlugins" needs to be exported by the entry point index.d.ts
//
// @internal @deprecated (undocumented)
export interface LegacyServiceDiscoverPlugins extends LegacyPlugins {
// (undocumented)
pluginExtendedConfig: LegacyConfig;
// (undocumented)
settings: LegacyVars;
}
// @public @deprecated (undocumented)
export interface LegacyServiceSetupDeps {
// Warning: (ae-forgotten-export) The symbol "LegacyCoreSetup" needs to be exported by the entry point index.d.ts
@ -1346,31 +1317,6 @@ export interface LegacyServiceStartDeps {
plugins: Record<string, unknown>;
}
// @internal @deprecated (undocumented)
export interface LegacyUiExports {
// Warning: (ae-forgotten-export) The symbol "VarsProvider" needs to be exported by the entry point index.d.ts
//
// (undocumented)
defaultInjectedVarProviders?: VarsProvider[];
// Warning: (ae-forgotten-export) The symbol "VarsReplacer" needs to be exported by the entry point index.d.ts
//
// (undocumented)
injectedVarsReplacers?: VarsReplacer[];
// Warning: (ae-forgotten-export) The symbol "LegacyNavLinkSpec" needs to be exported by the entry point index.d.ts
//
// (undocumented)
navLinkSpecs?: LegacyNavLinkSpec[] | null;
// Warning: (ae-forgotten-export) The symbol "LegacyAppSpec" needs to be exported by the entry point index.d.ts
//
// (undocumented)
uiAppSpecs?: Array<LegacyAppSpec | undefined>;
// (undocumented)
unknown?: [{
pluginSpec: LegacyPluginSpec;
type: unknown;
}];
}
// Warning: (ae-forgotten-export) The symbol "lifecycleResponseFactory" needs to be exported by the entry point index.d.ts
//
// @public
@ -2734,7 +2680,6 @@ export const validBodyOutput: readonly ["data", "stream"];
// Warnings were encountered during analysis:
//
// src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts
// src/core/server/legacy/types.ts:135:16 - (ae-forgotten-export) The symbol "LegacyPluginSpec" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:274:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:274:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts
// src/core/server/plugins/types.ts:277:3 - (ae-forgotten-export) The symbol "SavedObjectsConfigType" needs to be exported by the entry point index.d.ts

View file

@ -113,11 +113,12 @@ export class Server {
const { pluginTree, uiPlugins } = await this.plugins.discover({
environment: environmentSetup,
});
const legacyPlugins = await this.legacy.discoverPlugins();
const legacyConfigSetup = await this.legacy.setupLegacyConfig();
// Immediately terminate in case of invalid configuration
// This needs to be done after plugin discovery
await this.configService.validate();
await ensureValidConfiguration(this.configService, legacyPlugins);
await ensureValidConfiguration(this.configService, legacyConfigSetup);
const contextServiceSetup = this.context.setup({
// We inject a fake "legacy plugin" with dependencies on every plugin so that legacy plugins:
@ -166,7 +167,6 @@ export class Server {
const renderingSetup = await this.rendering.setup({
http: httpSetup,
status: statusSetup,
legacyPlugins,
uiPlugins,
});
@ -248,10 +248,6 @@ export class Server {
await this.http.start();
await this.rendering.start({
legacy: this.legacy,
});
return this.coreStart;
}

View file

@ -52,7 +52,6 @@ export default {
'@elastic/eui$': '<rootDir>/node_modules/@elastic/eui/test-env',
'@elastic/eui/lib/(.*)?': '<rootDir>/node_modules/@elastic/eui/test-env/$1',
'^src/plugins/(.*)': '<rootDir>/src/plugins/$1',
'^uiExports/(.*)': '<rootDir>/src/dev/jest/mocks/file_mock.js',
'^test_utils/(.*)': '<rootDir>/src/test_utils/public/$1',
'^fixtures/(.*)': '<rootDir>/src/fixtures/$1',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':

View file

@ -1,148 +0,0 @@
# Plugin Discovery
The plugin discovery module defines the core plugin loading logic used by the Kibana server. It exports functions for
## `findPluginSpecs(settings, [config])`
Finds [`PluginSpec`][PluginSpec] objects
### params
- `settings`: the same settings object accepted by [`KbnServer`][KbnServer]
- `[config]`: Optional - a [`Config`][Config] service. Using this param causes `findPluginSpecs()` to modify `config`'s schema to support the configuration for each discovered [`PluginSpec`][PluginSpec]. If you can, please use the [`Config`][Config] service produced by `extendedConfig$` rather than passing in an existing service so that `findPluginSpecs()` is side-effect free.
### return value
`findPluginSpecs()` returns an object of Observables which produce values at different parts of the process. Since the Observables are all aware of their own dependencies you can subscribe to any combination (within the same tick) and only the necessary plugin logic will be executed.
If you *never* subscribe to any of the Observables then plugin discovery won't actually run.
- `pack$`: emits every [`PluginPack`][PluginPack] found
- `invalidDirectoryError$: Observable<Error>`: emits [`InvalidDirectoryError`][Errors]s caused by `settings.plugins.scanDirs` values that don't point to actual directories. `findPluginSpecs()` will not abort when this error is encountered.
- `invalidPackError$: Observable<Error>`: emits [`InvalidPackError`][Errors]s caused by children of `settings.plugins.scanDirs` or `settings.plugins.paths` values which don't meet the requirements of a [`PluginPack`][PluginPack] (probably missing a `package.json`). `findPluginSpecs()` will not abort when this error is encountered.
- `deprecation$: Observable<string>`: emits deprecation warnings that are produces when reading each [`PluginPack`][PluginPack]'s configuration
- `extendedConfig$: Observable<Config>`: emits the [`Config`][Config] service that was passed to `findPluginSpecs()` (or created internally if none was passed) after it has been extended with the configuration from each plugin
- `spec$: Observable<PluginSpec>`: emits every *enabled* [`PluginSpec`][PluginSpec] defined by the discovered [`PluginPack`][PluginPack]s
- `disabledSpec$: Observable<PluginSpec>`: emits every *disabled* [`PluginSpec`][PluginSpec] defined by the discovered [`PluginPack`][PluginPack]s
- `invalidVersionSpec$: Observable<PluginSpec>`: emits every [`PluginSpec`][PluginSpec] who's required kibana version does not match the version exposed by `config.get('pkg.version')`
### example
Just get the plugin specs, only fail if there is an uncaught error of some sort:
```js
const { pack$ } = findPluginSpecs(settings);
const packs = await pack$.pipe(toArray()).toPromise()
```
Just log the deprecation messages:
```js
const { deprecation$ } = findPluginSpecs(settings);
for (const warning of await deprecation$.pipe(toArray()).toPromise()) {
console.log('DEPRECATION:', warning)
}
```
Get the packs but fail if any packs are invalid:
```js
const { pack$, invalidDirectoryError$ } = findPluginSpecs(settings);
const packs = await Rx.merge(
pack$.pipe(toArray()),
// if we ever get an InvalidDirectoryError, throw it
// into the stream so that all streams are unsubscribed,
// the discovery process is aborted, and the promise rejects
invalidDirectoryError$.pipe(
map(error => { throw error })
),
).toPromise()
```
Handle everything
```js
const {
pack$,
invalidDirectoryError$,
invalidPackError$,
deprecation$,
extendedConfig$,
spec$,
disabledSpecs$,
invalidVersionSpec$,
} = findPluginSpecs(settings);
Rx.merge(
pack$.pipe(
tap(pluginPack => console.log('Found plugin pack', pluginPack))
),
invalidDirectoryError$.pipe(
tap(error => console.log('Invalid directory error', error))
),
invalidPackError$.pipe(
tap(error => console.log('Invalid plugin pack error', error))
),
deprecation$.pipe(
tap(msg => console.log('DEPRECATION:', msg))
),
extendedConfig$.pipe(
tap(config => console.log('config service extended by plugins', config))
),
spec$.pipe(
tap(pluginSpec => console.log('enabled plugin spec found', spec))
),
disabledSpec$.pipe(
tap(pluginSpec => console.log('disabled plugin spec found', spec))
),
invalidVersionSpec$.pipe(
tap(pluginSpec => console.log('plugin spec with invalid version found', spec))
),
)
.toPromise()
.then(() => {
console.log('plugin discovery complete')
})
.catch((error) => {
console.log('plugin discovery failed', error)
})
```
## `reduceExportSpecs(pluginSpecs, reducers, [defaults={}])`
Reduces every value exported by the [`PluginSpec`][PluginSpec]s to produce a single value. If an exported value is an array each item in the array will be reduced individually. If the exported value is `undefined` it will be ignored. The reducer is called with the signature:
```js
reducer(
// the result of the previous reducer call, or `defaults`
acc: any,
// the exported value, found at `uiExports[type]` or `uiExports[type][i]`
// in the PluginSpec config.
spec: any,
// the key in `uiExports` where this export was found
type: string,
// the PluginSpec which exported this spec
pluginSpec: PluginSpec
)
```
## `new PluginPack(options)` class
Only exported so that `PluginPack` instances can be created in tests and used in place of on-disk plugin fixtures. Use `findPluginSpecs()`, or the cached result of a call to `findPluginSpecs()` (like `kbnServer.pluginSpecs`) any time you might need access to `PluginPack` objects in distributed code.
### params
- `options.path`: absolute path to where this plugin pack was found, this is normally a direct child of `./src/legacy/core_plugins` or `./plugins`
- `options.pkg`: the parsed `package.json` for this pack, used for defaults in `PluginSpec` objects defined by this pack
- `options.provider`: the default export of the pack, a function which is called with the `PluginSpec` class which should return one or more `PluginSpec` objects.
[PluginPack]: ./plugin_pack/plugin_pack.js "PluginPath class definition"
[PluginSpec]: ./plugin_spec/plugin_spec.js "PluginSpec class definition"
[Errors]: ./errors.js "PluginDiscover specific error types"
[KbnServer]: ../server/kbn_server.js "KbnServer class definition"
[Config]: ../server/config/config.js "KbnServer/Config class definition"

View file

@ -1,219 +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 { resolve } from 'path';
import { toArray } from 'rxjs/operators';
import expect from '@kbn/expect';
import { isEqual } from 'lodash';
import { findPluginSpecs } from '../find_plugin_specs';
import { PluginSpec } from '../plugin_spec';
const PLUGIN_FIXTURES = resolve(__dirname, 'fixtures/plugins');
const CONFLICT_FIXTURES = resolve(__dirname, 'fixtures/conflicts');
describe('plugin discovery', () => {
describe('findPluginSpecs()', function () {
this.timeout(10000);
describe('spec$', () => {
it('finds specs for specified plugin paths', async () => {
const { spec$ } = findPluginSpecs({
plugins: {
paths: [
resolve(PLUGIN_FIXTURES, 'foo'),
resolve(PLUGIN_FIXTURES, 'bar'),
resolve(PLUGIN_FIXTURES, 'broken'),
],
},
});
const specs = await spec$.pipe(toArray()).toPromise();
expect(specs).to.have.length(3);
specs.forEach((spec) => {
expect(spec).to.be.a(PluginSpec);
});
expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']);
});
it('finds all specs in scanDirs', async () => {
const { spec$ } = findPluginSpecs({
// used to ensure the dev_mode plugin is enabled
env: 'development',
plugins: {
scanDirs: [PLUGIN_FIXTURES],
},
});
const specs = await spec$.pipe(toArray()).toPromise();
expect(specs).to.have.length(3);
specs.forEach((spec) => {
expect(spec).to.be.a(PluginSpec);
});
expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']);
});
it('does not find disabled plugins', async () => {
const { spec$ } = findPluginSpecs({
'bar:one': {
enabled: false,
},
plugins: {
paths: [
resolve(PLUGIN_FIXTURES, 'foo'),
resolve(PLUGIN_FIXTURES, 'bar'),
resolve(PLUGIN_FIXTURES, 'broken'),
],
},
});
const specs = await spec$.pipe(toArray()).toPromise();
expect(specs).to.have.length(2);
specs.forEach((spec) => {
expect(spec).to.be.a(PluginSpec);
});
expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:two', 'foo']);
});
it('dedupes duplicate packs', async () => {
const { spec$ } = findPluginSpecs({
plugins: {
scanDirs: [PLUGIN_FIXTURES],
paths: [
resolve(PLUGIN_FIXTURES, 'foo'),
resolve(PLUGIN_FIXTURES, 'foo'),
resolve(PLUGIN_FIXTURES, 'bar'),
resolve(PLUGIN_FIXTURES, 'bar'),
resolve(PLUGIN_FIXTURES, 'broken'),
resolve(PLUGIN_FIXTURES, 'broken'),
],
},
});
const specs = await spec$.pipe(toArray()).toPromise();
expect(specs).to.have.length(3);
specs.forEach((spec) => {
expect(spec).to.be.a(PluginSpec);
});
expect(specs.map((s) => s.getId()).sort()).to.eql(['bar:one', 'bar:two', 'foo']);
});
describe('conflicting plugin spec ids', () => {
it('fails with informative message', async () => {
const { spec$ } = findPluginSpecs({
plugins: {
scanDirs: [],
paths: [resolve(CONFLICT_FIXTURES, 'foo')],
},
});
try {
await spec$.pipe(toArray()).toPromise();
throw new Error('expected spec$ to throw an error');
} catch (error) {
expect(error.message).to.contain('Multiple plugins found with the id "foo"');
expect(error.message).to.contain(CONFLICT_FIXTURES);
}
});
});
});
describe('packageJson$', () => {
const checkPackageJsons = (packageJsons) => {
expect(packageJsons).to.have.length(2);
const package1 = packageJsons.find((packageJson) =>
isEqual(
{
directoryPath: resolve(PLUGIN_FIXTURES, 'foo'),
contents: {
name: 'foo',
version: 'kibana',
},
},
packageJson
)
);
expect(package1).to.be.an(Object);
const package2 = packageJsons.find((packageJson) =>
isEqual(
{
directoryPath: resolve(PLUGIN_FIXTURES, 'bar'),
contents: {
name: 'foo',
version: 'kibana',
},
},
packageJson
)
);
expect(package2).to.be.an(Object);
};
it('finds packageJson for specified plugin paths', async () => {
const { packageJson$ } = findPluginSpecs({
plugins: {
paths: [
resolve(PLUGIN_FIXTURES, 'foo'),
resolve(PLUGIN_FIXTURES, 'bar'),
resolve(PLUGIN_FIXTURES, 'broken'),
],
},
});
const packageJsons = await packageJson$.pipe(toArray()).toPromise();
checkPackageJsons(packageJsons);
});
it('finds all packageJsons in scanDirs', async () => {
const { packageJson$ } = findPluginSpecs({
// used to ensure the dev_mode plugin is enabled
env: 'development',
plugins: {
scanDirs: [PLUGIN_FIXTURES],
},
});
const packageJsons = await packageJson$.pipe(toArray()).toPromise();
checkPackageJsons(packageJsons);
});
it('dedupes duplicate packageJson', async () => {
const { packageJson$ } = findPluginSpecs({
plugins: {
scanDirs: [PLUGIN_FIXTURES],
paths: [
resolve(PLUGIN_FIXTURES, 'foo'),
resolve(PLUGIN_FIXTURES, 'foo'),
resolve(PLUGIN_FIXTURES, 'bar'),
resolve(PLUGIN_FIXTURES, 'bar'),
resolve(PLUGIN_FIXTURES, 'broken'),
resolve(PLUGIN_FIXTURES, 'broken'),
],
},
});
const packageJsons = await packageJson$.pipe(toArray()).toPromise();
checkPackageJsons(packageJsons);
});
});
});
});

View file

@ -1,27 +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.
*/
export default function (kibana) {
return [
// two plugins exported without ids will both inherit
// the id of the pack and conflict
new kibana.Plugin({}),
new kibana.Plugin({}),
];
}

View file

@ -1,4 +0,0 @@
{
"name": "foo",
"version": "kibana"
}

View file

@ -1,29 +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.
*/
export default function (kibana) {
return [
new kibana.Plugin({
id: 'bar:one',
}),
new kibana.Plugin({
id: 'bar:two',
}),
];
}

View file

@ -1,4 +0,0 @@
{
"name": "foo",
"version": "kibana"
}

View file

@ -1,22 +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.
*/
export default {
foo: 'bar',
};

View file

@ -1,4 +0,0 @@
{
"name": "baz",
"version": "kibana"
}

View file

@ -1,24 +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.
*/
module.exports = function (kibana) {
return new kibana.Plugin({
id: 'foo',
});
};

View file

@ -1,4 +0,0 @@
{
"name": "foo",
"version": "kibana"
}

View file

@ -1,84 +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.
*/
const errorCodeProperty = Symbol('pluginDiscovery/errorCode');
/**
* Thrown when reading a plugin directory fails, wraps failure
* @type {String}
*/
const ERROR_INVALID_DIRECTORY = 'ERROR_INVALID_DIRECTORY';
export function createInvalidDirectoryError(sourceError, path) {
sourceError[errorCodeProperty] = ERROR_INVALID_DIRECTORY;
sourceError.path = path;
return sourceError;
}
export function isInvalidDirectoryError(error) {
return error && error[errorCodeProperty] === ERROR_INVALID_DIRECTORY;
}
/**
* Thrown when trying to create a PluginPack for a path that
* is not a valid plugin definition
* @type {String}
*/
const ERROR_INVALID_PACK = 'ERROR_INVALID_PACK';
export function createInvalidPackError(path, reason) {
const error = new Error(`PluginPack${path ? ` at "${path}"` : ''} ${reason}`);
error[errorCodeProperty] = ERROR_INVALID_PACK;
error.path = path;
return error;
}
export function isInvalidPackError(error) {
return error && error[errorCodeProperty] === ERROR_INVALID_PACK;
}
/**
* Thrown when trying to load a PluginSpec that is invalid for some reason
* @type {String}
*/
const ERROR_INVALID_PLUGIN = 'ERROR_INVALID_PLUGIN';
export function createInvalidPluginError(spec, reason) {
const error = new Error(
`Plugin from ${spec.getId()} at ${spec.getPack().getPath()} is invalid because ${reason}`
);
error[errorCodeProperty] = ERROR_INVALID_PLUGIN;
error.spec = spec;
return error;
}
export function isInvalidPluginError(error) {
return error && error[errorCodeProperty] === ERROR_INVALID_PLUGIN;
}
/**
* Thrown when trying to load a PluginSpec whose version is incompatible
* @type {String}
*/
const ERROR_INCOMPATIBLE_PLUGIN_VERSION = 'ERROR_INCOMPATIBLE_PLUGIN_VERSION';
export function createIncompatiblePluginVersionError(spec) {
const error = new Error(
`Plugin ${spec.getId()} is only compatible with Kibana version ${spec.getExpectedKibanaVersion()}`
);
error[errorCodeProperty] = ERROR_INCOMPATIBLE_PLUGIN_VERSION;
error.spec = spec;
return error;
}
export function isIncompatiblePluginVersionError(error) {
return error && error[errorCodeProperty] === ERROR_INCOMPATIBLE_PLUGIN_VERSION;
}

View file

@ -1,234 +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 * as Rx from 'rxjs';
import {
distinct,
toArray,
mergeMap,
share,
shareReplay,
filter,
last,
map,
tap,
} from 'rxjs/operators';
import { realpathSync } from 'fs';
import { Config } from '../server/config';
import { extendConfigService, disableConfigExtension } from './plugin_config';
import {
createPack$,
createPackageJsonAtPath$,
createPackageJsonsInDirectory$,
} from './plugin_pack';
import { isInvalidDirectoryError, isInvalidPackError } from './errors';
export function defaultConfig(settings) {
return Config.withDefaultSchema(settings);
}
function bufferAllResults(observable) {
return observable.pipe(
// buffer all results into a single array
toArray(),
// merge the array back into the stream when complete
mergeMap((array) => array)
);
}
/**
* Determine a distinct value for each result from find$
* so they can be deduplicated
* @param {{error?,pack?}} result
* @return {Any}
*/
function getDistinctKeyForFindResult(result) {
// errors are distinct by their message
if (result.error) {
return result.error.message;
}
// packs are distinct by their absolute and real path
if (result.packageJson) {
return realpathSync(result.packageJson.directoryPath);
}
// non error/pack results shouldn't exist, but if they do they are all unique
return result;
}
function groupSpecsById(specs) {
const specsById = new Map();
for (const spec of specs) {
const id = spec.getId();
if (specsById.has(id)) {
specsById.get(id).push(spec);
} else {
specsById.set(id, [spec]);
}
}
return specsById;
}
/**
* Creates a collection of observables for discovering pluginSpecs
* using Kibana's defaults, settings, and config service
*
* @param {Object} settings
* @param {ConfigService} [configToMutate] when supplied **it is mutated** to
* include the config from discovered plugin specs
* @return {Object<name,Rx>}
*/
export function findPluginSpecs(settings, configToMutate) {
const config$ = Rx.defer(async () => {
if (configToMutate) {
return configToMutate;
}
return defaultConfig(settings);
}).pipe(shareReplay());
// find plugin packs in configured paths/dirs
const packageJson$ = config$.pipe(
mergeMap((config) =>
Rx.merge(
...config.get('plugins.paths').map(createPackageJsonAtPath$),
...config.get('plugins.scanDirs').map(createPackageJsonsInDirectory$)
)
),
distinct(getDistinctKeyForFindResult),
share()
);
const pack$ = createPack$(packageJson$).pipe(share());
const extendConfig$ = config$.pipe(
mergeMap((config) =>
pack$.pipe(
// get the specs for each found plugin pack
mergeMap(({ pack }) => (pack ? pack.getPluginSpecs() : [])),
// make sure that none of the plugin specs have conflicting ids, fail
// early if conflicts detected or merge the specs back into the stream
toArray(),
mergeMap((allSpecs) => {
for (const [id, specs] of groupSpecsById(allSpecs)) {
if (specs.length > 1) {
throw new Error(
`Multiple plugins found with the id "${id}":\n${specs
.map((spec) => ` - ${id} at ${spec.getPath()}`)
.join('\n')}`
);
}
}
return allSpecs;
}),
mergeMap(async (spec) => {
// extend the config service with this plugin spec and
// collect its deprecations messages if some of its
// settings are outdated
const deprecations = [];
await extendConfigService(spec, config, settings, (message) => {
deprecations.push({ spec, message });
});
return {
spec,
deprecations,
};
}),
// extend the config with all plugins before determining enabled status
bufferAllResults,
map(({ spec, deprecations }) => {
const isRightVersion = spec.isVersionCompatible(config.get('pkg.version'));
const enabled = isRightVersion && spec.isEnabled(config);
return {
config,
spec,
deprecations,
enabledSpecs: enabled ? [spec] : [],
disabledSpecs: enabled ? [] : [spec],
invalidVersionSpecs: isRightVersion ? [] : [spec],
};
}),
// determine which plugins are disabled before actually removing things from the config
bufferAllResults,
tap((result) => {
for (const spec of result.disabledSpecs) {
disableConfigExtension(spec, config);
}
})
)
),
share()
);
return {
// package JSONs found when searching configure paths
packageJson$: packageJson$.pipe(
mergeMap((result) => (result.packageJson ? [result.packageJson] : []))
),
// plugin packs found when searching configured paths
pack$: pack$.pipe(mergeMap((result) => (result.pack ? [result.pack] : []))),
// errors caused by invalid directories of plugin directories
invalidDirectoryError$: pack$.pipe(
mergeMap((result) => (isInvalidDirectoryError(result.error) ? [result.error] : []))
),
// errors caused by directories that we expected to be plugin but were invalid
invalidPackError$: pack$.pipe(
mergeMap((result) => (isInvalidPackError(result.error) ? [result.error] : []))
),
otherError$: pack$.pipe(
mergeMap((result) => (isUnhandledError(result.error) ? [result.error] : []))
),
// { spec, message } objects produced when transforming deprecated
// settings for a plugin spec
deprecation$: extendConfig$.pipe(mergeMap((result) => result.deprecations)),
// the config service we extended with all of the plugin specs,
// only emitted once it is fully extended by all
extendedConfig$: extendConfig$.pipe(
mergeMap((result) => result.config),
filter(Boolean),
last()
),
// all enabled PluginSpec objects
spec$: extendConfig$.pipe(mergeMap((result) => result.enabledSpecs)),
// all disabled PluginSpec objects
disabledSpec$: extendConfig$.pipe(mergeMap((result) => result.disabledSpecs)),
// all PluginSpec objects that were disabled because their version was incompatible
invalidVersionSpec$: extendConfig$.pipe(mergeMap((result) => result.invalidVersionSpecs)),
};
}
function isUnhandledError(error) {
return error != null && !isInvalidDirectoryError(error) && !isInvalidPackError(error);
}

View file

@ -1,22 +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.
*/
export { findPluginSpecs } from './find_plugin_specs';
export { reduceExportSpecs } from './plugin_exports';
export { PluginPack } from './plugin_pack';

View file

@ -1,162 +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 sinon from 'sinon';
import expect from '@kbn/expect';
import { Config } from '../../../server/config';
import { PluginPack } from '../../plugin_pack';
import { extendConfigService, disableConfigExtension } from '../extend_config_service';
import * as SchemaNS from '../schema';
import * as SettingsNS from '../settings';
describe('plugin discovery/extend config service', () => {
const sandbox = sinon.createSandbox();
afterEach(() => sandbox.restore());
const pluginSpec = new PluginPack({
path: '/dev/null',
pkg: {
name: 'test',
version: 'kibana',
},
provider: ({ Plugin }) =>
new Plugin({
configPrefix: 'foo.bar.baz',
config: (Joi) =>
Joi.object({
enabled: Joi.boolean().default(true),
test: Joi.string().default('bonk'),
}).default(),
}),
})
.getPluginSpecs()
.pop();
describe('extendConfigService()', () => {
it('calls getSettings, getSchema, and Config.extendSchema() correctly', async () => {
const rootSettings = {
foo: {
bar: {
enabled: false,
},
},
};
const schema = {
validate: () => {},
};
const configPrefix = 'foo.bar';
const config = {
extendSchema: sandbox.stub(),
};
const pluginSpec = {
getConfigPrefix: sandbox.stub().returns(configPrefix),
};
const getSettings = sandbox.stub(SettingsNS, 'getSettings').returns(rootSettings.foo.bar);
const getSchema = sandbox.stub(SchemaNS, 'getSchema').returns(schema);
await extendConfigService(pluginSpec, config, rootSettings);
sinon.assert.calledOnce(getSettings);
sinon.assert.calledWithExactly(getSettings, pluginSpec, rootSettings);
sinon.assert.calledOnce(getSchema);
sinon.assert.calledWithExactly(getSchema, pluginSpec);
sinon.assert.calledOnce(config.extendSchema);
sinon.assert.calledWithExactly(
config.extendSchema,
schema,
rootSettings.foo.bar,
configPrefix
);
});
it('adds the schema for a plugin spec to its config prefix', async () => {
const config = Config.withDefaultSchema();
expect(config.has('foo.bar.baz')).to.be(false);
await extendConfigService(pluginSpec, config);
expect(config.has('foo.bar.baz')).to.be(true);
});
it('initializes it with the default settings', async () => {
const config = Config.withDefaultSchema();
await extendConfigService(pluginSpec, config);
expect(config.get('foo.bar.baz.enabled')).to.be(true);
expect(config.get('foo.bar.baz.test')).to.be('bonk');
});
it('initializes it with values from root settings if defined', async () => {
const config = Config.withDefaultSchema();
await extendConfigService(pluginSpec, config, {
foo: {
bar: {
baz: {
test: 'hello world',
},
},
},
});
expect(config.get('foo.bar.baz.test')).to.be('hello world');
});
it('throws if root settings are invalid', async () => {
const config = Config.withDefaultSchema();
try {
await extendConfigService(pluginSpec, config, {
foo: {
bar: {
baz: {
test: {
'not a string': true,
},
},
},
},
});
throw new Error('Expected extendConfigService() to throw because of bad settings');
} catch (error) {
expect(error.message).to.contain('"test" must be a string');
}
});
});
describe('disableConfigExtension()', () => {
it('removes added config', async () => {
const config = Config.withDefaultSchema();
await extendConfigService(pluginSpec, config);
expect(config.has('foo.bar.baz.test')).to.be(true);
await disableConfigExtension(pluginSpec, config);
expect(config.has('foo.bar.baz.test')).to.be(false);
});
it('leaves {configPrefix}.enabled config', async () => {
const config = Config.withDefaultSchema();
expect(config.has('foo.bar.baz.enabled')).to.be(false);
await extendConfigService(pluginSpec, config);
expect(config.get('foo.bar.baz.enabled')).to.be(true);
await disableConfigExtension(pluginSpec, config);
expect(config.get('foo.bar.baz.enabled')).to.be(false);
});
});
});

View file

@ -1,92 +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 { PluginPack } from '../../plugin_pack';
import { getSchema, getStubSchema } from '../schema';
describe('plugin discovery/schema', () => {
function createPluginSpec(configProvider) {
return new PluginPack({
path: '/dev/null',
pkg: {
name: 'test',
version: 'kibana',
},
provider: ({ Plugin }) =>
new Plugin({
configPrefix: 'foo.bar.baz',
config: configProvider,
}),
})
.getPluginSpecs()
.pop();
}
describe('getSchema()', () => {
it('calls the config provider and returns its return value', async () => {
const pluginSpec = createPluginSpec(() => 'foo');
expect(await getSchema(pluginSpec)).to.be('foo');
});
it('supports config provider that returns a promise', async () => {
const pluginSpec = createPluginSpec(() => Promise.resolve('foo'));
expect(await getSchema(pluginSpec)).to.be('foo');
});
it('uses default schema when no config provider', async () => {
const schema = await getSchema(createPluginSpec());
expect(schema).to.be.an('object');
expect(schema).to.have.property('validate').a('function');
expect(schema.validate({}).value).to.eql({
enabled: true,
});
});
it('uses default schema when config returns falsy value', async () => {
const schema = await getSchema(createPluginSpec(() => null));
expect(schema).to.be.an('object');
expect(schema).to.have.property('validate').a('function');
expect(schema.validate({}).value).to.eql({
enabled: true,
});
});
it('uses default schema when config promise resolves to falsy value', async () => {
const schema = await getSchema(createPluginSpec(() => Promise.resolve(null)));
expect(schema).to.be.an('object');
expect(schema).to.have.property('validate').a('function');
expect(schema.validate({}).value).to.eql({
enabled: true,
});
});
});
describe('getStubSchema()', () => {
it('returns schema with enabled: false', async () => {
const schema = await getStubSchema();
expect(schema).to.be.an('object');
expect(schema).to.have.property('validate').a('function');
expect(schema.validate({}).value).to.eql({
enabled: false,
});
});
});
});

View file

@ -1,61 +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 { PluginPack } from '../../plugin_pack';
import { getSettings } from '../settings';
describe('plugin_discovery/settings', () => {
const pluginSpec = new PluginPack({
path: '/dev/null',
pkg: {
name: 'test',
version: 'kibana',
},
provider: ({ Plugin }) =>
new Plugin({
configPrefix: 'a.b.c',
}),
})
.getPluginSpecs()
.pop();
describe('getSettings()', () => {
it('reads settings from config prefix', async () => {
const rootSettings = {
a: {
b: {
c: {
enabled: false,
},
},
},
};
expect(await getSettings(pluginSpec, rootSettings)).to.eql({
enabled: false,
});
});
it('allows rootSettings to be undefined', async () => {
expect(await getSettings(pluginSpec)).to.eql(undefined);
});
});
});

View file

@ -1,50 +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 { getSettings } from './settings';
import { getSchema, getStubSchema } from './schema';
/**
* Extend a config service with the schema and settings for a
* plugin spec and optionally call logDeprecation with warning
* messages about deprecated settings that are used
* @param {PluginSpec} spec
* @param {Server.Config} config
* @param {Object} rootSettings
* @param {Function} [logDeprecation]
* @return {Promise<undefined>}
*/
export async function extendConfigService(spec, config, rootSettings) {
const settings = await getSettings(spec, rootSettings);
const schema = await getSchema(spec);
config.extendSchema(schema, settings, spec.getConfigPrefix());
}
/**
* Disable the schema and settings applied to a config service for
* a plugin spec
* @param {PluginSpec} spec
* @param {Server.Config} config
* @return {undefined}
*/
export function disableConfigExtension(spec, config) {
const prefix = spec.getConfigPrefix();
config.removeSchema(prefix);
config.extendSchema(getStubSchema(), { enabled: false }, prefix);
}

View file

@ -1,20 +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.
*/
export { extendConfigService, disableConfigExtension } from './extend_config_service';

View file

@ -1,46 +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 Joi from 'joi';
const STUB_CONFIG_SCHEMA = Joi.object()
.keys({
enabled: Joi.valid(false).default(false),
})
.default();
const DEFAULT_CONFIG_SCHEMA = Joi.object()
.keys({
enabled: Joi.boolean().default(true),
})
.default();
/**
* Get the config schema for a plugin spec
* @param {PluginSpec} spec
* @return {Promise<Joi>}
*/
export async function getSchema(spec) {
const provider = spec.getConfigSchemaProvider();
return (provider && (await provider(Joi))) || DEFAULT_CONFIG_SCHEMA;
}
export function getStubSchema() {
return STUB_CONFIG_SCHEMA;
}

View file

@ -1,34 +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 { get } from 'lodash';
/**
* Get the settings for a pluginSpec from the raw root settings while
* optionally calling logDeprecation() with warnings about deprecated
* settings that were used
* @param {PluginSpec} spec
* @param {Object} rootSettings
* @return {Promise<Object>}
*/
export async function getSettings(spec, rootSettings) {
const prefix = spec.getConfigPrefix();
const rawSettings = get(rootSettings, prefix);
return rawSettings;
}

View file

@ -1,75 +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 { PluginPack } from '../../plugin_pack';
import { reduceExportSpecs } from '../reduce_export_specs';
const PLUGIN = new PluginPack({
path: __dirname,
pkg: {
name: 'foo',
version: 'kibana',
},
provider: ({ Plugin }) =>
new Plugin({
uiExports: {
concatNames: {
name: 'export1',
},
concat: ['export2', 'export3'],
},
}),
});
const REDUCERS = {
concatNames(acc, spec, type, pluginSpec) {
return {
names: [].concat(acc.names || [], `${pluginSpec.getId()}:${spec.name}`),
};
},
concat(acc, spec, type, pluginSpec) {
return {
names: [].concat(acc.names || [], `${pluginSpec.getId()}:${spec}`),
};
},
};
const PLUGIN_SPECS = PLUGIN.getPluginSpecs();
describe('reduceExportSpecs', () => {
it('combines ui exports from a list of plugin definitions', () => {
const exports = reduceExportSpecs(PLUGIN_SPECS, REDUCERS);
expect(exports).to.eql({
names: ['foo:export1', 'foo:export2', 'foo:export3'],
});
});
it('starts with the defaults', () => {
const exports = reduceExportSpecs(PLUGIN_SPECS, REDUCERS, {
names: ['default'],
});
expect(exports).to.eql({
names: ['default', 'foo:export1', 'foo:export2', 'foo:export3'],
});
});
});

View file

@ -1,20 +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.
*/
export { reduceExportSpecs } from './reduce_export_specs';

View file

@ -1,47 +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.
*/
/**
* Combine the exportSpecs from a list of pluginSpecs
* by calling the reducers for each export type
* @param {Array<PluginSpecs>} pluginSpecs
* @param {Object<exportType,reducer>} reducers
* @param {Object<exportType,exports>} [defaults={}]
* @return {Object<exportType,exports>}
*/
export function reduceExportSpecs(pluginSpecs, reducers, defaults = {}) {
return pluginSpecs.reduce((acc, pluginSpec) => {
const specsByType = pluginSpec.getExportSpecs() || {};
const types = Object.keys(specsByType);
return types.reduce((acc, type) => {
const reducer = reducers[type] || reducers.unknown;
if (!reducer) {
throw new Error(`Unknown export type ${type}`);
}
// convert specs to an array if not already one or
// ignore the spec if it is undefined
const specs = [].concat(specsByType[type] === undefined ? [] : specsByType[type]);
return specs.reduce((acc, spec) => reducer(acc, spec, type, pluginSpec), acc);
}, acc);
}, defaults);
}

View file

@ -1,85 +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 { resolve } from 'path';
import * as Rx from 'rxjs';
import { toArray } from 'rxjs/operators';
import expect from '@kbn/expect';
import { createPack$ } from '../create_pack';
import { PluginPack } from '../plugin_pack';
import { PLUGINS_DIR, assertInvalidPackError } from './utils';
describe('plugin discovery/create pack', () => {
it('creates PluginPack', async () => {
const packageJson$ = Rx.from([
{
packageJson: {
directoryPath: resolve(PLUGINS_DIR, 'prebuilt'),
contents: {
name: 'prebuilt',
},
},
},
]);
const results = await createPack$(packageJson$).pipe(toArray()).toPromise();
expect(results).to.have.length(1);
expect(results[0]).to.only.have.keys(['pack']);
const { pack } = results[0];
expect(pack).to.be.a(PluginPack);
});
describe('errors thrown', () => {
async function checkError(path, check) {
const packageJson$ = Rx.from([
{
packageJson: {
directoryPath: path,
},
},
]);
const results = await createPack$(packageJson$).pipe(toArray()).toPromise();
expect(results).to.have.length(1);
expect(results[0]).to.only.have.keys(['error']);
const { error } = results[0];
await check(error);
}
it('default export is an object', () =>
checkError(resolve(PLUGINS_DIR, 'exports_object'), (error) => {
assertInvalidPackError(error);
expect(error.message).to.contain('must export a function');
}));
it('default export is an number', () =>
checkError(resolve(PLUGINS_DIR, 'exports_number'), (error) => {
assertInvalidPackError(error);
expect(error.message).to.contain('must export a function');
}));
it('default export is an string', () =>
checkError(resolve(PLUGINS_DIR, 'exports_string'), (error) => {
assertInvalidPackError(error);
expect(error.message).to.contain('must export a function');
}));
it('directory with code that fails when required', () =>
checkError(resolve(PLUGINS_DIR, 'broken_code'), (error) => {
expect(error.message).to.contain("Cannot find module 'does-not-exist'");
}));
});
});

View file

@ -1,7 +0,0 @@
const brokenRequire = require('does-not-exist'); // eslint-disable-line
module.exports = function (kibana) {
return new kibana.Plugin({
id: 'foo',
});
};

View file

@ -1,4 +0,0 @@
{
"name": "foo",
"version": "kibana"
}

View file

@ -1,20 +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.
*/
export default 1;

View file

@ -1,4 +0,0 @@
{
"name": "foo",
"version": "kibana"
}

View file

@ -1,22 +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.
*/
export default {
foo: 'bar',
};

View file

@ -1,4 +0,0 @@
{
"name": "foo",
"version": "kibana"
}

View file

@ -1,20 +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.
*/
export default 'foo';

View file

@ -1,4 +0,0 @@
{
"name": "foo",
"version": "kibana"
}

View file

@ -1,24 +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.
*/
module.exports = function (kibana) {
return new kibana.Plugin({
id: 'foo',
});
};

View file

@ -1,4 +0,0 @@
{
"name": "foo",
"version": "kibana"
}

View file

@ -1,20 +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.
*/
console.log('hello world');

View file

@ -1,20 +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.
*/
export { myLib } from './my_lib';

View file

@ -1,22 +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.
*/
export function myLib() {
console.log('lib');
}

View file

@ -1,14 +0,0 @@
/* eslint-disable */
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = function (_ref) {
var Plugin = _ref.Plugin;
return new Plugin({
id: 'foo'
});
};

View file

@ -1,88 +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 { resolve } from 'path';
import { toArray } from 'rxjs/operators';
import expect from '@kbn/expect';
import { createPackageJsonAtPath$ } from '../package_json_at_path';
import { PLUGINS_DIR, assertInvalidPackError, assertInvalidDirectoryError } from './utils';
describe('plugin discovery/plugin_pack', () => {
describe('createPackageJsonAtPath$()', () => {
it('returns an observable', () => {
expect(createPackageJsonAtPath$()).to.have.property('subscribe').a('function');
});
it('gets the default provider from prebuilt babel modules', async () => {
const results = await createPackageJsonAtPath$(resolve(PLUGINS_DIR, 'prebuilt'))
.pipe(toArray())
.toPromise();
expect(results).to.have.length(1);
expect(results[0]).to.only.have.keys(['packageJson']);
expect(results[0].packageJson).to.be.an(Object);
expect(results[0].packageJson.directoryPath).to.be(resolve(PLUGINS_DIR, 'prebuilt'));
expect(results[0].packageJson.contents).to.eql({ name: 'prebuilt' });
});
describe('errors emitted as { error } results', () => {
async function checkError(path, check) {
const results = await createPackageJsonAtPath$(path).pipe(toArray()).toPromise();
expect(results).to.have.length(1);
expect(results[0]).to.only.have.keys(['error']);
const { error } = results[0];
await check(error);
}
it('undefined path', () =>
checkError(undefined, (error) => {
assertInvalidDirectoryError(error);
expect(error.message).to.contain('path must be a string');
}));
it('relative path', () =>
checkError('plugins/foo', (error) => {
assertInvalidDirectoryError(error);
expect(error.message).to.contain('path must be absolute');
}));
it('./relative path', () =>
checkError('./plugins/foo', (error) => {
assertInvalidDirectoryError(error);
expect(error.message).to.contain('path must be absolute');
}));
it('non-existent path', () =>
checkError(resolve(PLUGINS_DIR, 'baz'), (error) => {
assertInvalidPackError(error);
expect(error.message).to.contain('must be a directory');
}));
it('path to a file', () =>
checkError(resolve(PLUGINS_DIR, 'index.js'), (error) => {
assertInvalidPackError(error);
expect(error.message).to.contain('must be a directory');
}));
it('directory without a package.json', () =>
checkError(resolve(PLUGINS_DIR, 'lib'), (error) => {
assertInvalidPackError(error);
expect(error.message).to.contain('must have a package.json file');
}));
it('directory with an invalid package.json', () =>
checkError(resolve(PLUGINS_DIR, 'broken'), (error) => {
assertInvalidPackError(error);
expect(error.message).to.contain('must have a valid package.json file');
}));
});
});
});

View file

@ -1,83 +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 { resolve } from 'path';
import { toArray } from 'rxjs/operators';
import expect from '@kbn/expect';
import { createPackageJsonsInDirectory$ } from '../package_jsons_in_directory';
import { PLUGINS_DIR, assertInvalidDirectoryError } from './utils';
describe('plugin discovery/packs in directory', () => {
describe('createPackageJsonsInDirectory$()', () => {
describe('errors emitted as { error } results', () => {
async function checkError(path, check) {
const results = await createPackageJsonsInDirectory$(path).pipe(toArray()).toPromise();
expect(results).to.have.length(1);
expect(results[0]).to.only.have.keys('error');
const { error } = results[0];
await check(error);
}
it('undefined path', () =>
checkError(undefined, (error) => {
assertInvalidDirectoryError(error);
expect(error.message).to.contain('path must be a string');
}));
it('relative path', () =>
checkError('my/plugins', (error) => {
assertInvalidDirectoryError(error);
expect(error.message).to.contain('path must be absolute');
}));
it('./relative path', () =>
checkError('./my/pluginsd', (error) => {
assertInvalidDirectoryError(error);
expect(error.message).to.contain('path must be absolute');
}));
it('non-existent path', () =>
checkError(resolve(PLUGINS_DIR, 'notreal'), (error) => {
assertInvalidDirectoryError(error);
expect(error.message).to.contain('no such file or directory');
}));
it('path to a file', () =>
checkError(resolve(PLUGINS_DIR, 'index.js'), (error) => {
assertInvalidDirectoryError(error);
expect(error.message).to.contain('not a directory');
}));
});
it('includes child errors for invalid packageJsons within a valid directory', async () => {
const results = await createPackageJsonsInDirectory$(PLUGINS_DIR).pipe(toArray()).toPromise();
const errors = results.map((result) => result.error).filter(Boolean);
const packageJsons = results.map((result) => result.packageJson).filter(Boolean);
packageJsons.forEach((pack) => expect(pack).to.be.an(Object));
// there should be one result for each item in PLUGINS_DIR
expect(results).to.have.length(8);
// three of the fixtures are errors of some sort
expect(errors).to.have.length(2);
// six of them are valid
expect(packageJsons).to.have.length(6);
});
});
});

View file

@ -1,126 +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 { PluginPack } from '../plugin_pack';
import { PluginSpec } from '../../plugin_spec';
describe('plugin discovery/plugin pack', () => {
describe('constructor', () => {
it('requires an object', () => {
expect(() => {
new PluginPack();
}).to.throwError();
});
});
describe('#getPkg()', () => {
it('returns the `pkg` constructor argument', () => {
const pkg = {};
const pack = new PluginPack({ pkg });
expect(pack.getPkg()).to.be(pkg);
});
});
describe('#getPath()', () => {
it('returns the `path` constructor argument', () => {
const path = {};
const pack = new PluginPack({ path });
expect(pack.getPath()).to.be(path);
});
});
describe('#getPluginSpecs()', () => {
it('calls the `provider` constructor argument with an api including a single sub class of PluginSpec', () => {
const provider = sinon.stub();
const pack = new PluginPack({ provider });
sinon.assert.notCalled(provider);
pack.getPluginSpecs();
sinon.assert.calledOnce(provider);
sinon.assert.calledWithExactly(provider, {
Plugin: sinon.match((Class) => {
return Class.prototype instanceof PluginSpec;
}, 'Subclass of PluginSpec'),
});
});
it('casts undefined return value to array', () => {
const pack = new PluginPack({ provider: () => undefined });
expect(pack.getPluginSpecs()).to.eql([]);
});
it('casts single PluginSpec to an array', () => {
const pack = new PluginPack({
path: '/dev/null',
pkg: { name: 'foo', version: 'kibana' },
provider: ({ Plugin }) => new Plugin({}),
});
const specs = pack.getPluginSpecs();
expect(specs).to.be.an('array');
expect(specs).to.have.length(1);
expect(specs[0]).to.be.a(PluginSpec);
});
it('returns an array of PluginSpec', () => {
const pack = new PluginPack({
path: '/dev/null',
pkg: { name: 'foo', version: 'kibana' },
provider: ({ Plugin }) => [new Plugin({}), new Plugin({})],
});
const specs = pack.getPluginSpecs();
expect(specs).to.be.an('array');
expect(specs).to.have.length(2);
expect(specs[0]).to.be.a(PluginSpec);
expect(specs[1]).to.be.a(PluginSpec);
});
it('throws if non-undefined return value is not an instance of api.Plugin', () => {
let OtherPluginSpecClass;
const otherPack = new PluginPack({
path: '/dev/null',
pkg: { name: 'foo', version: 'kibana' },
provider: (api) => {
OtherPluginSpecClass = api.Plugin;
},
});
// call getPluginSpecs() on other pack to get it's api.Plugin class
otherPack.getPluginSpecs();
const badPacks = [
new PluginPack({ provider: () => false }),
new PluginPack({ provider: () => null }),
new PluginPack({ provider: () => 1 }),
new PluginPack({ provider: () => 'true' }),
new PluginPack({ provider: () => true }),
new PluginPack({ provider: () => new Date() }),
new PluginPack({ provider: () => /foo.*bar/ }),
new PluginPack({ provider: () => function () {} }),
new PluginPack({ provider: () => new OtherPluginSpecClass({}) }),
];
for (const pack of badPacks) {
expect(() => pack.getPluginSpecs()).to.throwError((error) => {
expect(error.message).to.contain('unexpected plugin export');
});
}
});
});
});

View file

@ -1,37 +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 { resolve } from 'path';
import { inspect } from 'util';
import { isInvalidPackError, isInvalidDirectoryError } from '../../errors';
export const PLUGINS_DIR = resolve(__dirname, 'fixtures/plugins');
export function assertInvalidDirectoryError(error) {
if (!isInvalidDirectoryError(error)) {
throw new Error(`Expected ${inspect(error)} to be an 'InvalidDirectoryError'`);
}
}
export function assertInvalidPackError(error) {
if (!isInvalidPackError(error)) {
throw new Error(`Expected ${inspect(error)} to be an 'InvalidPackError'`);
}
}

View file

@ -1,54 +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 { PluginPack } from './plugin_pack';
import { map, catchError } from 'rxjs/operators';
import { createInvalidPackError } from '../errors';
function createPack(packageJson) {
let provider = require(packageJson.directoryPath); // eslint-disable-line import/no-dynamic-require
if (provider.__esModule) {
provider = provider.default;
}
if (typeof provider !== 'function') {
throw createInvalidPackError(packageJson.directoryPath, 'must export a function');
}
return new PluginPack({ path: packageJson.directoryPath, pkg: packageJson.contents, provider });
}
export const createPack$ = (packageJson$) =>
packageJson$.pipe(
map(({ error, packageJson }) => {
if (error) {
return { error };
}
if (!packageJson) {
throw new Error('packageJson is required to create the pack');
}
return {
pack: createPack(packageJson),
};
}),
// createPack can throw errors, and we want them to be represented
// like the errors we consume from createPackageJsonAtPath/Directory
catchError((error) => [{ error }])
);

View file

@ -1,23 +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.
*/
export { createPack$ } from './create_pack';
export { createPackageJsonAtPath$ } from './package_json_at_path';
export { createPackageJsonsInDirectory$ } from './package_jsons_in_directory';
export { PluginPack } from './plugin_pack';

View file

@ -1,85 +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 { stat, readdir } from 'fs';
import { resolve, isAbsolute } from 'path';
import { fromNode as fcb } from 'bluebird';
import * as Rx from 'rxjs';
import { catchError, mergeAll, filter, map, mergeMap } from 'rxjs/operators';
import { createInvalidDirectoryError } from '../../errors';
function assertAbsolutePath(path) {
if (typeof path !== 'string') {
throw createInvalidDirectoryError(new TypeError('path must be a string'), path);
}
if (!isAbsolute(path)) {
throw createInvalidDirectoryError(new TypeError('path must be absolute'), path);
}
}
async function statTest(path, test) {
try {
const stats = await fcb((cb) => stat(path, cb));
return Boolean(test(stats));
} catch (error) {
if (error.code !== 'ENOENT') {
throw error;
}
}
return false;
}
/**
* Determine if a path currently points to a directory
* @param {String} path
* @return {Promise<boolean>}
*/
export async function isDirectory(path) {
assertAbsolutePath(path);
return await statTest(path, (stat) => stat.isDirectory());
}
/**
* Get absolute paths for child directories within a path
* @param {string} path
* @return {Promise<Array<string>>}
*/
export const createChildDirectory$ = (path) =>
Rx.defer(() => {
assertAbsolutePath(path);
return fcb((cb) => readdir(path, cb));
}).pipe(
catchError((error) => {
throw createInvalidDirectoryError(error, path);
}),
mergeAll(),
filter((name) => !name.startsWith('.')),
map((name) => resolve(path, name)),
mergeMap(async (absolute) => {
if (await isDirectory(absolute)) {
return [absolute];
} else {
return [];
}
}),
mergeAll()
);

View file

@ -1,20 +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.
*/
export { isDirectory, createChildDirectory$ } from './fs';

View file

@ -1,62 +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 { readFileSync } from 'fs';
import * as Rx from 'rxjs';
import { map, mergeMap, catchError } from 'rxjs/operators';
import { resolve } from 'path';
import { createInvalidPackError } from '../errors';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { isNewPlatformPlugin } from '../../../core/server/plugins';
import { isDirectory } from './lib';
async function createPackageJsonAtPath(path) {
if (!(await isDirectory(path))) {
throw createInvalidPackError(path, 'must be a directory');
}
let str;
try {
str = readFileSync(resolve(path, 'package.json'));
} catch (err) {
throw createInvalidPackError(path, 'must have a package.json file');
}
let pkg;
try {
pkg = JSON.parse(str);
} catch (err) {
throw createInvalidPackError(path, 'must have a valid package.json file');
}
return {
directoryPath: path,
contents: pkg,
};
}
export const createPackageJsonAtPath$ = (path) =>
// If plugin directory contains manifest file, we should skip it since it
// should have been handled by the core plugin system already.
Rx.defer(() => isNewPlatformPlugin(path)).pipe(
mergeMap((isNewPlatformPlugin) => (isNewPlatformPlugin ? [] : createPackageJsonAtPath(path))),
map((packageJson) => ({ packageJson })),
catchError((error) => [{ error }])
);

View file

@ -1,52 +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 { mergeMap, catchError } from 'rxjs/operators';
import { isInvalidDirectoryError } from '../errors';
import { createChildDirectory$ } from './lib';
import { createPackageJsonAtPath$ } from './package_json_at_path';
/**
* Finds the plugins within a directory. Results are
* an array of objects with either `pack` or `error`
* keys.
*
* - `{ error }` results are provided when the path is not
* a directory, or one of the child directories is not a
* valid plugin pack.
* - `{ pack }` results are for discovered plugins defs
*
* @param {String} path
* @return {Array<{pack}|{error}>}
*/
export const createPackageJsonsInDirectory$ = (path) =>
createChildDirectory$(path).pipe(
mergeMap(createPackageJsonAtPath$),
catchError((error) => {
// this error is produced by createChildDirectory$() when the path
// is invalid, we return them as an error result similar to how
// createPackAtPath$ works when it finds invalid packs in a directory
if (isInvalidDirectoryError(error)) {
return [{ error }];
}
throw error;
})
);

View file

@ -1,74 +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 { inspect } from 'util';
import { PluginSpec } from '../plugin_spec';
export class PluginPack {
constructor({ path, pkg, provider }) {
this._path = path;
this._pkg = pkg;
this._provider = provider;
}
/**
* Get the contents of this plugin pack's package.json file
* @return {Object}
*/
getPkg() {
return this._pkg;
}
/**
* Get the absolute path to this plugin pack on disk
* @return {String}
*/
getPath() {
return this._path;
}
/**
* Invoke the plugin pack's provider to get the list
* of specs defined in this plugin.
* @return {Array<PluginSpec>}
*/
getPluginSpecs() {
const pack = this;
const api = {
Plugin: class ScopedPluginSpec extends PluginSpec {
constructor(options) {
super(pack, options);
}
},
};
const result = this._provider(api);
const specs = [].concat(result === undefined ? [] : result);
// verify that all specs are instances of passed "Plugin" class
specs.forEach((spec) => {
if (!(spec instanceof api.Plugin)) {
throw new TypeError('unexpected plugin export ' + inspect(spec));
}
});
return specs;
}
}

View file

@ -1,48 +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 { isVersionCompatible } from '../is_version_compatible';
describe('plugin discovery/plugin spec', () => {
describe('isVersionCompatible()', () => {
const tests = [
['kibana', '6.0.0', true],
['kibana', '6.0.0-rc1', true],
['6.0.0-rc1', '6.0.0', true],
['6.0.0', '6.0.0-rc1', true],
['6.0.0-rc2', '6.0.0-rc1', true],
['6.0.0-rc2', '6.0.0-rc3', true],
['foo', 'bar', false],
['6.0.0', '5.1.4', false],
['5.1.4', '6.0.0', false],
['5.1.4-SNAPSHOT', '6.0.0-rc2-SNAPSHOT', false],
['5.1.4', '6.0.0-rc2-SNAPSHOT', false],
['5.1.4-SNAPSHOT', '6.0.0', false],
['5.1.4-SNAPSHOT', '6.0.0-rc2', false],
];
for (const [plugin, kibana, shouldPass] of tests) {
it(`${shouldPass ? 'should' : `shouldn't`} allow plugin: ${plugin} kibana: ${kibana}`, () => {
expect(isVersionCompatible(plugin, kibana)).to.be(shouldPass);
});
}
});
});

View file

@ -1,496 +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 { resolve } from 'path';
import expect from '@kbn/expect';
import sinon from 'sinon';
import { PluginPack } from '../../plugin_pack';
import { PluginSpec } from '../plugin_spec';
import * as IsVersionCompatibleNS from '../is_version_compatible';
const fooPack = new PluginPack({
path: '/dev/null',
pkg: { name: 'foo', version: 'kibana' },
});
describe('plugin discovery/plugin spec', () => {
describe('PluginSpec', () => {
describe('validation', () => {
it('throws if missing spec.id AND Pack has no name', () => {
const pack = new PluginPack({ pkg: {} });
expect(() => new PluginSpec(pack, {})).to.throwError((error) => {
expect(error.message).to.contain('Unable to determine plugin id');
});
});
it('throws if missing spec.kibanaVersion AND Pack has no version', () => {
const pack = new PluginPack({ pkg: { name: 'foo' } });
expect(() => new PluginSpec(pack, {})).to.throwError((error) => {
expect(error.message).to.contain('Unable to determine plugin version');
});
});
it('throws if spec.require is defined, but not an array', () => {
function assert(require) {
expect(() => new PluginSpec(fooPack, { require })).to.throwError((error) => {
expect(error.message).to.contain('"plugin.require" must be an array of plugin ids');
});
}
assert(null);
assert('');
assert('kibana');
assert(1);
assert(0);
assert(/a.*b/);
});
it('throws if spec.publicDir is truthy and not a string', () => {
function assert(publicDir) {
expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError((error) => {
expect(error.message).to.contain(
`The "path" argument must be of type string. Received type ${typeof publicDir}`
);
});
}
assert(1);
assert(function () {});
assert([]);
assert(/a.*b/);
});
it('throws if spec.publicDir is not an absolute path', () => {
function assert(publicDir) {
expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError((error) => {
expect(error.message).to.contain('plugin.publicDir must be an absolute path');
});
}
assert('relative/path');
assert('./relative/path');
});
it('throws if spec.publicDir basename is not `public`', () => {
function assert(publicDir) {
expect(() => new PluginSpec(fooPack, { publicDir })).to.throwError((error) => {
expect(error.message).to.contain('must end with a "public" directory');
});
}
assert('/www');
assert('/www/');
assert('/www/public/my_plugin');
assert('/www/public/my_plugin/');
});
});
describe('#getPack()', () => {
it('returns the pack', () => {
const spec = new PluginSpec(fooPack, {});
expect(spec.getPack()).to.be(fooPack);
});
});
describe('#getPkg()', () => {
it('returns the pkg from the pack', () => {
const spec = new PluginSpec(fooPack, {});
expect(spec.getPkg()).to.be(fooPack.getPkg());
});
});
describe('#getPath()', () => {
it('returns the path from the pack', () => {
const spec = new PluginSpec(fooPack, {});
expect(spec.getPath()).to.be(fooPack.getPath());
});
});
describe('#getId()', () => {
it('uses spec.id', () => {
const spec = new PluginSpec(fooPack, {
id: 'bar',
});
expect(spec.getId()).to.be('bar');
});
it('defaults to pack.pkg.name', () => {
const spec = new PluginSpec(fooPack, {});
expect(spec.getId()).to.be('foo');
});
});
describe('#getVersion()', () => {
it('uses spec.version', () => {
const spec = new PluginSpec(fooPack, {
version: 'bar',
});
expect(spec.getVersion()).to.be('bar');
});
it('defaults to pack.pkg.version', () => {
const spec = new PluginSpec(fooPack, {});
expect(spec.getVersion()).to.be('kibana');
});
});
describe('#isEnabled()', () => {
describe('spec.isEnabled is not defined', () => {
function setup(configPrefix, configGetImpl) {
const spec = new PluginSpec(fooPack, { configPrefix });
const config = {
get: sinon.spy(configGetImpl),
has: sinon.stub(),
};
return { spec, config };
}
it('throws if not passed a config service', () => {
const { spec } = setup('a.b.c', () => true);
expect(() => spec.isEnabled()).to.throwError((error) => {
expect(error.message).to.contain('must be called with a config service');
});
expect(() => spec.isEnabled(null)).to.throwError((error) => {
expect(error.message).to.contain('must be called with a config service');
});
expect(() => spec.isEnabled({ get: () => {} })).to.throwError((error) => {
expect(error.message).to.contain('must be called with a config service');
});
});
it('returns true when config.get([...configPrefix, "enabled"]) returns true', () => {
const { spec, config } = setup('d.e.f', () => true);
expect(spec.isEnabled(config)).to.be(true);
sinon.assert.calledOnce(config.get);
sinon.assert.calledWithExactly(config.get, ['d', 'e', 'f', 'enabled']);
});
it('returns false when config.get([...configPrefix, "enabled"]) returns false', () => {
const { spec, config } = setup('g.h.i', () => false);
expect(spec.isEnabled(config)).to.be(false);
sinon.assert.calledOnce(config.get);
sinon.assert.calledWithExactly(config.get, ['g', 'h', 'i', 'enabled']);
});
});
describe('spec.isEnabled is defined', () => {
function setup(isEnabledImpl) {
const isEnabled = sinon.spy(isEnabledImpl);
const spec = new PluginSpec(fooPack, { isEnabled });
const config = {
get: sinon.stub(),
has: sinon.stub(),
};
return { isEnabled, spec, config };
}
it('throws if not passed a config service', () => {
const { spec } = setup(() => true);
expect(() => spec.isEnabled()).to.throwError((error) => {
expect(error.message).to.contain('must be called with a config service');
});
expect(() => spec.isEnabled(null)).to.throwError((error) => {
expect(error.message).to.contain('must be called with a config service');
});
expect(() => spec.isEnabled({ get: () => {} })).to.throwError((error) => {
expect(error.message).to.contain('must be called with a config service');
});
});
it('does not check config if spec.isEnabled returns true', () => {
const { spec, isEnabled, config } = setup(() => true);
expect(spec.isEnabled(config)).to.be(true);
sinon.assert.calledOnce(isEnabled);
sinon.assert.notCalled(config.get);
});
it('does not check config if spec.isEnabled returns false', () => {
const { spec, isEnabled, config } = setup(() => false);
expect(spec.isEnabled(config)).to.be(false);
sinon.assert.calledOnce(isEnabled);
sinon.assert.notCalled(config.get);
});
});
});
describe('#getExpectedKibanaVersion()', () => {
describe('has: spec.kibanaVersion,pkg.kibana.version,spec.version,pkg.version', () => {
it('uses spec.kibanaVersion', () => {
const pack = new PluginPack({
path: '/dev/null',
pkg: {
name: 'expkv',
version: '1.0.0',
kibana: {
version: '6.0.0',
},
},
});
const spec = new PluginSpec(pack, {
version: '2.0.0',
kibanaVersion: '5.0.0',
});
expect(spec.getExpectedKibanaVersion()).to.be('5.0.0');
});
});
describe('missing: spec.kibanaVersion, has: pkg.kibana.version,spec.version,pkg.version', () => {
it('uses pkg.kibana.version', () => {
const pack = new PluginPack({
path: '/dev/null',
pkg: {
name: 'expkv',
version: '1.0.0',
kibana: {
version: '6.0.0',
},
},
});
const spec = new PluginSpec(pack, {
version: '2.0.0',
});
expect(spec.getExpectedKibanaVersion()).to.be('6.0.0');
});
});
describe('missing: spec.kibanaVersion,pkg.kibana.version, has: spec.version,pkg.version', () => {
it('uses spec.version', () => {
const pack = new PluginPack({
path: '/dev/null',
pkg: {
name: 'expkv',
version: '1.0.0',
},
});
const spec = new PluginSpec(pack, {
version: '2.0.0',
});
expect(spec.getExpectedKibanaVersion()).to.be('2.0.0');
});
});
describe('missing: spec.kibanaVersion,pkg.kibana.version,spec.version, has: pkg.version', () => {
it('uses pkg.version', () => {
const pack = new PluginPack({
path: '/dev/null',
pkg: {
name: 'expkv',
version: '1.0.0',
},
});
const spec = new PluginSpec(pack, {});
expect(spec.getExpectedKibanaVersion()).to.be('1.0.0');
});
});
});
describe('#isVersionCompatible()', () => {
it('passes this.getExpectedKibanaVersion() and arg to isVersionCompatible(), returns its result', () => {
const spec = new PluginSpec(fooPack, { version: '1.0.0' });
sinon.stub(spec, 'getExpectedKibanaVersion').returns('foo');
const isVersionCompatible = sinon
.stub(IsVersionCompatibleNS, 'isVersionCompatible')
.returns('bar');
expect(spec.isVersionCompatible('baz')).to.be('bar');
sinon.assert.calledOnce(spec.getExpectedKibanaVersion);
sinon.assert.calledWithExactly(spec.getExpectedKibanaVersion);
sinon.assert.calledOnce(isVersionCompatible);
sinon.assert.calledWithExactly(isVersionCompatible, 'foo', 'baz');
});
});
describe('#getRequiredPluginIds()', () => {
it('returns spec.require', () => {
const spec = new PluginSpec(fooPack, { require: [1, 2, 3] });
expect(spec.getRequiredPluginIds()).to.eql([1, 2, 3]);
});
});
describe('#getPublicDir()', () => {
describe('spec.publicDir === false', () => {
it('returns null', () => {
const spec = new PluginSpec(fooPack, { publicDir: false });
expect(spec.getPublicDir()).to.be(null);
});
});
describe('spec.publicDir is falsy', () => {
it('returns public child of pack path', () => {
function assert(publicDir) {
const spec = new PluginSpec(fooPack, { publicDir });
expect(spec.getPublicDir()).to.be(resolve('/dev/null/public'));
}
assert(0);
assert('');
assert(null);
assert(undefined);
assert(NaN);
});
});
describe('spec.publicDir is an absolute path', () => {
it('returns the path', () => {
const spec = new PluginSpec(fooPack, {
publicDir: '/var/www/public',
});
expect(spec.getPublicDir()).to.be('/var/www/public');
});
});
// NOTE: see constructor tests for other truthy-tests that throw in constructor
});
describe('#getExportSpecs()', () => {
it('returns spec.uiExports', () => {
const spec = new PluginSpec(fooPack, {
uiExports: 'foo',
});
expect(spec.getExportSpecs()).to.be('foo');
});
});
describe('#getPreInitHandler()', () => {
it('returns spec.preInit', () => {
const spec = new PluginSpec(fooPack, {
preInit: 'foo',
});
expect(spec.getPreInitHandler()).to.be('foo');
});
});
describe('#getInitHandler()', () => {
it('returns spec.init', () => {
const spec = new PluginSpec(fooPack, {
init: 'foo',
});
expect(spec.getInitHandler()).to.be('foo');
});
});
describe('#getConfigPrefix()', () => {
describe('spec.configPrefix is truthy', () => {
it('returns spec.configPrefix', () => {
const spec = new PluginSpec(fooPack, {
configPrefix: 'foo.bar.baz',
});
expect(spec.getConfigPrefix()).to.be('foo.bar.baz');
});
});
describe('spec.configPrefix is falsy', () => {
it('returns spec.getId()', () => {
function assert(configPrefix) {
const spec = new PluginSpec(fooPack, { configPrefix });
sinon.stub(spec, 'getId').returns('foo');
expect(spec.getConfigPrefix()).to.be('foo');
sinon.assert.calledOnce(spec.getId);
}
assert(false);
assert(null);
assert(undefined);
assert('');
assert(0);
});
});
});
describe('#getConfigSchemaProvider()', () => {
it('returns spec.config', () => {
const spec = new PluginSpec(fooPack, {
config: 'foo',
});
expect(spec.getConfigSchemaProvider()).to.be('foo');
});
});
describe('#readConfigValue()', () => {
const spec = new PluginSpec(fooPack, {
configPrefix: 'foo.bar',
});
const config = {
get: sinon.stub(),
};
afterEach(() => config.get.resetHistory());
describe('key = "foo"', () => {
it('passes key as own array item', () => {
spec.readConfigValue(config, 'foo');
sinon.assert.calledOnce(config.get);
sinon.assert.calledWithExactly(config.get, ['foo', 'bar', 'foo']);
});
});
describe('key = "foo.bar"', () => {
it('passes key as two array items', () => {
spec.readConfigValue(config, 'foo.bar');
sinon.assert.calledOnce(config.get);
sinon.assert.calledWithExactly(config.get, ['foo', 'bar', 'foo', 'bar']);
});
});
describe('key = ["foo", "bar"]', () => {
it('merged keys into array', () => {
spec.readConfigValue(config, ['foo', 'bar']);
sinon.assert.calledOnce(config.get);
sinon.assert.calledWithExactly(config.get, ['foo', 'bar', 'foo', 'bar']);
});
});
});
describe('#getDeprecationsProvider()', () => {
it('returns spec.deprecations', () => {
const spec = new PluginSpec(fooPack, {
deprecations: 'foo',
});
expect(spec.getDeprecationsProvider()).to.be('foo');
});
});
});
});

View file

@ -1,20 +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.
*/
export { PluginSpec } from './plugin_spec';

View file

@ -1,30 +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 { cleanVersion, versionSatisfies } from '../../utils/version';
export function isVersionCompatible(version, compatibleWith) {
// the special "kibana" version can be used to always be compatible,
// but is intentionally not supported by the plugin installer
if (version === 'kibana') {
return true;
}
return versionSatisfies(cleanVersion(version), cleanVersion(compatibleWith));
}

View file

@ -1,210 +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 { resolve, basename, isAbsolute as isAbsolutePath } from 'path';
import { get, toPath } from 'lodash';
import { createInvalidPluginError } from '../errors';
import { isVersionCompatible } from './is_version_compatible';
export class PluginSpec {
/**
* @param {PluginPack} pack The plugin pack that produced this spec
* @param {Object} opts the options for this plugin
* @param {String} [opts.id=pkg.name] the id for this plugin.
* @param {Object} [opts.uiExports] a mapping of UiExport types to
* UI modules or metadata about the UI module
* @param {Array} [opts.require] the other plugins that this plugin
* requires. These plugins must exist and be enabled for this plugin
* to function. The require'd plugins will also be initialized first,
* in order to make sure that dependencies provided by these plugins
* are available
* @param {String} [opts.version=pkg.version] the version of this plugin
* @param {Function} [opts.init] A function that will be called to initialize
* this plugin at the appropriate time.
* @param {Function} [opts.configPrefix=this.id] The prefix to use for
* configuration values in the main configuration service
* @param {Function} [opts.config] A function that produces a configuration
* schema using Joi, which is passed as its first argument.
* @param {String|False} [opts.publicDir=path + '/public'] the public
* directory for this plugin. The final directory must have the name "public",
* though it can be located somewhere besides the root of the plugin. Set
* this to false to disable exposure of a public directory
*/
constructor(pack, options) {
const {
id,
require,
version,
kibanaVersion,
uiExports,
uiCapabilities,
publicDir,
configPrefix,
config,
deprecations,
preInit,
init,
postInit,
isEnabled,
} = options;
this._id = id;
this._pack = pack;
this._version = version;
this._kibanaVersion = kibanaVersion;
this._require = require;
this._publicDir = publicDir;
this._uiExports = uiExports;
this._uiCapabilities = uiCapabilities;
this._configPrefix = configPrefix;
this._configSchemaProvider = config;
this._configDeprecationsProvider = deprecations;
this._isEnabled = isEnabled;
this._preInit = preInit;
this._init = init;
this._postInit = postInit;
if (!this.getId()) {
throw createInvalidPluginError(this, 'Unable to determine plugin id');
}
if (!this.getVersion()) {
throw createInvalidPluginError(this, 'Unable to determine plugin version');
}
if (this.getRequiredPluginIds() !== undefined && !Array.isArray(this.getRequiredPluginIds())) {
throw createInvalidPluginError(this, '"plugin.require" must be an array of plugin ids');
}
if (this._publicDir) {
if (!isAbsolutePath(this._publicDir)) {
throw createInvalidPluginError(this, 'plugin.publicDir must be an absolute path');
}
if (basename(this._publicDir) !== 'public') {
throw createInvalidPluginError(
this,
`publicDir for plugin ${this.getId()} must end with a "public" directory.`
);
}
}
}
getPack() {
return this._pack;
}
getPkg() {
return this._pack.getPkg();
}
getPath() {
return this._pack.getPath();
}
getId() {
return this._id || this.getPkg().name;
}
getVersion() {
return this._version || this.getPkg().version;
}
isEnabled(config) {
if (!config || typeof config.get !== 'function' || typeof config.has !== 'function') {
throw new TypeError('PluginSpec#isEnabled() must be called with a config service');
}
if (this._isEnabled) {
return this._isEnabled(config);
}
return Boolean(this.readConfigValue(config, 'enabled'));
}
getExpectedKibanaVersion() {
// Plugins must specify their version, and by default that version should match
// the version of kibana down to the patch level. If these two versions need
// to diverge, they can specify a kibana.version in the package to indicate the
// version of kibana the plugin is intended to work with.
return (
this._kibanaVersion || get(this.getPack().getPkg(), 'kibana.version') || this.getVersion()
);
}
isVersionCompatible(actualKibanaVersion) {
return isVersionCompatible(this.getExpectedKibanaVersion(), actualKibanaVersion);
}
getRequiredPluginIds() {
return this._require;
}
getPublicDir() {
if (this._publicDir === false) {
return null;
}
if (!this._publicDir) {
return resolve(this.getPack().getPath(), 'public');
}
return this._publicDir;
}
getExportSpecs() {
return this._uiExports;
}
getUiCapabilitiesProvider() {
return this._uiCapabilities;
}
getPreInitHandler() {
return this._preInit;
}
getInitHandler() {
return this._init;
}
getPostInitHandler() {
return this._postInit;
}
getConfigPrefix() {
return this._configPrefix || this.getId();
}
getConfigSchemaProvider() {
return this._configSchemaProvider;
}
readConfigValue(config, key) {
return config.get([...toPath(this.getConfigPrefix()), ...toPath(key)]);
}
getDeprecationsProvider() {
return this._configDeprecationsProvider;
}
}

View file

@ -1,35 +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 { Server } from '../../server/kbn_server';
import { Capabilities } from '../../../core/server';
export type InitPluginFunction = (server: Server) => void;
export interface UiExports {
injectDefaultVars?: (server: Server) => { [key: string]: any };
}
export interface PluginSpecOptions {
id: string;
require?: string[];
publicDir?: string;
uiExports?: UiExports;
uiCapabilities?: Capabilities;
init?: InitPluginFunction;
config?: any;
}

View file

@ -1,107 +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 { Server } from '../server/kbn_server';
import { Capabilities } from '../../core/server';
import { AppCategory } from '../../core/types';
/**
* Usage
*
* ```
* const apmOss: LegacyPlugin = (kibana) => {
* return new kibana.Plugin({
* id: 'apm_oss',
* // ...
* });
* };
* ```
*/
export type LegacyPluginInitializer = (kibana: LegacyPluginApi) => ArrayOrItem<LegacyPluginSpec>;
export type ArrayOrItem<T> = T | T[];
export interface LegacyPluginApi {
Plugin: new (options: Partial<LegacyPluginOptions>) => LegacyPluginSpec;
}
export interface LegacyPluginOptions {
id: string;
require: string[];
version: string;
kibanaVersion: 'kibana';
uiExports: Partial<{
app: Partial<{
title: string;
category?: AppCategory;
description: string;
main: string;
icon: string;
euiIconType: string;
order: number;
listed: boolean;
}>;
apps: any;
hacks: string[];
visualize: string[];
devTools: string[];
injectDefaultVars: (server: Server) => Record<string, any>;
home: string[];
mappings: any;
migrations: any;
visTypes: string[];
embeddableActions?: string[];
embeddableFactories?: string[];
uiSettingDefaults?: Record<string, any>;
interpreter: string | string[];
}>;
uiCapabilities?: Capabilities;
publicDir: any;
configPrefix: any;
config: any;
deprecations: any;
preInit: any;
init: InitPluginFunction;
postInit: any;
isEnabled: boolean;
}
export type InitPluginFunction = (server: Server) => void;
export interface LegacyPluginSpec {
getPack(): any;
getPkg(): any;
getPath(): string;
getId(): string;
getVersion(): string;
isEnabled(config: any): boolean;
getExpectedKibanaVersion(): string;
isVersionCompatible(actualKibanaVersion: any): boolean;
getRequiredPluginIds(): string[];
getPublicDir(): string | null;
getExportSpecs(): any;
getUiCapabilitiesProvider(): any;
getPreInitHandler(): any;
getInitHandler(): any;
getPostInitHandler(): any;
getConfigPrefix(): string;
getConfigSchemaProvider(): any;
readConfigValue(config: any, key: string): any;
getDeprecationsProvider(): any;
}

View file

@ -132,6 +132,7 @@ export default () =>
}),
}).default(),
// still used by the legacy i18n mixin
plugins: Joi.object({
paths: Joi.array().items(Joi.string()).default([]),
scanDirs: Joi.array().items(Joi.string()).default([]),
@ -147,71 +148,8 @@ export default () =>
status: Joi.object({
allowAnonymous: Joi.boolean().default(false),
}).default(),
map: Joi.object({
includeElasticMapsService: Joi.boolean().default(true),
proxyElasticMapsServiceInMaps: Joi.boolean().default(false),
tilemap: Joi.object({
url: Joi.string(),
options: Joi.object({
attribution: Joi.string(),
minZoom: Joi.number().min(0, 'Must be 0 or higher').default(0),
maxZoom: Joi.number().default(10),
tileSize: Joi.number(),
subdomains: Joi.array().items(Joi.string()).single(),
errorTileUrl: Joi.string().uri(),
tms: Joi.boolean(),
reuseTiles: Joi.boolean(),
bounds: Joi.array().items(Joi.array().items(Joi.number()).min(2).required()).min(2),
default: Joi.boolean(),
}).default({
default: true,
}),
}).default(),
regionmap: Joi.object({
includeElasticMapsService: Joi.boolean().default(true),
layers: Joi.array()
.items(
Joi.object({
url: Joi.string(),
format: Joi.object({
type: Joi.string().default('geojson'),
}).default({
type: 'geojson',
}),
meta: Joi.object({
feature_collection_path: Joi.string().default('data'),
}).default({
feature_collection_path: 'data',
}),
attribution: Joi.string(),
name: Joi.string(),
fields: Joi.array().items(
Joi.object({
name: Joi.string(),
description: Joi.string(),
})
),
})
)
.default([]),
}).default(),
manifestServiceUrl: Joi.string().default('').allow(''),
emsFileApiUrl: Joi.string().default('https://vector.maps.elastic.co'),
emsTileApiUrl: Joi.string().default('https://tiles.maps.elastic.co'),
emsLandingPageUrl: Joi.string().default('https://maps.elastic.co/v7.9'),
emsFontLibraryUrl: Joi.string().default(
'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf'
),
emsTileLayerId: Joi.object({
bright: Joi.string().default('road_map'),
desaturated: Joi.string().default('road_map_desaturated'),
dark: Joi.string().default('dark_map'),
}).default({
bright: 'road_map',
desaturated: 'road_map_desaturated',
dark: 'dark_map',
}),
}).default(),
map: HANDLED_IN_NEW_PLATFORM,
i18n: Joi.object({
locale: Joi.string().default('en'),

View file

@ -26,11 +26,10 @@ import {
LoggerFactory,
PackageInfo,
LegacyServiceSetupDeps,
LegacyServiceDiscoverPlugins,
} from '../../core/server';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { LegacyConfig, ILegacyInternals } from '../../core/server/legacy';
import { LegacyConfig } from '../../core/server/legacy';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { UiPlugins } from '../../core/server/plugins';
@ -58,9 +57,7 @@ export interface PluginsSetup {
export interface KibanaCore {
__internals: {
elasticsearch: LegacyServiceSetupDeps['core']['elasticsearch'];
hapiServer: LegacyServiceSetupDeps['core']['http']['server'];
legacy: ILegacyInternals;
rendering: LegacyServiceSetupDeps['core']['rendering'];
uiPlugins: UiPlugins;
};
@ -90,31 +87,18 @@ export interface NewPlatform {
stop: null;
}
export type LegacyPlugins = Pick<
LegacyServiceDiscoverPlugins,
'pluginSpecs' | 'disabledPluginSpecs' | 'uiExports'
>;
// eslint-disable-next-line import/no-default-export
export default class KbnServer {
public readonly newPlatform: NewPlatform;
public server: Server;
public inject: Server['inject'];
public pluginSpecs: any[];
public uiBundles: any;
constructor(
settings: Record<string, any>,
config: KibanaConfig,
core: KibanaCore,
legacyPlugins: LegacyPlugins
);
constructor(settings: Record<string, any>, config: KibanaConfig, core: KibanaCore);
public ready(): Promise<void>;
public mixin(...fns: KbnMixinFunc[]): Promise<void>;
public listen(): Promise<Server>;
public close(): Promise<void>;
public afterPluginsInit(callback: () => void): void;
public applyLoggingConfiguration(settings: any): void;
public config: KibanaConfig;
}

View file

@ -30,7 +30,6 @@ import { loggingMixin } from './logging';
import warningsMixin from './warnings';
import configCompleteMixin from './config/complete';
import { optimizeMixin } from '../../optimize';
import * as Plugins from './plugins';
import { uiMixin } from '../ui';
import { i18nMixin } from './i18n';
@ -47,9 +46,8 @@ export default class KbnServer {
* @param {Record<string, any>} settings
* @param {KibanaConfig} config
* @param {KibanaCore} core
* @param {LegacyPlugins} legacyPlugins
*/
constructor(settings, config, core, legacyPlugins) {
constructor(settings, config, core) {
this.name = pkg.name;
this.version = pkg.version;
this.build = pkg.build || false;
@ -74,14 +72,8 @@ export default class KbnServer {
stop: null,
};
this.uiExports = legacyPlugins.uiExports;
this.pluginSpecs = legacyPlugins.pluginSpecs;
this.disabledPluginSpecs = legacyPlugins.disabledPluginSpecs;
this.ready = constant(
this.mixin(
Plugins.waitForInitSetupMixin,
// Sets global HTTP behaviors
httpMixin,
@ -93,22 +85,13 @@ export default class KbnServer {
// scan translations dirs, register locale files and initialize i18n engine.
i18nMixin,
// find plugins and set this.plugins and this.pluginSpecs
Plugins.scanMixin,
// tell the config we are done loading plugins
configCompleteMixin,
uiMixin,
// setup routes that serve the @kbn/optimizer output
optimizeMixin,
// initialize the plugins
Plugins.initializeMixin,
// notify any deferred setup logic that plugins have initialized
Plugins.waitForInitResolveMixin
optimizeMixin
)
);

View file

@ -1,22 +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.
*/
export { scanMixin } from './scan_mixin';
export { initializeMixin } from './initialize_mixin';
export { waitForInitSetupMixin, waitForInitResolveMixin } from './wait_for_plugins_init';

View file

@ -1,47 +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 { callPluginHook } from './lib';
/**
* KbnServer mixin that initializes all plugins found in ./scan mixin
* @param {KbnServer} kbnServer
* @param {Hapi.Server} server
* @param {Config} config
* @return {Promise<undefined>}
*/
export async function initializeMixin(kbnServer, server, config) {
if (!config.get('plugins.initialize')) {
server.log(['info'], 'Plugin initialization disabled.');
return;
}
async function callHookOnPlugins(hookName) {
const { plugins } = kbnServer;
const ids = plugins.map((p) => p.id);
for (const id of ids) {
await callPluginHook(hookName, plugins, id, []);
}
}
await callHookOnPlugins('preInit');
await callHookOnPlugins('init');
await callHookOnPlugins('postInit');
}

View file

@ -1,50 +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 { last } from 'lodash';
export async function callPluginHook(hookName, plugins, id, history) {
const plugin = plugins.find((plugin) => plugin.id === id);
// make sure this is a valid plugin id
if (!plugin) {
if (history.length) {
throw new Error(`Unmet requirement "${id}" for plugin "${last(history)}"`);
} else {
throw new Error(`Unknown plugin "${id}"`);
}
}
const circleStart = history.indexOf(id);
const path = [...history, id];
// make sure we are not trying to load a dependency within itself
if (circleStart > -1) {
const circle = path.slice(circleStart);
throw new Error(`circular dependency found: "${circle.join(' -> ')}"`);
}
// call hook on all dependencies
for (const req of plugin.requiredIds) {
await callPluginHook(hookName, plugins, req, path);
}
// call hook on this plugin
await plugin[hookName]();
}

View file

@ -1,101 +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 sinon from 'sinon';
import { callPluginHook } from './call_plugin_hook';
describe('server/plugins/callPluginHook', () => {
it('should call in correct order based on requirements', async () => {
const plugins = [
{
id: 'foo',
init: sinon.spy(),
preInit: sinon.spy(),
requiredIds: ['bar', 'baz'],
},
{
id: 'bar',
init: sinon.spy(),
preInit: sinon.spy(),
requiredIds: [],
},
{
id: 'baz',
init: sinon.spy(),
preInit: sinon.spy(),
requiredIds: ['bar'],
},
];
await callPluginHook('init', plugins, 'foo', []);
const [foo, bar, baz] = plugins;
sinon.assert.calledOnce(foo.init);
sinon.assert.calledTwice(bar.init);
sinon.assert.calledOnce(baz.init);
sinon.assert.callOrder(bar.init, baz.init, foo.init);
});
it('throws meaningful error when required plugin is missing', async () => {
const plugins = [
{
id: 'foo',
init: sinon.spy(),
preInit: sinon.spy(),
requiredIds: ['bar'],
},
];
try {
await callPluginHook('init', plugins, 'foo', []);
throw new Error('expected callPluginHook to throw');
} catch (error) {
expect(error.message).toContain('"bar" for plugin "foo"');
}
});
it('throws meaningful error when dependencies are circular', async () => {
const plugins = [
{
id: 'foo',
init: sinon.spy(),
preInit: sinon.spy(),
requiredIds: ['bar'],
},
{
id: 'bar',
init: sinon.spy(),
preInit: sinon.spy(),
requiredIds: ['baz'],
},
{
id: 'baz',
init: sinon.spy(),
preInit: sinon.spy(),
requiredIds: ['foo'],
},
];
try {
await callPluginHook('init', plugins, 'foo', []);
throw new Error('expected callPluginHook to throw');
} catch (error) {
expect(error.message).toContain('foo -> bar -> baz -> foo');
}
});
});

View file

@ -1,21 +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.
*/
export { callPluginHook } from './call_plugin_hook';
export { Plugin } from './plugin';

View file

@ -1,114 +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 { once } from 'lodash';
/**
* The server plugin class, used to extend the server
* and add custom behavior. A "scoped" plugin class is
* created by the PluginApi class and provided to plugin
* providers that automatically binds all but the `opts`
* arguments.
*
* @class Plugin
* @param {KbnServer} kbnServer - the KbnServer this plugin
* belongs to.
* @param {PluginDefinition} def
* @param {PluginSpec} spec
*/
export class Plugin {
constructor(kbnServer, spec) {
this.kbnServer = kbnServer;
this.spec = spec;
this.pkg = spec.getPkg();
this.path = spec.getPath();
this.id = spec.getId();
this.version = spec.getVersion();
this.requiredIds = spec.getRequiredPluginIds() || [];
this.externalPreInit = spec.getPreInitHandler();
this.externalInit = spec.getInitHandler();
this.externalPostInit = spec.getPostInitHandler();
this.enabled = spec.isEnabled(kbnServer.config);
this.configPrefix = spec.getConfigPrefix();
this.publicDir = spec.getPublicDir();
this.preInit = once(this.preInit);
this.init = once(this.init);
this.postInit = once(this.postInit);
}
async preInit() {
if (this.externalPreInit) {
return await this.externalPreInit(this.kbnServer.server);
}
}
async init() {
const { id, version, kbnServer, configPrefix } = this;
const { config } = kbnServer;
// setup the hapi register function and get on with it
const register = async (server, options) => {
this._server = server;
this._options = options;
server.logWithMetadata(['plugins', 'debug'], `Initializing plugin ${this.toString()}`, {
plugin: this,
});
if (this.publicDir) {
server.newPlatform.__internals.http.registerStaticDir(
`/plugins/${id}/{path*}`,
this.publicDir
);
}
if (this.externalInit) {
await this.externalInit(server, options);
}
};
await kbnServer.server.register({
plugin: { register, name: id, version },
options: config.has(configPrefix) ? config.get(configPrefix) : null,
});
}
async postInit() {
if (this.externalPostInit) {
return await this.externalPostInit(this.kbnServer.server);
}
}
getServer() {
return this._server;
}
getOptions() {
return this._options;
}
toJSON() {
return this.pkg;
}
toString() {
return `${this.id}@${this.version}`;
}
}

View file

@ -1,23 +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 { Plugin } from './lib';
export async function scanMixin(kbnServer) {
kbnServer.plugins = kbnServer.pluginSpecs.map((spec) => new Plugin(kbnServer, spec));
}

View file

@ -1,53 +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.
*/
/**
* Tracks the individual queue for each kbnServer, rather than attaching
* it to the kbnServer object via a property or something
* @type {WeakMap}
*/
const queues = new WeakMap();
export function waitForInitSetupMixin(kbnServer) {
queues.set(kbnServer, []);
kbnServer.afterPluginsInit = function (callback) {
const queue = queues.get(kbnServer);
if (!queue) {
throw new Error(
'Plugins have already initialized. Only use this method for setup logic that must wait for plugins to initialize.'
);
}
queue.push(callback);
};
}
export async function waitForInitResolveMixin(kbnServer, server, config) {
const queue = queues.get(kbnServer);
queues.set(kbnServer, null);
// only actually call the callbacks if we are really initializing
if (config.get('plugins.initialize')) {
for (const cb of queue) {
await cb();
}
}
}

Some files were not shown because too many files have changed in this diff Show more