[Alerting] migrates all remaining plugins to new platform (#64335)

Completes the migration of all Alerting Services plugins onto the Kibana Platform

It includes:

1. Actions plugin
2. Alerting plugin
3. Task Manager plugin
4. Triggers UI plugin

And touches the Uptime and Siem plugins as their use of the Task Manager relied on some of the legacy lifecycle to work (registering AlertTypes and Telemetry tasks after the Start stage has already began). The fix was simply to moves these registrations to the Setup stage.
This commit is contained in:
Gidi Meir Morris 2020-04-29 15:46:54 +01:00 committed by GitHub
parent 30e41c5d8f
commit 9fe7229357
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 587 additions and 628 deletions

3
.github/CODEOWNERS vendored
View file

@ -166,8 +166,6 @@
/x-pack/plugins/telemetry_collection_xpack/ @elastic/pulse
# Kibana Alerting Services
/x-pack/legacy/plugins/alerting/ @elastic/kibana-alerting-services
/x-pack/legacy/plugins/actions/ @elastic/kibana-alerting-services
/x-pack/plugins/alerting/ @elastic/kibana-alerting-services
/x-pack/plugins/actions/ @elastic/kibana-alerting-services
/x-pack/plugins/event_log/ @elastic/kibana-alerting-services
@ -175,7 +173,6 @@
/x-pack/test/alerting_api_integration/ @elastic/kibana-alerting-services
/x-pack/test/plugin_api_integration/plugins/task_manager/ @elastic/kibana-alerting-services
/x-pack/test/plugin_api_integration/test_suites/task_manager/ @elastic/kibana-alerting-services
/x-pack/legacy/plugins/triggers_actions_ui/ @elastic/kibana-alerting-services
/x-pack/plugins/triggers_actions_ui/ @elastic/kibana-alerting-services
/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/ @elastic/kibana-alerting-services
/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/ @elastic/kibana-alerting-services

View file

@ -17,10 +17,7 @@ import { canvas } from './legacy/plugins/canvas';
import { infra } from './legacy/plugins/infra';
import { taskManager } from './legacy/plugins/task_manager';
import { encryptedSavedObjects } from './legacy/plugins/encrypted_saved_objects';
import { actions } from './legacy/plugins/actions';
import { alerting } from './legacy/plugins/alerting';
import { ingestManager } from './legacy/plugins/ingest_manager';
import { triggersActionsUI } from './legacy/plugins/triggers_actions_ui';
module.exports = function(kibana) {
return [
@ -37,9 +34,6 @@ module.exports = function(kibana) {
infra(kibana),
taskManager(kibana),
encryptedSavedObjects(kibana),
actions(kibana),
alerting(kibana),
ingestManager(kibana),
triggersActionsUI(kibana),
];
};

View file

@ -1,38 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { Root } from 'joi';
import { Legacy } from 'kibana';
import mappings from './mappings.json';
import {
LegacyPluginApi,
LegacyPluginSpec,
ArrayOrItem,
} from '../../../../../src/legacy/plugin_discovery/types';
export function actions(kibana: LegacyPluginApi): ArrayOrItem<LegacyPluginSpec> {
return new kibana.Plugin({
id: 'actions',
configPrefix: 'xpack.actions',
config(Joi: Root) {
return Joi.object({
enabled: Joi.boolean().default(true),
})
.unknown(true)
.default();
},
require: ['kibana', 'elasticsearch'],
isEnabled(config: Legacy.KibanaConfig) {
return (
config.get('xpack.encryptedSavedObjects.enabled') === true &&
config.get('xpack.actions.enabled') === true &&
config.get('xpack.task_manager.enabled') === true
);
},
uiExports: {
mappings,
},
} as Legacy.PluginSpecOptions);
}

View file

@ -1,7 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export * from './server';

View file

@ -1,40 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { Legacy } from 'kibana';
import { Root } from 'joi';
import mappings from './mappings.json';
import {
LegacyPluginApi,
LegacyPluginSpec,
ArrayOrItem,
} from '../../../../../src/legacy/plugin_discovery/types';
export function alerting(kibana: LegacyPluginApi): ArrayOrItem<LegacyPluginSpec> {
return new kibana.Plugin({
id: 'alerting',
configPrefix: 'xpack.alerting',
require: ['kibana', 'elasticsearch', 'actions', 'task_manager', 'encryptedSavedObjects'],
isEnabled(config: Legacy.KibanaConfig) {
return (
config.get('xpack.alerting.enabled') === true &&
config.get('xpack.actions.enabled') === true &&
config.get('xpack.encryptedSavedObjects.enabled') === true &&
config.get('xpack.task_manager.enabled') === true
);
},
config(Joi: Root) {
return Joi.object()
.keys({
enabled: Joi.boolean().default(true),
})
.default();
},
uiExports: {
mappings,
},
} as Legacy.PluginSpecOptions);
}

View file

@ -251,7 +251,7 @@ export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION, ALERT_TYPE_CLUSTER_ST
/**
* Matches the id for the built-in in email action type
* See x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts
* See x-pack/plugins/actions/server/builtin_action_types/email.ts
*/
export const ALERT_ACTION_TYPE_EMAIL = '.email';

View file

@ -6,8 +6,6 @@
import { Root } from 'joi';
import { Legacy } from 'kibana';
import mappings from './mappings.json';
import { migrations } from './migrations';
import { createLegacyApi, getTaskManagerSetup } from './legacy';
export { LegacyTaskManagerApi, getTaskManagerSetup, getTaskManagerStart } from './legacy';
@ -21,19 +19,6 @@ import {
ArrayOrItem,
} from '../../../../../src/legacy/plugin_discovery/types';
const savedObjectSchemas = {
task: {
hidden: true,
isNamespaceAgnostic: true,
convertToAliasScript: `ctx._id = ctx._source.type + ':' + ctx._id`,
// legacy config is marked as any in core, no choice here
// eslint-disable-next-line @typescript-eslint/no-explicit-any
indexPattern(config: any) {
return config.get('xpack.task_manager.index');
},
},
};
export function taskManager(kibana: LegacyPluginApi): ArrayOrItem<LegacyPluginSpec> {
return new kibana.Plugin({
id: 'task_manager',
@ -58,7 +43,7 @@ export function taskManager(kibana: LegacyPluginApi): ArrayOrItem<LegacyPluginSp
server.expose(
createLegacyApi(
getTaskManagerSetup(server)!
.registerLegacyAPI({})
.registerLegacyAPI()
.then((taskManagerPlugin: TaskManager) => {
// we can't tell the Kibana Platform Task Manager plugin to
// to wait to `start` as that happens before legacy plugins
@ -77,10 +62,5 @@ export function taskManager(kibana: LegacyPluginApi): ArrayOrItem<LegacyPluginSp
)
);
},
uiExports: {
mappings,
migrations,
savedObjectSchemas,
},
} as Legacy.PluginSpecOptions);
}

View file

@ -1,34 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { Legacy } from 'kibana';
import { Root } from 'joi';
import { resolve } from 'path';
export function triggersActionsUI(kibana: any) {
return new kibana.Plugin({
id: 'triggers_actions_ui',
configPrefix: 'xpack.triggers_actions_ui',
isEnabled(config: Legacy.KibanaConfig) {
return (
config.get('xpack.triggers_actions_ui.enabled') &&
(config.get('xpack.actions.enabled') || config.get('xpack.alerting.enabled'))
);
},
publicDir: resolve(__dirname, 'public'),
require: ['kibana'],
config(Joi: Root) {
return Joi.object()
.keys({
enabled: Joi.boolean().default(true),
})
.default();
},
uiExports: {
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
},
});
}

View file

@ -1,2 +0,0 @@
// Imported EUI
@import 'src/legacy/ui/public/styles/_styling_constants';

View file

@ -53,6 +53,7 @@ import {
} from './routes';
import { IEventLogger, IEventLogService } from '../../event_log/server';
import { initializeActionsTelemetry, scheduleActionsTelemetry } from './usage/task';
import { setupSavedObjects } from './saved_objects';
const EVENT_LOG_PROVIDER = 'actions';
export const EVENT_LOG_ACTIONS = {
@ -133,19 +134,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
);
}
// Encrypted attributes
// - `secrets` properties will be encrypted
// - `config` will be included in AAD
// - everything else excluded from AAD
plugins.encryptedSavedObjects.registerType({
type: 'action',
attributesToEncrypt: new Set(['secrets']),
attributesToExcludeFromAAD: new Set(['name']),
});
plugins.encryptedSavedObjects.registerType({
type: 'action_task_params',
attributesToEncrypt: new Set(['apiKey']),
});
setupSavedObjects(core.savedObjects, plugins.encryptedSavedObjects);
plugins.eventLog.registerProviderActions(EVENT_LOG_PROVIDER, Object.values(EVENT_LOG_ACTIONS));
this.eventLogger = plugins.eventLog.getLogger({

View file

@ -0,0 +1,42 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { SavedObjectsServiceSetup } from 'kibana/server';
import mappings from './mappings.json';
import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server';
export function setupSavedObjects(
savedObjects: SavedObjectsServiceSetup,
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup
) {
savedObjects.registerType({
name: 'action',
hidden: false,
namespaceType: 'single',
mappings: mappings.action,
});
// Encrypted attributes
// - `secrets` properties will be encrypted
// - `config` will be included in AAD
// - everything else excluded from AAD
encryptedSavedObjects.registerType({
type: 'action',
attributesToEncrypt: new Set(['secrets']),
attributesToExcludeFromAAD: new Set(['name']),
});
savedObjects.registerType({
name: 'action_task_params',
hidden: false,
namespaceType: 'single',
mappings: mappings.action_task_params,
});
encryptedSavedObjects.registerType({
type: 'action_task_params',
attributesToEncrypt: new Set(['apiKey']),
});
}

View file

@ -58,6 +58,7 @@ import { Services } from './types';
import { registerAlertsUsageCollector } from './usage';
import { initializeAlertingTelemetry, scheduleAlertingTelemetry } from './usage/task';
import { IEventLogger, IEventLogService } from '../../event_log/server';
import { setupSavedObjects } from './saved_objects';
const EVENT_LOG_PROVIDER = 'alerting';
export const EVENT_LOG_ACTIONS = {
@ -134,17 +135,7 @@ export class AlertingPlugin {
);
}
// Encrypted attributes
plugins.encryptedSavedObjects.registerType({
type: 'alert',
attributesToEncrypt: new Set(['apiKey']),
attributesToExcludeFromAAD: new Set([
'scheduledTaskId',
'muteAll',
'mutedInstanceIds',
'updatedBy',
]),
});
setupSavedObjects(core.savedObjects, plugins.encryptedSavedObjects);
plugins.eventLog.registerProviderActions(EVENT_LOG_PROVIDER, Object.values(EVENT_LOG_ACTIONS));
this.eventLogger = plugins.eventLog.getLogger({

View file

@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { SavedObjectsServiceSetup } from 'kibana/server';
import mappings from './mappings.json';
import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server';
export function setupSavedObjects(
savedObjects: SavedObjectsServiceSetup,
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup
) {
savedObjects.registerType({
name: 'alert',
hidden: false,
namespaceType: 'single',
mappings: mappings.alert,
});
// Encrypted attributes
encryptedSavedObjects.registerType({
type: 'alert',
attributesToEncrypt: new Set(['apiKey']),
attributesToExcludeFromAAD: new Set([
'scheduledTaskId',
'muteAll',
'mutedInstanceIds',
'updatedBy',
]),
});
}

View file

@ -245,7 +245,7 @@ export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION, ALERT_TYPE_CLUSTER_ST
/**
* Matches the id for the built-in in email action type
* See x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts
* See x-pack/plugins/actions/server/builtin_action_types/email.ts
*/
export const ALERT_ACTION_TYPE_EMAIL = '.email';

View file

@ -10,7 +10,7 @@ set -e
./check_env_variables.sh
# Example: ./get_action_instances.sh
# https://github.com/elastic/kibana/blob/master/x-pack/legacy/plugins/actions/README.md#get-apiaction_find-find-actions
# https://github.com/elastic/kibana/blob/master/x-pack/plugins/actions/README.md#get-apiaction_find-find-actions
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET ${KIBANA_URL}${SPACE_URL}/api/action/_getAll \

View file

@ -10,7 +10,7 @@ set -e
./check_env_variables.sh
# Example: ./get_action_types.sh
# https://github.com/elastic/kibana/blob/master/x-pack/legacy/plugins/actions/README.md
# https://github.com/elastic/kibana/blob/master/x-pack/plugins/actions/README.md
curl -s -k \
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-X GET ${KIBANA_URL}${SPACE_URL}/api/action/types \

View file

@ -12,11 +12,10 @@ import { TaskManager } from './task_manager';
import { createTaskManager } from './create_task_manager';
import { TaskManagerConfig } from './config';
import { Middleware } from './lib/middleware';
import { setupSavedObjects } from './saved_objects';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PluginLegacyDependencies {}
export type TaskManagerSetupContract = {
registerLegacyAPI: (legacyDependencies: PluginLegacyDependencies) => Promise<TaskManager>;
registerLegacyAPI: () => Promise<TaskManager>;
} & Pick<TaskManager, 'addMiddleware' | 'registerTaskDefinitions'>;
export type TaskManagerStartContract = Pick<
@ -35,12 +34,18 @@ export class TaskManagerPlugin
this.currentConfig = {} as TaskManagerConfig;
}
public setup(core: CoreSetup, plugins: unknown): TaskManagerSetupContract {
public async setup(core: CoreSetup, plugins: unknown): Promise<TaskManagerSetupContract> {
const logger = this.initContext.logger.get('taskManager');
const config$ = this.initContext.config.create<TaskManagerConfig>();
const config = await this.initContext.config
.create<TaskManagerConfig>()
.pipe(first())
.toPromise();
setupSavedObjects(core.savedObjects, config);
return {
registerLegacyAPI: once((__LEGACY: PluginLegacyDependencies) => {
config$.subscribe(async config => {
registerLegacyAPI: once(() => {
(async () => {
const [{ savedObjects, elasticsearch }] = await core.getStartServices();
const savedObjectsRepository = savedObjects.createInternalRepository(['task']);
this.legacyTaskManager$.next(
@ -53,7 +58,7 @@ export class TaskManagerPlugin
})
);
this.legacyTaskManager$.complete();
});
})();
return this.taskManager;
}),
addMiddleware: (middleware: Middleware) => {

View file

@ -0,0 +1,23 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { SavedObjectsServiceSetup } from 'kibana/server';
import mappings from './mappings.json';
import { TaskManagerConfig } from '../config.js';
export function setupSavedObjects(
savedObjects: SavedObjectsServiceSetup,
config: TaskManagerConfig
) {
savedObjects.registerType({
name: 'task',
namespaceType: 'agnostic',
hidden: true,
convertToAliasScript: `ctx._id = ctx._source.type + ':' + ctx._id`,
mappings: mappings.task,
indexPattern: config.index,
});
}

View file

@ -240,7 +240,7 @@ export class TaskManager {
* @param taskDefinitions - The Kibana task definitions dictionary
*/
public registerTaskDefinitions(taskDefinitions: TaskDictionary<TaskDefinition>) {
this.assertUninitialized('register task definitions');
this.assertUninitialized('register task definitions', Object.keys(taskDefinitions).join(', '));
const duplicate = Object.keys(taskDefinitions).find(k => !!this.definitions[k]);
if (duplicate) {
throw new Error(`Task ${duplicate} is already defined!`);
@ -360,9 +360,11 @@ export class TaskManager {
* @param {string} message shown if task manager is already initialized
* @returns void
*/
private assertUninitialized(message: string) {
private assertUninitialized(message: string, context?: string) {
if (this.isStarted) {
throw new Error(`Cannot ${message} after the task manager is initialized!`);
throw new Error(
`${context ? `[${context}] ` : ''}Cannot ${message} after the task manager is initialized`
);
}
}
}

View file

@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import {
getExternalServiceSimulatorPath,
ExternalServiceSimulator,
} from '../../../../common/fixtures/plugins/actions';
} from '../../../../common/fixtures/plugins/actions_simulators';
// node ../scripts/functional_test_runner.js --grep "Actions.servicenddd" --config=test/alerting_api_integration/security_and_spaces/config.ts

View file

@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import {
getExternalServiceSimulatorPath,
ExternalServiceSimulator,
} from '../../../../common/fixtures/plugins/actions';
} from '../../../../common/fixtures/plugins/actions_simulators';
// eslint-disable-next-line import/no-default-export
export default function slackTest({ getService }: FtrProviderContext) {

View file

@ -8,7 +8,7 @@ import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import {
getExternalServiceSimulatorPath,
ExternalServiceSimulator,
} from '../../../../common/fixtures/plugins/actions';
} from '../../../../common/fixtures/plugins/actions_simulators';
// eslint-disable-next-line import/no-default-export
export default function webhookTest({ getService }: FtrProviderContext) {

View file

@ -8,7 +8,7 @@ import path from 'path';
import { CA_CERT_PATH } from '@kbn/dev-utils';
import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
import { services } from './services';
import { getAllExternalServiceSimulatorPaths } from './fixtures/plugins/actions';
import { getAllExternalServiceSimulatorPaths } from './fixtures/plugins/actions_simulators';
interface CreateTestConfigOptions {
license: string;
@ -75,7 +75,6 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
'some.non.existent.com',
])}`,
`--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`,
'--xpack.alerting.enabled=true',
'--xpack.eventLog.logEntries=true',
`--xpack.actions.preconfigured=${JSON.stringify([
{
@ -124,7 +123,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
])}`,
...disabledPlugins.map(key => `--xpack.${key}.enabled=false`),
`--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'alerts')}`,
`--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'actions')}`,
`--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'actions_simulators')}`,
`--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'task_manager')}`,
`--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'aad')}`,
`--server.xsrf.whitelist=${JSON.stringify(getAllExternalServiceSimulatorPaths())}`,

View file

@ -21,7 +21,7 @@ interface CheckAADRequest extends Hapi.Request {
// eslint-disable-next-line import/no-default-export
export default function(kibana: any) {
return new kibana.Plugin({
require: ['actions', 'alerting', 'encryptedSavedObjects'],
require: ['encryptedSavedObjects'],
name: 'aad-fixtures',
init(server: Legacy.Server) {
const newPlatform = ((server as unknown) as KbnServer).newPlatform;

View file

@ -36,7 +36,7 @@ export function getAllExternalServiceSimulatorPaths(): string[] {
// eslint-disable-next-line import/no-default-export
export default function(kibana: any) {
return new kibana.Plugin({
require: ['xpack_main', 'actions'],
require: ['xpack_main'],
name: NAME,
init: (server: Hapi.Server) => {
// this action is specifically NOT enabled in ../../config.ts

View file

@ -11,8 +11,8 @@ import { ActionTypeExecutorOptions, ActionType } from '../../../../../../plugins
// eslint-disable-next-line import/no-default-export
export default function(kibana: any) {
return new kibana.Plugin({
require: ['xpack_main', 'actions', 'alerting', 'elasticsearch'],
name: 'alerts',
require: ['xpack_main', 'elasticsearch'],
name: 'alerts-fixture',
init(server: any) {
const clusterClient = server.newPlatform.start.core.elasticsearch.legacy.client;
server.plugins.xpack_main.registerFeature({

View file

@ -31,7 +31,7 @@ const taskByIdQuery = (id: string) => ({
export default function(kibana: any) {
return new kibana.Plugin({
name: 'taskManagerHelpers',
require: ['elasticsearch', 'task_manager'],
require: ['elasticsearch'],
config(Joi: any) {
return Joi.object({

View file

@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import {
getExternalServiceSimulatorPath,
ExternalServiceSimulator,
} from '../../../../common/fixtures/plugins/actions';
} from '../../../../common/fixtures/plugins/actions_simulators';
// eslint-disable-next-line import/no-default-export
export default function pagerdutyTest({ getService }: FtrProviderContext) {

View file

@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import {
getExternalServiceSimulatorPath,
ExternalServiceSimulator,
} from '../../../../common/fixtures/plugins/actions';
} from '../../../../common/fixtures/plugins/actions_simulators';
// node ../scripts/functional_test_runner.js --grep "servicenow" --config=test/alerting_api_integration/security_and_spaces/config.ts

View file

@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import {
getExternalServiceSimulatorPath,
ExternalServiceSimulator,
} from '../../../../common/fixtures/plugins/actions';
} from '../../../../common/fixtures/plugins/actions_simulators';
// eslint-disable-next-line import/no-default-export
export default function slackTest({ getService }: FtrProviderContext) {

View file

@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import {
getExternalServiceSimulatorPath,
ExternalServiceSimulator,
} from '../../../../common/fixtures/plugins/actions';
} from '../../../../common/fixtures/plugins/actions_simulators';
const defaultValues: Record<string, any> = {
headers: null,

View file

@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../../../common/ftr_provider_context';
import {
getExternalServiceSimulatorPath,
ExternalServiceSimulator,
} from '../../../../common/fixtures/plugins/actions';
} from '../../../../common/fixtures/plugins/actions_simulators';
// eslint-disable-next-line import/no-default-export
export default function webhookTest({ getService }: FtrProviderContext) {

View file

@ -78,7 +78,6 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
'some.non.existent.com',
])}`,
`--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`,
'--xpack.alerting.enabled=true',
'--xpack.eventLog.logEntries=true',
...disabledPlugins.map(key => `--xpack.${key}.enabled=false`),
`--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'alerts')}`,

View file

@ -50,8 +50,6 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) {
`--elasticsearch.hosts=https://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`,
`--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`,
`--plugin-path=${join(__dirname, 'fixtures', 'plugins', 'alerts')}`,
'--xpack.actions.enabled=true',
'--xpack.alerting.enabled=true',
`--xpack.actions.preconfigured=${JSON.stringify([
{
id: 'my-slack1',

View file

@ -0,0 +1,9 @@
{
"id": "sample_task_plugin",
"version": "1.0.0",
"kibanaVersion": "kibana",
"configPath": ["xpack"],
"requiredPlugins": ["taskManager"],
"server": true,
"ui": false
}

View file

@ -0,0 +1,18 @@
{
"name": "sample_task_plugin",
"version": "1.0.0",
"kibana": {
"version": "kibana",
"templateVersion": "1.0.0"
},
"main": "target/test/plugin_api_integration/plugins/sample_task_plugin",
"scripts": {
"kbn": "node ../../../../../scripts/kbn.js",
"build": "rm -rf './target' && tsc"
},
"devDependencies": {
"typescript": "3.7.2"
},
"license": "Apache-2.0",
"dependencies": {}
}

View file

@ -4,4 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
export * from './server/';
import { SampleTaskManagerFixturePlugin } from './plugin';
export const plugin = () => new SampleTaskManagerFixturePlugin();

View file

@ -0,0 +1,252 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { schema } from '@kbn/config-schema';
import {
RequestHandlerContext,
KibanaRequest,
KibanaResponseFactory,
IKibanaResponse,
IRouter,
CoreSetup,
} from 'kibana/server';
import { EventEmitter } from 'events';
import { TaskManagerStartContract } from '../../../../../plugins/task_manager/server';
const scope = 'testing';
const taskManagerQuery = {
bool: {
filter: {
bool: {
must: [
{
term: {
'task.scope': scope,
},
},
],
},
},
},
};
export function initRoutes(
router: IRouter,
core: CoreSetup,
taskManagerStart: Promise<TaskManagerStartContract>,
taskTestingEvents: EventEmitter
) {
async function ensureIndexIsRefreshed() {
return await core.elasticsearch.adminClient.callAsInternalUser('indices.refresh', {
index: '.kibana_task_manager',
});
}
router.post(
{
path: `/api/sample_tasks/schedule`,
validate: {
body: schema.object({
task: schema.object({
taskType: schema.string(),
schedule: schema.maybe(
schema.object({
interval: schema.string(),
})
),
interval: schema.maybe(schema.string()),
params: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }),
state: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }),
id: schema.maybe(schema.string()),
}),
}),
},
},
async function(
context: RequestHandlerContext,
req: KibanaRequest<any, any, any, any>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
try {
const taskManager = await taskManagerStart;
const { task: taskFields } = req.body;
const task = {
...taskFields,
scope: [scope],
};
const taskResult = await taskManager.schedule(task, { req });
return res.ok({ body: taskResult });
} catch (err) {
return res.internalError({ body: err });
}
}
);
router.post(
{
path: `/api/sample_tasks/run_now`,
validate: {
body: schema.object({
task: schema.object({
id: schema.string({}),
}),
}),
},
},
async function(
context: RequestHandlerContext,
req: KibanaRequest<any, any, any, any>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
const {
task: { id },
} = req.body;
try {
const taskManager = await taskManagerStart;
return res.ok({ body: await taskManager.runNow(id) });
} catch (err) {
return res.ok({ body: { id, error: `${err}` } });
}
}
);
router.post(
{
path: `/api/sample_tasks/ensure_scheduled`,
validate: {
body: schema.object({
task: schema.object({
taskType: schema.string(),
params: schema.object({}),
state: schema.maybe(schema.object({})),
id: schema.maybe(schema.string()),
}),
}),
},
},
async function(
context: RequestHandlerContext,
req: KibanaRequest<any, any, any, any>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
try {
const { task: taskFields } = req.body;
const task = {
...taskFields,
scope: [scope],
};
const taskManager = await taskManagerStart;
const taskResult = await taskManager.ensureScheduled(task, { req });
return res.ok({ body: taskResult });
} catch (err) {
return res.ok({ body: err });
}
}
);
router.post(
{
path: `/api/sample_tasks/event`,
validate: {
body: schema.object({
event: schema.string(),
data: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }),
}),
},
},
async function(
context: RequestHandlerContext,
req: KibanaRequest<any, any, any, any>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
try {
const { event, data } = req.body;
taskTestingEvents.emit(event, data);
return res.ok({ body: event });
} catch (err) {
return res.ok({ body: err });
}
}
);
router.get(
{
path: `/api/sample_tasks`,
validate: {},
},
async function(
context: RequestHandlerContext,
req: KibanaRequest<any, any, any, any>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
try {
const taskManager = await taskManagerStart;
return res.ok({
body: await taskManager.fetch({
query: taskManagerQuery,
}),
});
} catch (err) {
return res.ok({ body: err });
}
}
);
router.get(
{
path: `/api/sample_tasks/task/{taskId}`,
validate: {
params: schema.object({
taskId: schema.string(),
}),
},
},
async function(
context: RequestHandlerContext,
req: KibanaRequest<any, any, any, any>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
try {
await ensureIndexIsRefreshed();
const taskManager = await taskManagerStart;
return res.ok({ body: await taskManager.get(req.params.taskId) });
} catch (err) {
return res.ok({ body: err });
}
return res.ok({ body: {} });
}
);
router.delete(
{
path: `/api/sample_tasks`,
validate: {},
},
async function(
context: RequestHandlerContext,
req: KibanaRequest<any, any, any, any>,
res: KibanaResponseFactory
): Promise<IKibanaResponse<any>> {
try {
let tasksFound = 0;
const taskManager = await taskManagerStart;
do {
const { docs: tasks } = await taskManager.fetch({
query: taskManagerQuery,
});
tasksFound = tasks.length;
await Promise.all(tasks.map(task => taskManager.remove(task.id)));
} while (tasksFound > 0);
return res.ok({ body: 'OK' });
} catch (err) {
return res.ok({ body: err });
}
}
);
}

View file

@ -0,0 +1,164 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { Plugin, CoreSetup, CoreStart } from 'kibana/server';
import { EventEmitter } from 'events';
import { Subject } from 'rxjs';
import { first } from 'rxjs/operators';
import { initRoutes } from './init_routes';
import {
TaskManagerSetupContract,
TaskManagerStartContract,
ConcreteTaskInstance,
} from '../../../../../plugins/task_manager/server';
import { DEFAULT_MAX_WORKERS } from '../../../../../plugins/task_manager/server/config';
// this plugin's dependendencies
export interface SampleTaskManagerFixtureSetupDeps {
taskManager: TaskManagerSetupContract;
}
export interface SampleTaskManagerFixtureStartDeps {
taskManager: TaskManagerStartContract;
}
export class SampleTaskManagerFixturePlugin
implements
Plugin<void, void, SampleTaskManagerFixtureSetupDeps, SampleTaskManagerFixtureStartDeps> {
taskManagerStart$: Subject<TaskManagerStartContract> = new Subject<TaskManagerStartContract>();
taskManagerStart: Promise<TaskManagerStartContract> = this.taskManagerStart$
.pipe(first())
.toPromise();
public setup(core: CoreSetup, { taskManager }: SampleTaskManagerFixtureSetupDeps) {
const taskTestingEvents = new EventEmitter();
taskTestingEvents.setMaxListeners(DEFAULT_MAX_WORKERS * 2);
const defaultSampleTaskConfig = {
timeout: '1m',
// This task allows tests to specify its behavior (whether it reschedules itself, whether it errors, etc)
// taskInstance.params has the following optional fields:
// nextRunMilliseconds: number - If specified, the run method will return a runAt that is now + nextRunMilliseconds
// failWith: string - If specified, the task will throw an error with the specified message
// failOn: number - If specified, the task will only throw the `failWith` error when `count` equals to the failOn value
// waitForParams : boolean - should the task stall ands wait to receive params asynchronously before using the default params
// waitForEvent : string - if provided, the task will stall (after completing the run) and wait for an asyn event before completing
createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => ({
async run() {
const { params, state, id } = taskInstance;
const prevState = state || { count: 0 };
const count = (prevState.count || 0) + 1;
const runParams = {
...params,
// if this task requires custom params provided async - wait for them
...(params.waitForParams ? await once(taskTestingEvents, id) : {}),
};
if (runParams.failWith) {
if (!runParams.failOn || (runParams.failOn && count === runParams.failOn)) {
throw new Error(runParams.failWith);
}
}
await core.elasticsearch.adminClient.callAsInternalUser('index', {
index: '.kibana_task_manager_test_result',
body: {
type: 'task',
taskId: taskInstance.id,
params: JSON.stringify(runParams),
state: JSON.stringify(state),
ranAt: new Date(),
},
refresh: true,
});
// Stall task run until a certain event is triggered
if (runParams.waitForEvent) {
await once(taskTestingEvents, runParams.waitForEvent);
}
return {
state: { count },
runAt: millisecondsFromNow(runParams.nextRunMilliseconds),
};
},
}),
};
taskManager.registerTaskDefinitions({
sampleTask: {
...defaultSampleTaskConfig,
type: 'sampleTask',
title: 'Sample Task',
description: 'A sample task for testing the task_manager.',
},
singleAttemptSampleTask: {
...defaultSampleTaskConfig,
type: 'singleAttemptSampleTask',
title: 'Failing Sample Task',
description:
'A sample task for testing the task_manager that fails on the first attempt to run.',
// fail after the first failed run
maxAttempts: 1,
},
});
taskManager.addMiddleware({
async beforeSave({ taskInstance, ...opts }) {
const modifiedInstance = {
...taskInstance,
params: {
originalParams: taskInstance.params,
superFly: 'My middleware param!',
},
};
return {
...opts,
taskInstance: modifiedInstance,
};
},
async beforeRun({ taskInstance, ...opts }) {
return {
...opts,
taskInstance: {
...taskInstance,
params: taskInstance.params.originalParams,
},
};
},
async beforeMarkRunning(context) {
return context;
},
});
initRoutes(core.http.createRouter(), core, this.taskManagerStart, taskTestingEvents);
}
public start(core: CoreStart, { taskManager }: SampleTaskManagerFixtureStartDeps) {
this.taskManagerStart$.next(taskManager);
this.taskManagerStart$.complete();
}
public stop() {}
}
function millisecondsFromNow(ms: number) {
if (!ms) {
return;
}
const dt = new Date();
dt.setTime(dt.getTime() + ms);
return dt;
}
const once = function(emitter: EventEmitter, event: string): Promise<Record<string, unknown>> {
return new Promise(resolve => {
emitter.once(event, data => resolve(data || {}));
});
};

View file

@ -1,150 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
const { DEFAULT_MAX_WORKERS } = require('../../../../plugins/task_manager/server/config.ts');
const { EventEmitter } = require('events');
import { initRoutes } from './init_routes';
const once = function(emitter, event) {
return new Promise(resolve => {
emitter.once(event, data => resolve(data || {}));
});
};
export default function TaskTestingAPI(kibana) {
const taskTestingEvents = new EventEmitter();
taskTestingEvents.setMaxListeners(DEFAULT_MAX_WORKERS * 2);
return new kibana.Plugin({
name: 'sampleTask',
require: ['elasticsearch', 'task_manager'],
config(Joi) {
return Joi.object({
enabled: Joi.boolean().default(true),
}).default();
},
init(server) {
const taskManager = {
...server.newPlatform.setup.plugins.taskManager,
...server.newPlatform.start.plugins.taskManager,
};
const legacyTaskManager = server.plugins.task_manager;
const defaultSampleTaskConfig = {
timeout: '1m',
// This task allows tests to specify its behavior (whether it reschedules itself, whether it errors, etc)
// taskInstance.params has the following optional fields:
// nextRunMilliseconds: number - If specified, the run method will return a runAt that is now + nextRunMilliseconds
// failWith: string - If specified, the task will throw an error with the specified message
// failOn: number - If specified, the task will only throw the `failWith` error when `count` equals to the failOn value
// waitForParams : boolean - should the task stall ands wait to receive params asynchronously before using the default params
// waitForEvent : string - if provided, the task will stall (after completing the run) and wait for an asyn event before completing
createTaskRunner: ({ taskInstance }) => ({
async run() {
const { params, state, id } = taskInstance;
const prevState = state || { count: 0 };
const count = (prevState.count || 0) + 1;
const runParams = {
...params,
// if this task requires custom params provided async - wait for them
...(params.waitForParams ? await once(taskTestingEvents, id) : {}),
};
if (runParams.failWith) {
if (!runParams.failOn || (runParams.failOn && count === runParams.failOn)) {
throw new Error(runParams.failWith);
}
}
const callCluster = server.plugins.elasticsearch.getCluster('admin')
.callWithInternalUser;
await callCluster('index', {
index: '.kibana_task_manager_test_result',
body: {
type: 'task',
taskId: taskInstance.id,
params: JSON.stringify(runParams),
state: JSON.stringify(state),
ranAt: new Date(),
},
refresh: true,
});
// Stall task run until a certain event is triggered
if (runParams.waitForEvent) {
await once(taskTestingEvents, runParams.waitForEvent);
}
return {
state: { count },
runAt: millisecondsFromNow(runParams.nextRunMilliseconds),
};
},
}),
};
taskManager.registerTaskDefinitions({
sampleTask: {
...defaultSampleTaskConfig,
title: 'Sample Task',
description: 'A sample task for testing the task_manager.',
},
singleAttemptSampleTask: {
...defaultSampleTaskConfig,
title: 'Failing Sample Task',
description:
'A sample task for testing the task_manager that fails on the first attempt to run.',
// fail after the first failed run
maxAttempts: 1,
},
});
taskManager.addMiddleware({
async beforeSave({ taskInstance, ...opts }) {
const modifiedInstance = {
...taskInstance,
params: {
originalParams: taskInstance.params,
superFly: 'My middleware param!',
},
};
return {
...opts,
taskInstance: modifiedInstance,
};
},
async beforeRun({ taskInstance, ...opts }) {
return {
...opts,
taskInstance: {
...taskInstance,
params: taskInstance.params.originalParams,
},
};
},
});
initRoutes(server, taskManager, legacyTaskManager, taskTestingEvents);
},
});
}
function millisecondsFromNow(ms) {
if (!ms) {
return;
}
const dt = new Date();
dt.setTime(dt.getTime() + ms);
return dt;
}

View file

@ -1,236 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import Joi from 'joi';
const scope = 'testing';
const taskManagerQuery = {
bool: {
filter: {
bool: {
must: [
{
term: {
'task.scope': scope,
},
},
],
},
},
},
};
export function initRoutes(server, taskManager, legacyTaskManager, taskTestingEvents) {
const callCluster = server.plugins.elasticsearch.getCluster('admin').callWithInternalUser;
async function ensureIndexIsRefreshed() {
return await callCluster('indices.refresh', {
index: '.kibana_task_manager',
});
}
server.route({
path: '/api/sample_tasks/schedule',
method: 'POST',
config: {
validate: {
payload: Joi.object({
task: Joi.object({
taskType: Joi.string().required(),
schedule: Joi.object({
interval: Joi.string(),
}).optional(),
interval: Joi.string().optional(),
params: Joi.object().required(),
state: Joi.object().optional(),
id: Joi.string().optional(),
}),
}),
},
},
async handler(request) {
try {
const { task: taskFields } = request.payload;
const task = {
...taskFields,
scope: [scope],
};
const taskResult = await taskManager.schedule(task, { request });
return taskResult;
} catch (err) {
return err;
}
},
});
/*
Schedule using legacy Api
*/
server.route({
path: '/api/sample_tasks/schedule_legacy',
method: 'POST',
config: {
validate: {
payload: Joi.object({
task: Joi.object({
taskType: Joi.string().required(),
schedule: Joi.object({
interval: Joi.string(),
}).optional(),
interval: Joi.string().optional(),
params: Joi.object().required(),
state: Joi.object().optional(),
id: Joi.string().optional(),
}),
}),
},
},
async handler(request) {
try {
const { task: taskFields } = request.payload;
const task = {
...taskFields,
scope: [scope],
};
const taskResult = await legacyTaskManager.schedule(task, { request });
return taskResult;
} catch (err) {
return err;
}
},
});
server.route({
path: '/api/sample_tasks/run_now',
method: 'POST',
config: {
validate: {
payload: Joi.object({
task: Joi.object({
id: Joi.string().optional(),
}),
}),
},
},
async handler(request) {
const {
task: { id },
} = request.payload;
try {
return await taskManager.runNow(id);
} catch (err) {
return { id, error: `${err}` };
}
},
});
server.route({
path: '/api/sample_tasks/ensure_scheduled',
method: 'POST',
config: {
validate: {
payload: Joi.object({
task: Joi.object({
taskType: Joi.string().required(),
params: Joi.object().required(),
state: Joi.object().optional(),
id: Joi.string().optional(),
}),
}),
},
},
async handler(request) {
try {
const { task: taskFields } = request.payload;
const task = {
...taskFields,
scope: [scope],
};
const taskResult = await taskManager.ensureScheduled(task, { request });
return taskResult;
} catch (err) {
return err;
}
},
});
server.route({
path: '/api/sample_tasks/event',
method: 'POST',
config: {
validate: {
payload: Joi.object({
event: Joi.string().required(),
data: Joi.object()
.optional()
.default({}),
}),
},
},
async handler(request) {
try {
const { event, data } = request.payload;
taskTestingEvents.emit(event, data);
return { event };
} catch (err) {
return err;
}
},
});
server.route({
path: '/api/sample_tasks',
method: 'GET',
async handler() {
try {
return taskManager.fetch({
query: taskManagerQuery,
});
} catch (err) {
return err;
}
},
});
server.route({
path: '/api/sample_tasks/task/{taskId}',
method: 'GET',
async handler(request) {
try {
await ensureIndexIsRefreshed();
return await taskManager.get(request.params.taskId);
} catch (err) {
return err;
}
},
});
server.route({
path: '/api/sample_tasks',
method: 'DELETE',
async handler() {
try {
let tasksFound = 0;
do {
const { docs: tasks } = await taskManager.fetch({
query: taskManagerQuery,
});
tasksFound = tasks.length;
await Promise.all(tasks.map(task => taskManager.remove(task.id)));
} while (tasksFound > 0);
return 'OK';
} catch (err) {
return err;
}
},
});
}

View file

@ -1,12 +0,0 @@
{
"name": "sample_task_plugin",
"version": "1.0.0",
"kibana": {
"version": "kibana",
"templateVersion": "1.0.0"
},
"license": "Apache-2.0",
"dependencies": {
"joi": "^13.5.2"
}
}

View file

@ -11,7 +11,7 @@ import supertestAsPromised from 'supertest-as-promised';
const {
task: { properties: taskManagerIndexMapping },
} = require('../../../../legacy/plugins/task_manager/server/mappings.json');
} = require('../../../../plugins/task_manager/server/saved_objects/mappings.json');
const {
DEFAULT_MAX_WORKERS,
@ -90,15 +90,6 @@ export default function({ getService }) {
.then(response => response.body);
}
function scheduleTaskUsingLegacyApi(task) {
return supertest
.post('/api/sample_tasks/schedule_legacy')
.set('kbn-xsrf', 'xxx')
.send({ task })
.expect(200)
.then(response => response.body);
}
function runTaskNow(task) {
return supertest
.post('/api/sample_tasks/run_now')
@ -587,15 +578,5 @@ export default function({ getService }) {
expect(getTaskById(tasks, longRunningTask.id).state.count).to.eql(1);
});
});
it('should retain the legacy api until v8.0.0', async () => {
const result = await scheduleTaskUsingLegacyApi({
id: 'task-with-legacy-api',
taskType: 'sampleTask',
params: {},
});
expect(result.id).to.be('task-with-legacy-api');
});
});
}