mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Shim alerting plugin for new platform (#46954)
* Routes to register inside the init function * Start shimming plugins and core * More shimming * Move code over to plugin.ts * Use new platform basePath service * Cleanup * Create alerts client factory * Move some functions to plugin start * Remove some dependencies from Hapi * Apply PR feedback; use new platform logger, rename interfaces * Use new platform mocks for logger * Use new platform elasticsearch service * Fix type check failures * Fix failing jest tests * Remove logger from provided alert type service * Add unit tests for alerts client factory * Cleanup and fix type check errors * Change structure exposed via plugin * Use shim file, split plugin dependencies between setup and start contract, new task runner factory * Apply PR feedback * Fix ESLint errors * Set return type on plugin functions * Typo * Fix typecheck errors * IClusterClient
This commit is contained in:
parent
a691ba267f
commit
cbc2b1ad52
50 changed files with 1080 additions and 769 deletions
|
@ -37,7 +37,7 @@ When security is enabled, users who create alerts will need the `manage_api_key`
|
|||
|
||||
### Methods
|
||||
|
||||
**server.plugins.alerting.registerType(options)**
|
||||
**server.plugins.alerting.setup.registerType(options)**
|
||||
|
||||
The following table describes the properties of the `options` object.
|
||||
|
||||
|
@ -71,7 +71,7 @@ This example receives server and threshold as parameters. It will read the CPU u
|
|||
```
|
||||
import { schema } from '@kbn/config-schema';
|
||||
...
|
||||
server.plugins.alerting.registerType({
|
||||
server.plugins.alerting.setup.registerType({
|
||||
id: 'my-alert-type',
|
||||
name: 'My alert type',
|
||||
validate: {
|
||||
|
@ -129,7 +129,7 @@ server.plugins.alerting.registerType({
|
|||
This example only receives threshold as a parameter. It will read the CPU usage of all the servers and schedule individual actions if the reading for a server is greater than the threshold. This is a better implementation than above as only one query is performed for all the servers instead of one query per server.
|
||||
|
||||
```
|
||||
server.plugins.alerting.registerType({
|
||||
server.plugins.alerting.setup.registerType({
|
||||
id: 'my-alert-type',
|
||||
name: 'My alert type',
|
||||
validate: {
|
||||
|
|
|
@ -9,7 +9,14 @@ import { Root } from 'joi';
|
|||
import { init } from './server';
|
||||
import mappings from './mappings.json';
|
||||
|
||||
export { AlertingPlugin, AlertsClient, AlertType, AlertExecutorOptions } from './server';
|
||||
export {
|
||||
AlertingPlugin,
|
||||
AlertsClient,
|
||||
AlertType,
|
||||
AlertExecutorOptions,
|
||||
PluginSetupContract,
|
||||
PluginStartContract,
|
||||
} from './server';
|
||||
|
||||
export function alerting(kibana: any) {
|
||||
return new kibana.Plugin({
|
||||
|
|
|
@ -4,31 +4,15 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
jest.mock('./lib/get_create_task_runner_function', () => ({
|
||||
getCreateTaskRunnerFunction: jest.fn().mockReturnValue(jest.fn()),
|
||||
}));
|
||||
|
||||
import { TaskRunnerFactory } from './lib';
|
||||
import { AlertTypeRegistry } from './alert_type_registry';
|
||||
import { SavedObjectsClientMock } from '../../../../../src/core/server/mocks';
|
||||
import { taskManagerMock } from '../../task_manager/task_manager.mock';
|
||||
import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/plugin.mock';
|
||||
|
||||
const taskManager = taskManagerMock.create();
|
||||
|
||||
const alertTypeRegistryParams = {
|
||||
isSecurityEnabled: true,
|
||||
getServices() {
|
||||
return {
|
||||
log: jest.fn(),
|
||||
callCluster: jest.fn(),
|
||||
savedObjectsClient: SavedObjectsClientMock.create(),
|
||||
};
|
||||
},
|
||||
taskManager,
|
||||
executeAction: jest.fn(),
|
||||
getBasePath: jest.fn().mockReturnValue(undefined),
|
||||
spaceIdToNamespace: jest.fn().mockReturnValue(undefined),
|
||||
encryptedSavedObjectsPlugin: encryptedSavedObjectsMock.create(),
|
||||
taskRunnerFactory: new TaskRunnerFactory(),
|
||||
};
|
||||
|
||||
beforeEach(() => jest.resetAllMocks());
|
||||
|
@ -60,31 +44,20 @@ describe('register()', () => {
|
|||
executor: jest.fn(),
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { getCreateTaskRunnerFunction } = require('./lib/get_create_task_runner_function');
|
||||
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
|
||||
getCreateTaskRunnerFunction.mockReturnValue(jest.fn());
|
||||
registry.register(alertType);
|
||||
expect(taskManager.registerTaskDefinitions).toHaveBeenCalledTimes(1);
|
||||
expect(taskManager.registerTaskDefinitions.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"alerting:test": Object {
|
||||
"createTaskRunner": [MockFunction],
|
||||
"createTaskRunner": [Function],
|
||||
"title": "Test",
|
||||
"type": "alerting:test",
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(getCreateTaskRunnerFunction).toHaveBeenCalledWith({
|
||||
alertType,
|
||||
isSecurityEnabled: true,
|
||||
getServices: alertTypeRegistryParams.getServices,
|
||||
encryptedSavedObjectsPlugin: alertTypeRegistryParams.encryptedSavedObjectsPlugin,
|
||||
getBasePath: alertTypeRegistryParams.getBasePath,
|
||||
spaceIdToNamespace: alertTypeRegistryParams.spaceIdToNamespace,
|
||||
executeAction: alertTypeRegistryParams.executeAction,
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw an error if type is already registered', () => {
|
||||
|
|
|
@ -6,53 +6,24 @@
|
|||
|
||||
import Boom from 'boom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TaskManager } from '../../task_manager';
|
||||
import { getCreateTaskRunnerFunction } from './lib';
|
||||
import { ActionsPlugin } from '../../actions';
|
||||
import { EncryptedSavedObjectsPlugin } from '../../encrypted_saved_objects';
|
||||
import {
|
||||
AlertType,
|
||||
GetBasePathFunction,
|
||||
GetServicesFunction,
|
||||
SpaceIdToNamespaceFunction,
|
||||
} from './types';
|
||||
import { TaskRunnerFactory } from './lib';
|
||||
import { RunContext } from '../../task_manager';
|
||||
import { TaskManagerSetupContract } from './shim';
|
||||
import { AlertType } from './types';
|
||||
|
||||
interface ConstructorOptions {
|
||||
isSecurityEnabled: boolean;
|
||||
getServices: GetServicesFunction;
|
||||
taskManager: TaskManager;
|
||||
executeAction: ActionsPlugin['execute'];
|
||||
encryptedSavedObjectsPlugin: EncryptedSavedObjectsPlugin;
|
||||
spaceIdToNamespace: SpaceIdToNamespaceFunction;
|
||||
getBasePath: GetBasePathFunction;
|
||||
taskManager: TaskManagerSetupContract;
|
||||
taskRunnerFactory: TaskRunnerFactory;
|
||||
}
|
||||
|
||||
export class AlertTypeRegistry {
|
||||
private readonly getServices: GetServicesFunction;
|
||||
private readonly taskManager: TaskManager;
|
||||
private readonly executeAction: ActionsPlugin['execute'];
|
||||
private readonly taskManager: TaskManagerSetupContract;
|
||||
private readonly alertTypes: Map<string, AlertType> = new Map();
|
||||
private readonly encryptedSavedObjectsPlugin: EncryptedSavedObjectsPlugin;
|
||||
private readonly spaceIdToNamespace: SpaceIdToNamespaceFunction;
|
||||
private readonly getBasePath: GetBasePathFunction;
|
||||
private readonly isSecurityEnabled: boolean;
|
||||
private readonly taskRunnerFactory: TaskRunnerFactory;
|
||||
|
||||
constructor({
|
||||
encryptedSavedObjectsPlugin,
|
||||
executeAction,
|
||||
taskManager,
|
||||
getServices,
|
||||
spaceIdToNamespace,
|
||||
getBasePath,
|
||||
isSecurityEnabled,
|
||||
}: ConstructorOptions) {
|
||||
constructor({ taskManager, taskRunnerFactory }: ConstructorOptions) {
|
||||
this.taskManager = taskManager;
|
||||
this.executeAction = executeAction;
|
||||
this.encryptedSavedObjectsPlugin = encryptedSavedObjectsPlugin;
|
||||
this.getServices = getServices;
|
||||
this.getBasePath = getBasePath;
|
||||
this.spaceIdToNamespace = spaceIdToNamespace;
|
||||
this.isSecurityEnabled = isSecurityEnabled;
|
||||
this.taskRunnerFactory = taskRunnerFactory;
|
||||
}
|
||||
|
||||
public has(id: string) {
|
||||
|
@ -75,15 +46,8 @@ export class AlertTypeRegistry {
|
|||
[`alerting:${alertType.id}`]: {
|
||||
title: alertType.name,
|
||||
type: `alerting:${alertType.id}`,
|
||||
createTaskRunner: getCreateTaskRunnerFunction({
|
||||
alertType,
|
||||
isSecurityEnabled: this.isSecurityEnabled,
|
||||
getServices: this.getServices,
|
||||
executeAction: this.executeAction,
|
||||
encryptedSavedObjectsPlugin: this.encryptedSavedObjectsPlugin,
|
||||
getBasePath: this.getBasePath,
|
||||
spaceIdToNamespace: this.spaceIdToNamespace,
|
||||
}),
|
||||
createTaskRunner: (context: RunContext) =>
|
||||
this.taskRunnerFactory.create(alertType, context),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { AlertsClient } from './alerts_client';
|
||||
import { SavedObjectsClientMock } from '../../../../../src/core/server/mocks';
|
||||
import { SavedObjectsClientMock, loggingServiceMock } from '../../../../../src/core/server/mocks';
|
||||
import { taskManagerMock } from '../../task_manager/task_manager.mock';
|
||||
import { alertTypeRegistryMock } from './alert_type_registry.mock';
|
||||
|
||||
|
@ -15,13 +15,13 @@ const alertTypeRegistry = alertTypeRegistryMock.create();
|
|||
const savedObjectsClient = SavedObjectsClientMock.create();
|
||||
|
||||
const alertsClientParams = {
|
||||
log: jest.fn(),
|
||||
taskManager,
|
||||
alertTypeRegistry,
|
||||
savedObjectsClient,
|
||||
spaceId: 'default',
|
||||
getUserName: jest.fn(),
|
||||
createAPIKey: jest.fn(),
|
||||
logger: loggingServiceMock.create().get(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -418,16 +418,9 @@ describe('create()', () => {
|
|||
await expect(alertsClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Task manager error"`
|
||||
);
|
||||
expect(alertsClientParams.log).toHaveBeenCalledTimes(1);
|
||||
expect(alertsClientParams.log.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"alerting",
|
||||
"error",
|
||||
],
|
||||
"Failed to cleanup alert \\"1\\" after scheduling task failed. Error: Saved object delete error",
|
||||
]
|
||||
`);
|
||||
expect(alertsClientParams.logger.error).toHaveBeenCalledWith(
|
||||
'Failed to cleanup alert "1" after scheduling task failed. Error: Saved object delete error'
|
||||
);
|
||||
});
|
||||
|
||||
test('throws an error if alert type not registerd', async () => {
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
import Boom from 'boom';
|
||||
import { omit } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SavedObjectsClientContract, SavedObjectReference } from 'src/core/server';
|
||||
import { Alert, RawAlert, AlertTypeRegistry, AlertAction, Log, AlertType } from './types';
|
||||
import { TaskManager } from '../../task_manager';
|
||||
import { Logger, SavedObjectsClientContract, SavedObjectReference } from 'src/core/server';
|
||||
import { Alert, RawAlert, AlertTypeRegistry, AlertAction, AlertType } from './types';
|
||||
import { TaskManagerStartContract } from './shim';
|
||||
import { validateAlertTypeParams } from './lib';
|
||||
import { CreateAPIKeyResult as SecurityPluginCreateAPIKeyResult } from '../../../../plugins/security/server';
|
||||
|
||||
|
@ -23,8 +23,8 @@ interface SuccessCreateAPIKeyResult {
|
|||
export type CreateAPIKeyResult = FailedCreateAPIKeyResult | SuccessCreateAPIKeyResult;
|
||||
|
||||
interface ConstructorOptions {
|
||||
log: Log;
|
||||
taskManager: TaskManager;
|
||||
logger: Logger;
|
||||
taskManager: TaskManagerStartContract;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
alertTypeRegistry: AlertTypeRegistry;
|
||||
spaceId?: string;
|
||||
|
@ -78,10 +78,10 @@ interface UpdateOptions {
|
|||
}
|
||||
|
||||
export class AlertsClient {
|
||||
private readonly log: Log;
|
||||
private readonly logger: Logger;
|
||||
private readonly getUserName: () => Promise<string | null>;
|
||||
private readonly spaceId?: string;
|
||||
private readonly taskManager: TaskManager;
|
||||
private readonly taskManager: TaskManagerStartContract;
|
||||
private readonly savedObjectsClient: SavedObjectsClientContract;
|
||||
private readonly alertTypeRegistry: AlertTypeRegistry;
|
||||
private readonly createAPIKey: () => Promise<CreateAPIKeyResult>;
|
||||
|
@ -90,12 +90,12 @@ export class AlertsClient {
|
|||
alertTypeRegistry,
|
||||
savedObjectsClient,
|
||||
taskManager,
|
||||
log,
|
||||
logger,
|
||||
spaceId,
|
||||
getUserName,
|
||||
createAPIKey,
|
||||
}: ConstructorOptions) {
|
||||
this.log = log;
|
||||
this.logger = logger;
|
||||
this.getUserName = getUserName;
|
||||
this.spaceId = spaceId;
|
||||
this.taskManager = taskManager;
|
||||
|
@ -143,8 +143,7 @@ export class AlertsClient {
|
|||
await this.savedObjectsClient.delete('alert', createdAlert.id);
|
||||
} catch (err) {
|
||||
// Skip the cleanup error and throw the task manager error to avoid confusion
|
||||
this.log(
|
||||
['alerting', 'error'],
|
||||
this.logger.error(
|
||||
`Failed to cleanup alert "${createdAlert.id}" after scheduling task failed. Error: ${err.message}`
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,3 +7,4 @@
|
|||
export { init } from './init';
|
||||
export { AlertType, AlertingPlugin, AlertExecutorOptions } from './types';
|
||||
export { AlertsClient } from './alerts_client';
|
||||
export { PluginSetupContract, PluginStartContract } from './plugin';
|
||||
|
|
|
@ -4,181 +4,25 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Hapi from 'hapi';
|
||||
import uuid from 'uuid';
|
||||
import { Legacy } from 'kibana';
|
||||
import KbnServer from 'src/legacy/server/kbn_server';
|
||||
import { ActionsPlugin } from '../../actions';
|
||||
import { TaskManager } from '../../task_manager';
|
||||
import { AlertingPlugin, Services } from './types';
|
||||
import { AlertTypeRegistry } from './alert_type_registry';
|
||||
import { AlertsClient, CreateAPIKeyResult } from './alerts_client';
|
||||
import { SpacesPlugin } from '../../spaces';
|
||||
import { KibanaRequest } from '../../../../../src/core/server';
|
||||
import { EncryptedSavedObjectsPlugin } from '../../encrypted_saved_objects';
|
||||
import { PluginSetupContract as SecurityPluginSetupContract } from '../../../../plugins/security/server';
|
||||
import { createOptionalPlugin } from '../../../server/lib/optional_plugin';
|
||||
import {
|
||||
createAlertRoute,
|
||||
deleteAlertRoute,
|
||||
findRoute,
|
||||
getRoute,
|
||||
listAlertTypesRoute,
|
||||
updateAlertRoute,
|
||||
enableAlertRoute,
|
||||
disableAlertRoute,
|
||||
updateApiKeyRoute,
|
||||
muteAllAlertRoute,
|
||||
unmuteAllAlertRoute,
|
||||
muteAlertInstanceRoute,
|
||||
unmuteAlertInstanceRoute,
|
||||
} from './routes';
|
||||
import { Server, shim } from './shim';
|
||||
import { Plugin } from './plugin';
|
||||
import { AlertingPlugin } from './types';
|
||||
|
||||
// Extend PluginProperties to indicate which plugins are guaranteed to exist
|
||||
// due to being marked as dependencies
|
||||
interface Plugins extends Hapi.PluginProperties {
|
||||
actions: ActionsPlugin;
|
||||
task_manager: TaskManager;
|
||||
encrypted_saved_objects: EncryptedSavedObjectsPlugin;
|
||||
}
|
||||
export async function init(server: Server) {
|
||||
const { initializerContext, coreSetup, coreStart, pluginsSetup, pluginsStart } = shim(server);
|
||||
|
||||
interface Server extends Legacy.Server {
|
||||
plugins: Plugins;
|
||||
}
|
||||
const plugin = new Plugin(initializerContext);
|
||||
|
||||
export function init(server: Server) {
|
||||
const config = server.config();
|
||||
const kbnServer = (server as unknown) as KbnServer;
|
||||
const taskManager = server.plugins.task_manager;
|
||||
const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin');
|
||||
const spaces = createOptionalPlugin<SpacesPlugin>(
|
||||
config,
|
||||
'xpack.spaces',
|
||||
server.plugins,
|
||||
'spaces'
|
||||
);
|
||||
const security = createOptionalPlugin<SecurityPluginSetupContract>(
|
||||
config,
|
||||
'xpack.security',
|
||||
kbnServer.newPlatform.setup.plugins,
|
||||
'security'
|
||||
);
|
||||
const setupContract = await plugin.setup(coreSetup, pluginsSetup);
|
||||
const startContract = plugin.start(coreStart, pluginsStart);
|
||||
|
||||
server.plugins.xpack_main.registerFeature({
|
||||
id: 'alerting',
|
||||
name: 'Alerting',
|
||||
app: ['alerting', 'kibana'],
|
||||
privileges: {
|
||||
all: {
|
||||
savedObject: {
|
||||
all: ['alert'],
|
||||
read: [],
|
||||
},
|
||||
ui: [],
|
||||
api: ['alerting-read', 'alerting-all'],
|
||||
},
|
||||
read: {
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: ['alert'],
|
||||
},
|
||||
ui: [],
|
||||
api: ['alerting-read'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Encrypted attributes
|
||||
server.plugins.encrypted_saved_objects.registerType({
|
||||
type: 'alert',
|
||||
attributesToEncrypt: new Set(['apiKey']),
|
||||
attributesToExcludeFromAAD: new Set([
|
||||
'scheduledTaskId',
|
||||
'muted',
|
||||
'mutedInstanceIds',
|
||||
'updatedBy',
|
||||
]),
|
||||
});
|
||||
|
||||
function getServices(request: any): Services {
|
||||
return {
|
||||
log: (...args) => server.log(...args),
|
||||
callCluster: (...args) => callWithRequest(request, ...args),
|
||||
savedObjectsClient: server.savedObjects.getScopedSavedObjectsClient(request),
|
||||
};
|
||||
}
|
||||
function getBasePath(spaceId?: string): string {
|
||||
return spaces.isEnabled && spaceId
|
||||
? spaces.getBasePath(spaceId)
|
||||
: ((server.config().get('server.basePath') || '') as string);
|
||||
}
|
||||
function spaceIdToNamespace(spaceId?: string): string | undefined {
|
||||
return spaces.isEnabled && spaceId ? spaces.spaceIdToNamespace(spaceId) : undefined;
|
||||
}
|
||||
|
||||
const alertTypeRegistry = new AlertTypeRegistry({
|
||||
getServices,
|
||||
isSecurityEnabled: security.isEnabled,
|
||||
taskManager,
|
||||
executeAction: server.plugins.actions.execute,
|
||||
encryptedSavedObjectsPlugin: server.plugins.encrypted_saved_objects,
|
||||
getBasePath,
|
||||
spaceIdToNamespace,
|
||||
});
|
||||
|
||||
// Register routes
|
||||
createAlertRoute(server);
|
||||
deleteAlertRoute(server);
|
||||
findRoute(server);
|
||||
getRoute(server);
|
||||
listAlertTypesRoute(server);
|
||||
updateAlertRoute(server);
|
||||
enableAlertRoute(server);
|
||||
disableAlertRoute(server);
|
||||
updateApiKeyRoute(server);
|
||||
muteAllAlertRoute(server);
|
||||
unmuteAllAlertRoute(server);
|
||||
muteAlertInstanceRoute(server);
|
||||
unmuteAlertInstanceRoute(server);
|
||||
|
||||
// Expose functions
|
||||
server.decorate('request', 'getAlertsClient', function() {
|
||||
const request = this;
|
||||
const savedObjectsClient = request.getSavedObjectsClient();
|
||||
|
||||
const alertsClient = new AlertsClient({
|
||||
log: server.log.bind(server),
|
||||
savedObjectsClient,
|
||||
alertTypeRegistry,
|
||||
taskManager,
|
||||
spaceId: spaces.isEnabled ? spaces.getSpaceId(request) : undefined,
|
||||
async getUserName(): Promise<string | null> {
|
||||
if (!security.isEnabled) {
|
||||
return null;
|
||||
}
|
||||
const user = await security.authc.getCurrentUser(KibanaRequest.from(request));
|
||||
return user ? user.username : null;
|
||||
},
|
||||
async createAPIKey(): Promise<CreateAPIKeyResult> {
|
||||
if (!security.isEnabled) {
|
||||
return { created: false };
|
||||
}
|
||||
return {
|
||||
created: true,
|
||||
result: (await security.authc.createAPIKey(KibanaRequest.from(request), {
|
||||
name: `source: alerting, generated uuid: "${uuid.v4()}"`,
|
||||
role_descriptors: {},
|
||||
}))!,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
return alertsClient;
|
||||
return startContract.getAlertsClientWithRequest(this);
|
||||
});
|
||||
|
||||
const exposedFunctions: AlertingPlugin = {
|
||||
registerType: alertTypeRegistry.register.bind(alertTypeRegistry),
|
||||
listTypes: alertTypeRegistry.list.bind(alertTypeRegistry),
|
||||
setup: setupContract,
|
||||
start: startContract,
|
||||
};
|
||||
server.expose(exposedFunctions);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* 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 { Request } from 'hapi';
|
||||
import { AlertsClientFactory, ConstructorOpts } from './alerts_client_factory';
|
||||
import { alertTypeRegistryMock } from '../alert_type_registry.mock';
|
||||
import { taskManagerMock } from '../../../task_manager/task_manager.mock';
|
||||
import { KibanaRequest } from '../../../../../../src/core/server';
|
||||
import { loggingServiceMock } from '../../../../../../src/core/server/mocks';
|
||||
|
||||
jest.mock('../alerts_client');
|
||||
|
||||
const savedObjectsClient = jest.fn();
|
||||
const securityPluginSetup = {
|
||||
authc: {
|
||||
createAPIKey: jest.fn(),
|
||||
getCurrentUser: jest.fn(),
|
||||
},
|
||||
};
|
||||
const alertsClientFactoryParams: jest.Mocked<ConstructorOpts> = {
|
||||
logger: loggingServiceMock.create().get(),
|
||||
taskManager: taskManagerMock.create(),
|
||||
alertTypeRegistry: alertTypeRegistryMock.create(),
|
||||
getSpaceId: jest.fn(),
|
||||
};
|
||||
const fakeRequest: Request = {
|
||||
headers: {},
|
||||
getBasePath: () => '',
|
||||
path: '/',
|
||||
route: { settings: {} },
|
||||
url: {
|
||||
href: '/',
|
||||
},
|
||||
raw: {
|
||||
req: {
|
||||
url: '/',
|
||||
},
|
||||
},
|
||||
getSavedObjectsClient: () => savedObjectsClient,
|
||||
} as any;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
alertsClientFactoryParams.getSpaceId.mockReturnValue('default');
|
||||
});
|
||||
|
||||
test('creates an alerts client with proper constructor arguments', async () => {
|
||||
const factory = new AlertsClientFactory(alertsClientFactoryParams);
|
||||
factory.create(KibanaRequest.from(fakeRequest), fakeRequest);
|
||||
|
||||
expect(jest.requireMock('../alerts_client').AlertsClient).toHaveBeenCalledWith({
|
||||
savedObjectsClient,
|
||||
logger: alertsClientFactoryParams.logger,
|
||||
taskManager: alertsClientFactoryParams.taskManager,
|
||||
alertTypeRegistry: alertsClientFactoryParams.alertTypeRegistry,
|
||||
spaceId: 'default',
|
||||
getUserName: expect.any(Function),
|
||||
createAPIKey: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
test('getUserName() returns null when security is disabled', async () => {
|
||||
const factory = new AlertsClientFactory(alertsClientFactoryParams);
|
||||
factory.create(KibanaRequest.from(fakeRequest), fakeRequest);
|
||||
const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0];
|
||||
|
||||
const userNameResult = await constructorCall.getUserName();
|
||||
expect(userNameResult).toEqual(null);
|
||||
});
|
||||
|
||||
test('getUserName() returns a name when security is enabled', async () => {
|
||||
const factory = new AlertsClientFactory({
|
||||
...alertsClientFactoryParams,
|
||||
securityPluginSetup: securityPluginSetup as any,
|
||||
});
|
||||
factory.create(KibanaRequest.from(fakeRequest), fakeRequest);
|
||||
const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0];
|
||||
|
||||
securityPluginSetup.authc.getCurrentUser.mockResolvedValueOnce({ username: 'bob' });
|
||||
const userNameResult = await constructorCall.getUserName();
|
||||
expect(userNameResult).toEqual('bob');
|
||||
});
|
||||
|
||||
test('createAPIKey() returns { created: false } when security is disabled', async () => {
|
||||
const factory = new AlertsClientFactory(alertsClientFactoryParams);
|
||||
factory.create(KibanaRequest.from(fakeRequest), fakeRequest);
|
||||
const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0];
|
||||
|
||||
const createAPIKeyResult = await constructorCall.createAPIKey();
|
||||
expect(createAPIKeyResult).toEqual({ created: false });
|
||||
});
|
||||
|
||||
test('createAPIKey() returns an API key when security is enabled', async () => {
|
||||
const factory = new AlertsClientFactory({
|
||||
...alertsClientFactoryParams,
|
||||
securityPluginSetup: securityPluginSetup as any,
|
||||
});
|
||||
factory.create(KibanaRequest.from(fakeRequest), fakeRequest);
|
||||
const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0];
|
||||
|
||||
securityPluginSetup.authc.createAPIKey.mockResolvedValueOnce({ api_key: '123', id: 'abc' });
|
||||
const createAPIKeyResult = await constructorCall.createAPIKey();
|
||||
expect(createAPIKeyResult).toEqual({ created: true, result: { api_key: '123', id: 'abc' } });
|
||||
});
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 Hapi from 'hapi';
|
||||
import uuid from 'uuid';
|
||||
import { AlertTypeRegistry } from '../types';
|
||||
import { AlertsClient } from '../alerts_client';
|
||||
import { SecurityPluginStartContract, TaskManagerStartContract } from '../shim';
|
||||
import { KibanaRequest, Logger } from '../../../../../../src/core/server';
|
||||
|
||||
export interface ConstructorOpts {
|
||||
logger: Logger;
|
||||
taskManager: TaskManagerStartContract;
|
||||
alertTypeRegistry: AlertTypeRegistry;
|
||||
securityPluginSetup?: SecurityPluginStartContract;
|
||||
getSpaceId: (request: Hapi.Request) => string | undefined;
|
||||
}
|
||||
|
||||
export class AlertsClientFactory {
|
||||
private readonly logger: Logger;
|
||||
private readonly taskManager: TaskManagerStartContract;
|
||||
private readonly alertTypeRegistry: AlertTypeRegistry;
|
||||
private readonly securityPluginSetup?: SecurityPluginStartContract;
|
||||
private readonly getSpaceId: (request: Hapi.Request) => string | undefined;
|
||||
|
||||
constructor(options: ConstructorOpts) {
|
||||
this.logger = options.logger;
|
||||
this.getSpaceId = options.getSpaceId;
|
||||
this.taskManager = options.taskManager;
|
||||
this.alertTypeRegistry = options.alertTypeRegistry;
|
||||
this.securityPluginSetup = options.securityPluginSetup;
|
||||
}
|
||||
|
||||
public create(request: KibanaRequest, legacyRequest: Hapi.Request): AlertsClient {
|
||||
const { securityPluginSetup } = this;
|
||||
return new AlertsClient({
|
||||
logger: this.logger,
|
||||
taskManager: this.taskManager,
|
||||
alertTypeRegistry: this.alertTypeRegistry,
|
||||
savedObjectsClient: legacyRequest.getSavedObjectsClient(),
|
||||
spaceId: this.getSpaceId(legacyRequest),
|
||||
async getUserName() {
|
||||
if (!securityPluginSetup) {
|
||||
return null;
|
||||
}
|
||||
const user = await securityPluginSetup.authc.getCurrentUser(request);
|
||||
return user ? user.username : null;
|
||||
},
|
||||
async createAPIKey() {
|
||||
if (!securityPluginSetup) {
|
||||
return { created: false };
|
||||
}
|
||||
return {
|
||||
created: true,
|
||||
result: (await securityPluginSetup.authc.createAPIKey(request, {
|
||||
name: `source: alerting, generated uuid: "${uuid.v4()}"`,
|
||||
role_descriptors: {},
|
||||
}))!,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import { AlertType } from '../types';
|
||||
import { createExecutionHandler } from './create_execution_handler';
|
||||
import { loggingServiceMock } from '../../../../../../src/core/server/mocks';
|
||||
|
||||
const alertType: AlertType = {
|
||||
id: 'test',
|
||||
|
@ -15,7 +16,6 @@ const alertType: AlertType = {
|
|||
};
|
||||
|
||||
const createExecutionHandlerParams = {
|
||||
log: jest.fn(),
|
||||
executeAction: jest.fn(),
|
||||
spaceId: 'default',
|
||||
alertId: '1',
|
||||
|
@ -23,6 +23,7 @@ const createExecutionHandlerParams = {
|
|||
spaceIdToNamespace: jest.fn().mockReturnValue(undefined),
|
||||
getBasePath: jest.fn().mockReturnValue(undefined),
|
||||
alertType,
|
||||
logger: loggingServiceMock.create().get(),
|
||||
actions: [
|
||||
{
|
||||
id: '1',
|
||||
|
@ -133,8 +134,7 @@ test(`logs an error when action group isn't part of actionGroups available for t
|
|||
alertInstanceId: '2',
|
||||
});
|
||||
expect(result).toBeUndefined();
|
||||
expect(createExecutionHandlerParams.log).toHaveBeenCalledWith(
|
||||
['error', 'alerting'],
|
||||
expect(createExecutionHandlerParams.logger.error).toHaveBeenCalledWith(
|
||||
'Invalid action group "invalid-group" for alert "test".'
|
||||
);
|
||||
});
|
||||
|
|
|
@ -4,18 +4,19 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { AlertAction, State, Context, AlertType, Log } from '../types';
|
||||
import { ActionsPlugin } from '../../../actions';
|
||||
import { AlertAction, State, Context, AlertType } from '../types';
|
||||
import { ActionsPluginStartContract } from '../shim';
|
||||
import { Logger } from '../../../../../../src/core/server';
|
||||
import { transformActionParams } from './transform_action_params';
|
||||
|
||||
interface CreateExecutionHandlerOptions {
|
||||
alertId: string;
|
||||
executeAction: ActionsPlugin['execute'];
|
||||
executeAction: ActionsPluginStartContract['execute'];
|
||||
actions: AlertAction[];
|
||||
spaceId: string;
|
||||
apiKey?: string;
|
||||
alertType: AlertType;
|
||||
log: Log;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
interface ExecutionHandlerOptions {
|
||||
|
@ -26,7 +27,7 @@ interface ExecutionHandlerOptions {
|
|||
}
|
||||
|
||||
export function createExecutionHandler({
|
||||
log,
|
||||
logger,
|
||||
alertId,
|
||||
executeAction,
|
||||
actions: alertActions,
|
||||
|
@ -36,10 +37,7 @@ export function createExecutionHandler({
|
|||
}: CreateExecutionHandlerOptions) {
|
||||
return async ({ actionGroup, context, state, alertInstanceId }: ExecutionHandlerOptions) => {
|
||||
if (!alertType.actionGroups.includes(actionGroup)) {
|
||||
log(
|
||||
['error', 'alerting'],
|
||||
`Invalid action group "${actionGroup}" for alert "${alertType.id}".`
|
||||
);
|
||||
logger.error(`Invalid action group "${actionGroup}" for alert "${alertType.id}".`);
|
||||
return;
|
||||
}
|
||||
const actions = alertActions
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
export { AlertInstance } from './alert_instance';
|
||||
export { getCreateTaskRunnerFunction } from './get_create_task_runner_function';
|
||||
export { validateAlertTypeParams } from './validate_alert_type_params';
|
||||
export { parseDuration, getDurationSchema } from './parse_duration';
|
||||
export { AlertsClientFactory } from './alerts_client_factory';
|
||||
export { TaskRunnerFactory } from './task_runner_factory';
|
||||
|
|
|
@ -8,11 +8,21 @@ import sinon from 'sinon';
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import { AlertExecutorOptions } from '../types';
|
||||
import { ConcreteTaskInstance } from '../../../task_manager';
|
||||
import { SavedObjectsClientMock } from '../../../../../../src/core/server/mocks';
|
||||
import { getCreateTaskRunnerFunction } from './get_create_task_runner_function';
|
||||
import { TaskRunnerContext, TaskRunnerFactory } from './task_runner_factory';
|
||||
import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/plugin.mock';
|
||||
import {
|
||||
SavedObjectsClientMock,
|
||||
loggingServiceMock,
|
||||
} from '../../../../../../src/core/server/mocks';
|
||||
|
||||
const alertType = {
|
||||
id: 'test',
|
||||
name: 'My test alert',
|
||||
actionGroups: ['default'],
|
||||
executor: jest.fn(),
|
||||
};
|
||||
let fakeTimer: sinon.SinonFakeTimers;
|
||||
let taskRunnerFactory: TaskRunnerFactory;
|
||||
let mockedTaskInstance: ConcreteTaskInstance;
|
||||
|
||||
beforeAll(() => {
|
||||
|
@ -35,6 +45,8 @@ beforeAll(() => {
|
|||
},
|
||||
ownerId: null,
|
||||
};
|
||||
taskRunnerFactory = new TaskRunnerFactory();
|
||||
taskRunnerFactory.initialize(taskRunnerFactoryInitializerParams);
|
||||
});
|
||||
|
||||
afterAll(() => fakeTimer.restore());
|
||||
|
@ -47,17 +59,12 @@ const services = {
|
|||
savedObjectsClient,
|
||||
};
|
||||
|
||||
const getCreateTaskRunnerFunctionParams = {
|
||||
const taskRunnerFactoryInitializerParams: jest.Mocked<TaskRunnerContext> = {
|
||||
isSecurityEnabled: true,
|
||||
getServices: jest.fn().mockReturnValue(services),
|
||||
alertType: {
|
||||
id: 'test',
|
||||
name: 'My test alert',
|
||||
actionGroups: ['default'],
|
||||
executor: jest.fn(),
|
||||
},
|
||||
executeAction: jest.fn(),
|
||||
encryptedSavedObjectsPlugin,
|
||||
logger: loggingServiceMock.create().get(),
|
||||
spaceIdToNamespace: jest.fn().mockReturnValue(undefined),
|
||||
getBasePath: jest.fn().mockReturnValue(undefined),
|
||||
};
|
||||
|
@ -94,11 +101,28 @@ const mockedAlertTypeSavedObject = {
|
|||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
getCreateTaskRunnerFunctionParams.getServices.mockReturnValue(services);
|
||||
taskRunnerFactoryInitializerParams.getServices.mockReturnValue(services);
|
||||
});
|
||||
|
||||
test(`throws an error if factory isn't initialized`, () => {
|
||||
const factory = new TaskRunnerFactory();
|
||||
expect(() =>
|
||||
factory.create(alertType, { taskInstance: mockedTaskInstance })
|
||||
).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory not initialized"`);
|
||||
});
|
||||
|
||||
test(`throws an error if factory is already initialized`, () => {
|
||||
const factory = new TaskRunnerFactory();
|
||||
factory.initialize(taskRunnerFactoryInitializerParams);
|
||||
expect(() =>
|
||||
factory.initialize(taskRunnerFactoryInitializerParams)
|
||||
).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory already initialized"`);
|
||||
});
|
||||
|
||||
test('successfully executes the task', async () => {
|
||||
const createTaskRunner = getCreateTaskRunnerFunction(getCreateTaskRunnerFunctionParams);
|
||||
const taskRunner = taskRunnerFactory.create(alertType, {
|
||||
taskInstance: mockedTaskInstance,
|
||||
});
|
||||
savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject);
|
||||
encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
|
@ -108,8 +132,7 @@ test('successfully executes the task', async () => {
|
|||
},
|
||||
references: [],
|
||||
});
|
||||
const runner = createTaskRunner({ taskInstance: mockedTaskInstance });
|
||||
const runnerResult = await runner.run();
|
||||
const runnerResult = await taskRunner.run();
|
||||
expect(runnerResult).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"runAt": 1970-01-01T00:00:10.000Z,
|
||||
|
@ -120,8 +143,8 @@ test('successfully executes the task', async () => {
|
|||
},
|
||||
}
|
||||
`);
|
||||
expect(getCreateTaskRunnerFunctionParams.alertType.executor).toHaveBeenCalledTimes(1);
|
||||
const call = getCreateTaskRunnerFunctionParams.alertType.executor.mock.calls[0][0];
|
||||
expect(alertType.executor).toHaveBeenCalledTimes(1);
|
||||
const call = alertType.executor.mock.calls[0][0];
|
||||
expect(call.params).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"bar": true,
|
||||
|
@ -135,12 +158,12 @@ test('successfully executes the task', async () => {
|
|||
});
|
||||
|
||||
test('executeAction is called per alert instance that is scheduled', async () => {
|
||||
getCreateTaskRunnerFunctionParams.alertType.executor.mockImplementation(
|
||||
({ services: executorServices }: AlertExecutorOptions) => {
|
||||
executorServices.alertInstanceFactory('1').scheduleActions('default');
|
||||
}
|
||||
);
|
||||
const createTaskRunner = getCreateTaskRunnerFunction(getCreateTaskRunnerFunctionParams);
|
||||
alertType.executor.mockImplementation(({ services: executorServices }: AlertExecutorOptions) => {
|
||||
executorServices.alertInstanceFactory('1').scheduleActions('default');
|
||||
});
|
||||
const taskRunner = taskRunnerFactory.create(alertType, {
|
||||
taskInstance: mockedTaskInstance,
|
||||
});
|
||||
savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject);
|
||||
encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
|
@ -150,10 +173,9 @@ test('executeAction is called per alert instance that is scheduled', async () =>
|
|||
},
|
||||
references: [],
|
||||
});
|
||||
const runner = createTaskRunner({ taskInstance: mockedTaskInstance });
|
||||
await runner.run();
|
||||
expect(getCreateTaskRunnerFunctionParams.executeAction).toHaveBeenCalledTimes(1);
|
||||
expect(getCreateTaskRunnerFunctionParams.executeAction.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
await taskRunner.run();
|
||||
expect(taskRunnerFactoryInitializerParams.executeAction).toHaveBeenCalledTimes(1);
|
||||
expect(taskRunnerFactoryInitializerParams.executeAction.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"apiKey": "MTIzOmFiYw==",
|
||||
|
@ -168,22 +190,10 @@ test('executeAction is called per alert instance that is scheduled', async () =>
|
|||
});
|
||||
|
||||
test('persists alertInstances passed in from state, only if they are scheduled for execution', async () => {
|
||||
getCreateTaskRunnerFunctionParams.alertType.executor.mockImplementation(
|
||||
({ services: executorServices }: AlertExecutorOptions) => {
|
||||
executorServices.alertInstanceFactory('1').scheduleActions('default');
|
||||
}
|
||||
);
|
||||
const createTaskRunner = getCreateTaskRunnerFunction(getCreateTaskRunnerFunctionParams);
|
||||
savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject);
|
||||
encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
attributes: {
|
||||
apiKey: Buffer.from('123:abc').toString('base64'),
|
||||
},
|
||||
references: [],
|
||||
alertType.executor.mockImplementation(({ services: executorServices }: AlertExecutorOptions) => {
|
||||
executorServices.alertInstanceFactory('1').scheduleActions('default');
|
||||
});
|
||||
const runner = createTaskRunner({
|
||||
const taskRunner = taskRunnerFactory.create(alertType, {
|
||||
taskInstance: {
|
||||
...mockedTaskInstance,
|
||||
state: {
|
||||
|
@ -195,7 +205,16 @@ test('persists alertInstances passed in from state, only if they are scheduled f
|
|||
},
|
||||
},
|
||||
});
|
||||
const runnerResult = await runner.run();
|
||||
savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject);
|
||||
encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
attributes: {
|
||||
apiKey: Buffer.from('123:abc').toString('base64'),
|
||||
},
|
||||
references: [],
|
||||
});
|
||||
const runnerResult = await taskRunner.run();
|
||||
expect(runnerResult.state.alertInstances).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
|
@ -214,17 +233,17 @@ test('persists alertInstances passed in from state, only if they are scheduled f
|
|||
});
|
||||
|
||||
test('validates params before executing the alert type', async () => {
|
||||
const createTaskRunner = getCreateTaskRunnerFunction({
|
||||
...getCreateTaskRunnerFunctionParams,
|
||||
alertType: {
|
||||
...getCreateTaskRunnerFunctionParams.alertType,
|
||||
const taskRunner = taskRunnerFactory.create(
|
||||
{
|
||||
...alertType,
|
||||
validate: {
|
||||
params: schema.object({
|
||||
param1: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
{ taskInstance: mockedTaskInstance }
|
||||
);
|
||||
savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject);
|
||||
encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
|
@ -234,14 +253,15 @@ test('validates params before executing the alert type', async () => {
|
|||
},
|
||||
references: [],
|
||||
});
|
||||
const runner = createTaskRunner({ taskInstance: mockedTaskInstance });
|
||||
await expect(runner.run()).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
await expect(taskRunner.run()).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"alertTypeParams invalid: [param1]: expected value of type [string] but got [undefined]"`
|
||||
);
|
||||
});
|
||||
|
||||
test('throws error if reference not found', async () => {
|
||||
const createTaskRunner = getCreateTaskRunnerFunction(getCreateTaskRunnerFunctionParams);
|
||||
const taskRunner = taskRunnerFactory.create(alertType, {
|
||||
taskInstance: mockedTaskInstance,
|
||||
});
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
...mockedAlertTypeSavedObject,
|
||||
references: [],
|
||||
|
@ -254,14 +274,15 @@ test('throws error if reference not found', async () => {
|
|||
},
|
||||
references: [],
|
||||
});
|
||||
const runner = createTaskRunner({ taskInstance: mockedTaskInstance });
|
||||
await expect(runner.run()).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
await expect(taskRunner.run()).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Action reference \\"action_0\\" not found in alert id: 1"`
|
||||
);
|
||||
});
|
||||
|
||||
test('uses API key when provided', async () => {
|
||||
const createTaskRunner = getCreateTaskRunnerFunction(getCreateTaskRunnerFunctionParams);
|
||||
const taskRunner = taskRunnerFactory.create(alertType, {
|
||||
taskInstance: mockedTaskInstance,
|
||||
});
|
||||
savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject);
|
||||
encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
|
@ -271,23 +292,36 @@ test('uses API key when provided', async () => {
|
|||
},
|
||||
references: [],
|
||||
});
|
||||
const runner = createTaskRunner({ taskInstance: mockedTaskInstance });
|
||||
|
||||
await runner.run();
|
||||
expect(getCreateTaskRunnerFunctionParams.getServices).toHaveBeenCalledWith({
|
||||
await taskRunner.run();
|
||||
expect(taskRunnerFactoryInitializerParams.getServices).toHaveBeenCalledWith({
|
||||
getBasePath: expect.anything(),
|
||||
headers: {
|
||||
// base64 encoded "123:abc"
|
||||
authorization: 'ApiKey MTIzOmFiYw==',
|
||||
},
|
||||
path: '/',
|
||||
route: { settings: {} },
|
||||
url: {
|
||||
href: '/',
|
||||
},
|
||||
raw: {
|
||||
req: {
|
||||
url: '/',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test(`doesn't use API key when not provided`, async () => {
|
||||
const createTaskRunner = getCreateTaskRunnerFunction({
|
||||
...getCreateTaskRunnerFunctionParams,
|
||||
const factory = new TaskRunnerFactory();
|
||||
factory.initialize({
|
||||
...taskRunnerFactoryInitializerParams,
|
||||
isSecurityEnabled: false,
|
||||
});
|
||||
const taskRunner = factory.create(alertType, {
|
||||
taskInstance: mockedTaskInstance,
|
||||
});
|
||||
savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject);
|
||||
encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
|
@ -295,12 +329,21 @@ test(`doesn't use API key when not provided`, async () => {
|
|||
attributes: {},
|
||||
references: [],
|
||||
});
|
||||
const runner = createTaskRunner({ taskInstance: mockedTaskInstance });
|
||||
|
||||
await runner.run();
|
||||
await taskRunner.run();
|
||||
|
||||
expect(getCreateTaskRunnerFunctionParams.getServices).toHaveBeenCalledWith({
|
||||
expect(taskRunnerFactoryInitializerParams.getServices).toHaveBeenCalledWith({
|
||||
getBasePath: expect.anything(),
|
||||
headers: {},
|
||||
path: '/',
|
||||
route: { settings: {} },
|
||||
url: {
|
||||
href: '/',
|
||||
},
|
||||
raw: {
|
||||
req: {
|
||||
url: '/',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
|
@ -4,14 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ActionsPlugin } from '../../../actions';
|
||||
import { ConcreteTaskInstance } from '../../../task_manager';
|
||||
import { Logger } from '../../../../../../src/core/server';
|
||||
import { RunContext } from '../../../task_manager';
|
||||
import { createExecutionHandler } from './create_execution_handler';
|
||||
import { createAlertInstanceFactory } from './create_alert_instance_factory';
|
||||
import { AlertInstance } from './alert_instance';
|
||||
import { getNextRunAt } from './get_next_run_at';
|
||||
import { validateAlertTypeParams } from './validate_alert_type_params';
|
||||
import { EncryptedSavedObjectsPlugin } from '../../../encrypted_saved_objects';
|
||||
import { ActionsPluginStartContract, EncryptedSavedObjectsStartContract } from '../shim';
|
||||
import {
|
||||
AlertType,
|
||||
AlertServices,
|
||||
|
@ -21,32 +21,45 @@ import {
|
|||
SpaceIdToNamespaceFunction,
|
||||
} from '../types';
|
||||
|
||||
export interface CreateTaskRunnerFunctionOptions {
|
||||
export interface TaskRunnerContext {
|
||||
logger: Logger;
|
||||
isSecurityEnabled: boolean;
|
||||
getServices: GetServicesFunction;
|
||||
alertType: AlertType;
|
||||
executeAction: ActionsPlugin['execute'];
|
||||
encryptedSavedObjectsPlugin: EncryptedSavedObjectsPlugin;
|
||||
executeAction: ActionsPluginStartContract['execute'];
|
||||
encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract;
|
||||
spaceIdToNamespace: SpaceIdToNamespaceFunction;
|
||||
getBasePath: GetBasePathFunction;
|
||||
}
|
||||
|
||||
interface TaskRunnerOptions {
|
||||
taskInstance: ConcreteTaskInstance;
|
||||
}
|
||||
export class TaskRunnerFactory {
|
||||
private isInitialized = false;
|
||||
private taskRunnerContext?: TaskRunnerContext;
|
||||
|
||||
public initialize(taskRunnerContext: TaskRunnerContext) {
|
||||
if (this.isInitialized) {
|
||||
throw new Error('TaskRunnerFactory already initialized');
|
||||
}
|
||||
this.isInitialized = true;
|
||||
this.taskRunnerContext = taskRunnerContext;
|
||||
}
|
||||
|
||||
public create(alertType: AlertType, { taskInstance }: RunContext) {
|
||||
if (!this.isInitialized) {
|
||||
throw new Error('TaskRunnerFactory not initialized');
|
||||
}
|
||||
|
||||
const {
|
||||
logger,
|
||||
isSecurityEnabled,
|
||||
getServices,
|
||||
executeAction,
|
||||
encryptedSavedObjectsPlugin,
|
||||
spaceIdToNamespace,
|
||||
getBasePath,
|
||||
} = this.taskRunnerContext!;
|
||||
|
||||
export function getCreateTaskRunnerFunction({
|
||||
getServices,
|
||||
alertType,
|
||||
executeAction,
|
||||
encryptedSavedObjectsPlugin,
|
||||
spaceIdToNamespace,
|
||||
getBasePath,
|
||||
isSecurityEnabled,
|
||||
}: CreateTaskRunnerFunctionOptions) {
|
||||
return ({ taskInstance }: TaskRunnerOptions) => {
|
||||
return {
|
||||
run: async () => {
|
||||
async run() {
|
||||
const { alertId, spaceId } = taskInstance.params;
|
||||
const requestHeaders: Record<string, string> = {};
|
||||
const namespace = spaceIdToNamespace(spaceId);
|
||||
|
@ -69,6 +82,16 @@ export function getCreateTaskRunnerFunction({
|
|||
const fakeRequest = {
|
||||
headers: requestHeaders,
|
||||
getBasePath: () => getBasePath(spaceId),
|
||||
path: '/',
|
||||
route: { settings: {} },
|
||||
url: {
|
||||
href: '/',
|
||||
},
|
||||
raw: {
|
||||
req: {
|
||||
url: '/',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const services = getServices(fakeRequest);
|
||||
|
@ -97,7 +120,7 @@ export function getCreateTaskRunnerFunction({
|
|||
|
||||
const executionHandler = createExecutionHandler({
|
||||
alertId,
|
||||
log: services.log,
|
||||
logger,
|
||||
executeAction,
|
||||
apiKey,
|
||||
actions: actionsWithIds,
|
||||
|
@ -154,5 +177,5 @@ export function getCreateTaskRunnerFunction({
|
|||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
168
x-pack/legacy/plugins/alerting/server/plugin.ts
Normal file
168
x-pack/legacy/plugins/alerting/server/plugin.ts
Normal file
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* 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 Hapi from 'hapi';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { Services } from './types';
|
||||
import { AlertsClient } from './alerts_client';
|
||||
import { AlertTypeRegistry } from './alert_type_registry';
|
||||
import { AlertsClientFactory, TaskRunnerFactory } from './lib';
|
||||
import { IClusterClient, KibanaRequest, Logger } from '../../../../../src/core/server';
|
||||
import {
|
||||
AlertingPluginInitializerContext,
|
||||
AlertingCoreSetup,
|
||||
AlertingCoreStart,
|
||||
AlertingPluginsSetup,
|
||||
AlertingPluginsStart,
|
||||
} from './shim';
|
||||
import {
|
||||
createAlertRoute,
|
||||
deleteAlertRoute,
|
||||
findAlertRoute,
|
||||
getAlertRoute,
|
||||
listAlertTypesRoute,
|
||||
updateAlertRoute,
|
||||
enableAlertRoute,
|
||||
disableAlertRoute,
|
||||
updateApiKeyRoute,
|
||||
muteAllAlertRoute,
|
||||
unmuteAllAlertRoute,
|
||||
muteAlertInstanceRoute,
|
||||
unmuteAlertInstanceRoute,
|
||||
} from './routes';
|
||||
|
||||
export interface PluginSetupContract {
|
||||
registerType: AlertTypeRegistry['register'];
|
||||
}
|
||||
export interface PluginStartContract {
|
||||
listTypes: AlertTypeRegistry['list'];
|
||||
getAlertsClientWithRequest(request: Hapi.Request): AlertsClient;
|
||||
}
|
||||
|
||||
export class Plugin {
|
||||
private readonly logger: Logger;
|
||||
private alertTypeRegistry?: AlertTypeRegistry;
|
||||
private readonly taskRunnerFactory: TaskRunnerFactory;
|
||||
private adminClient?: IClusterClient;
|
||||
private serverBasePath?: string;
|
||||
|
||||
constructor(initializerContext: AlertingPluginInitializerContext) {
|
||||
this.logger = initializerContext.logger.get('plugins', 'alerting');
|
||||
this.taskRunnerFactory = new TaskRunnerFactory();
|
||||
}
|
||||
|
||||
public async setup(
|
||||
core: AlertingCoreSetup,
|
||||
plugins: AlertingPluginsSetup
|
||||
): Promise<PluginSetupContract> {
|
||||
this.adminClient = await core.elasticsearch.adminClient$.pipe(first()).toPromise();
|
||||
|
||||
plugins.xpack_main.registerFeature({
|
||||
id: 'alerting',
|
||||
name: 'Alerting',
|
||||
app: ['alerting', 'kibana'],
|
||||
privileges: {
|
||||
all: {
|
||||
savedObject: {
|
||||
all: ['alert'],
|
||||
read: [],
|
||||
},
|
||||
ui: [],
|
||||
api: ['alerting-read', 'alerting-all'],
|
||||
},
|
||||
read: {
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: ['alert'],
|
||||
},
|
||||
ui: [],
|
||||
api: ['alerting-read'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Encrypted attributes
|
||||
plugins.encrypted_saved_objects.registerType({
|
||||
type: 'alert',
|
||||
attributesToEncrypt: new Set(['apiKey']),
|
||||
attributesToExcludeFromAAD: new Set([
|
||||
'scheduledTaskId',
|
||||
'muted',
|
||||
'mutedInstanceIds',
|
||||
'updatedBy',
|
||||
]),
|
||||
});
|
||||
|
||||
const alertTypeRegistry = new AlertTypeRegistry({
|
||||
taskManager: plugins.task_manager,
|
||||
taskRunnerFactory: this.taskRunnerFactory,
|
||||
});
|
||||
this.alertTypeRegistry = alertTypeRegistry;
|
||||
this.serverBasePath = core.http.basePath.serverBasePath;
|
||||
|
||||
// Register routes
|
||||
core.http.route(createAlertRoute);
|
||||
core.http.route(deleteAlertRoute);
|
||||
core.http.route(findAlertRoute);
|
||||
core.http.route(getAlertRoute);
|
||||
core.http.route(listAlertTypesRoute);
|
||||
core.http.route(updateAlertRoute);
|
||||
core.http.route(enableAlertRoute);
|
||||
core.http.route(disableAlertRoute);
|
||||
core.http.route(updateApiKeyRoute);
|
||||
core.http.route(muteAllAlertRoute);
|
||||
core.http.route(unmuteAllAlertRoute);
|
||||
core.http.route(muteAlertInstanceRoute);
|
||||
core.http.route(unmuteAlertInstanceRoute);
|
||||
|
||||
return {
|
||||
registerType: alertTypeRegistry.register.bind(alertTypeRegistry),
|
||||
};
|
||||
}
|
||||
|
||||
public start(core: AlertingCoreStart, plugins: AlertingPluginsStart): PluginStartContract {
|
||||
const { adminClient, serverBasePath } = this;
|
||||
|
||||
const alertsClientFactory = new AlertsClientFactory({
|
||||
alertTypeRegistry: this.alertTypeRegistry!,
|
||||
logger: this.logger,
|
||||
taskManager: plugins.task_manager,
|
||||
securityPluginSetup: plugins.security,
|
||||
getSpaceId(request: Hapi.Request) {
|
||||
const spacesPlugin = plugins.spaces();
|
||||
return spacesPlugin ? spacesPlugin.getSpaceId(request) : undefined;
|
||||
},
|
||||
});
|
||||
|
||||
this.taskRunnerFactory.initialize({
|
||||
logger: this.logger,
|
||||
isSecurityEnabled: !!plugins.security,
|
||||
getServices(request: Hapi.Request): Services {
|
||||
return {
|
||||
callCluster: (...args) =>
|
||||
adminClient!.asScoped(KibanaRequest.from(request)).callAsCurrentUser(...args),
|
||||
savedObjectsClient: core.savedObjects.getScopedSavedObjectsClient(request),
|
||||
};
|
||||
},
|
||||
executeAction: plugins.actions.execute,
|
||||
encryptedSavedObjectsPlugin: plugins.encrypted_saved_objects,
|
||||
spaceIdToNamespace(spaceId?: string): string | undefined {
|
||||
const spacesPlugin = plugins.spaces();
|
||||
return spacesPlugin && spaceId ? spacesPlugin.spaceIdToNamespace(spaceId) : undefined;
|
||||
},
|
||||
getBasePath(spaceId?: string): string {
|
||||
const spacesPlugin = plugins.spaces();
|
||||
return spacesPlugin && spaceId ? spacesPlugin.getBasePath(spaceId) : serverBasePath!;
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
listTypes: this.alertTypeRegistry!.list.bind(this.alertTypeRegistry!),
|
||||
getAlertsClientWithRequest: (request: Hapi.Request) =>
|
||||
alertsClientFactory!.create(KibanaRequest.from(request), request),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -34,8 +34,14 @@ export function createMockServer(config: Record<string, any> = defaultConfig) {
|
|||
server.register({
|
||||
name: 'alerting',
|
||||
register(pluginServer: Hapi.Server) {
|
||||
pluginServer.expose('registerType', alertTypeRegistry.register);
|
||||
pluginServer.expose('listTypes', alertTypeRegistry.list);
|
||||
pluginServer.expose({
|
||||
setup: {
|
||||
registerType: alertTypeRegistry.register,
|
||||
},
|
||||
start: {
|
||||
listTypes: alertTypeRegistry.list,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import { createMockServer } from './_mock_server';
|
|||
import { createAlertRoute } from './create';
|
||||
|
||||
const { server, alertsClient } = createMockServer();
|
||||
createAlertRoute(server);
|
||||
server.route(createAlertRoute);
|
||||
|
||||
const mockedAlert = {
|
||||
alertTypeId: '1',
|
||||
|
|
|
@ -20,39 +20,37 @@ interface ScheduleRequest extends Hapi.Request {
|
|||
};
|
||||
}
|
||||
|
||||
export function createAlertRoute(server: Hapi.Server) {
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: '/api/alert',
|
||||
options: {
|
||||
tags: ['access:alerting-all'],
|
||||
validate: {
|
||||
options: {
|
||||
abortEarly: false,
|
||||
},
|
||||
payload: Joi.object()
|
||||
.keys({
|
||||
enabled: Joi.boolean().default(true),
|
||||
alertTypeId: Joi.string().required(),
|
||||
throttle: getDurationSchema().default(null),
|
||||
interval: getDurationSchema().required(),
|
||||
alertTypeParams: Joi.object().required(),
|
||||
actions: Joi.array()
|
||||
.items(
|
||||
Joi.object().keys({
|
||||
group: Joi.string().required(),
|
||||
id: Joi.string().required(),
|
||||
params: Joi.object().required(),
|
||||
})
|
||||
)
|
||||
.required(),
|
||||
})
|
||||
.required(),
|
||||
export const createAlertRoute = {
|
||||
method: 'POST',
|
||||
path: '/api/alert',
|
||||
options: {
|
||||
tags: ['access:alerting-all'],
|
||||
validate: {
|
||||
options: {
|
||||
abortEarly: false,
|
||||
},
|
||||
payload: Joi.object()
|
||||
.keys({
|
||||
enabled: Joi.boolean().default(true),
|
||||
alertTypeId: Joi.string().required(),
|
||||
throttle: getDurationSchema().default(null),
|
||||
interval: getDurationSchema().required(),
|
||||
alertTypeParams: Joi.object().required(),
|
||||
actions: Joi.array()
|
||||
.items(
|
||||
Joi.object().keys({
|
||||
group: Joi.string().required(),
|
||||
id: Joi.string().required(),
|
||||
params: Joi.object().required(),
|
||||
})
|
||||
)
|
||||
.required(),
|
||||
})
|
||||
.required(),
|
||||
},
|
||||
async handler(request: ScheduleRequest) {
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
return await alertsClient.create({ data: request.payload });
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
async handler(request: ScheduleRequest) {
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
return await alertsClient.create({ data: request.payload });
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ import { createMockServer } from './_mock_server';
|
|||
import { deleteAlertRoute } from './delete';
|
||||
|
||||
const { server, alertsClient } = createMockServer();
|
||||
deleteAlertRoute(server);
|
||||
server.route(deleteAlertRoute);
|
||||
|
||||
beforeEach(() => jest.resetAllMocks());
|
||||
|
||||
|
|
|
@ -13,25 +13,23 @@ interface DeleteRequest extends Hapi.Request {
|
|||
};
|
||||
}
|
||||
|
||||
export function deleteAlertRoute(server: Hapi.Server) {
|
||||
server.route({
|
||||
method: 'DELETE',
|
||||
path: '/api/alert/{id}',
|
||||
options: {
|
||||
tags: ['access:alerting-all'],
|
||||
validate: {
|
||||
params: Joi.object()
|
||||
.keys({
|
||||
id: Joi.string().required(),
|
||||
})
|
||||
.required(),
|
||||
},
|
||||
export const deleteAlertRoute = {
|
||||
method: 'DELETE',
|
||||
path: '/api/alert/{id}',
|
||||
config: {
|
||||
tags: ['access:alerting-all'],
|
||||
validate: {
|
||||
params: Joi.object()
|
||||
.keys({
|
||||
id: Joi.string().required(),
|
||||
})
|
||||
.required(),
|
||||
},
|
||||
async handler(request: DeleteRequest, h: Hapi.ResponseToolkit) {
|
||||
const { id } = request.params;
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
await alertsClient.delete({ id });
|
||||
return h.response().code(204);
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
async handler(request: DeleteRequest, h: Hapi.ResponseToolkit) {
|
||||
const { id } = request.params;
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
await alertsClient.delete({ id });
|
||||
return h.response().code(204);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ import { createMockServer } from './_mock_server';
|
|||
import { disableAlertRoute } from './disable';
|
||||
|
||||
const { server, alertsClient } = createMockServer();
|
||||
disableAlertRoute(server);
|
||||
server.route(disableAlertRoute);
|
||||
|
||||
test('disables an alert', async () => {
|
||||
const request = {
|
||||
|
|
|
@ -6,20 +6,18 @@
|
|||
|
||||
import Hapi from 'hapi';
|
||||
|
||||
export function disableAlertRoute(server: Hapi.Server) {
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: '/api/alert/{id}/_disable',
|
||||
options: {
|
||||
tags: ['access:alerting-all'],
|
||||
response: {
|
||||
emptyStatusCode: 204,
|
||||
},
|
||||
export const disableAlertRoute = {
|
||||
method: 'POST',
|
||||
path: '/api/alert/{id}/_disable',
|
||||
config: {
|
||||
tags: ['access:alerting-all'],
|
||||
response: {
|
||||
emptyStatusCode: 204,
|
||||
},
|
||||
async handler(request: Hapi.Request, h: Hapi.ResponseToolkit) {
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
await alertsClient.disable({ id: request.params.id });
|
||||
return h.response();
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
async handler(request: Hapi.Request, h: Hapi.ResponseToolkit) {
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
await alertsClient.disable({ id: request.params.id });
|
||||
return h.response();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ import { createMockServer } from './_mock_server';
|
|||
import { enableAlertRoute } from './enable';
|
||||
|
||||
const { server, alertsClient } = createMockServer();
|
||||
enableAlertRoute(server);
|
||||
server.route(enableAlertRoute);
|
||||
|
||||
test('enables an alert', async () => {
|
||||
const request = {
|
||||
|
|
|
@ -6,20 +6,18 @@
|
|||
|
||||
import Hapi from 'hapi';
|
||||
|
||||
export function enableAlertRoute(server: Hapi.Server) {
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: '/api/alert/{id}/_enable',
|
||||
options: {
|
||||
tags: ['access:alerting-all'],
|
||||
response: {
|
||||
emptyStatusCode: 204,
|
||||
},
|
||||
export const enableAlertRoute = {
|
||||
method: 'POST',
|
||||
path: '/api/alert/{id}/_enable',
|
||||
config: {
|
||||
tags: ['access:alerting-all'],
|
||||
response: {
|
||||
emptyStatusCode: 204,
|
||||
},
|
||||
async handler(request: Hapi.Request, h: Hapi.ResponseToolkit) {
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
await alertsClient.enable({ id: request.params.id });
|
||||
return h.response();
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
async handler(request: Hapi.Request, h: Hapi.ResponseToolkit) {
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
await alertsClient.enable({ id: request.params.id });
|
||||
return h.response();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
*/
|
||||
|
||||
import { createMockServer } from './_mock_server';
|
||||
import { findRoute } from './find';
|
||||
import { findAlertRoute } from './find';
|
||||
|
||||
const { server, alertsClient } = createMockServer();
|
||||
findRoute(server);
|
||||
server.route(findAlertRoute);
|
||||
|
||||
beforeEach(() => jest.resetAllMocks());
|
||||
|
||||
|
|
|
@ -25,59 +25,57 @@ interface FindRequest extends WithoutQueryAndParams<Hapi.Request> {
|
|||
};
|
||||
}
|
||||
|
||||
export function findRoute(server: Hapi.Server) {
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: '/api/alert/_find',
|
||||
options: {
|
||||
tags: ['access:alerting-read'],
|
||||
validate: {
|
||||
query: Joi.object()
|
||||
.keys({
|
||||
per_page: Joi.number()
|
||||
.min(0)
|
||||
.default(20),
|
||||
page: Joi.number()
|
||||
.min(1)
|
||||
.default(1),
|
||||
search: Joi.string()
|
||||
.allow('')
|
||||
.optional(),
|
||||
default_search_operator: Joi.string()
|
||||
.valid('OR', 'AND')
|
||||
.default('OR'),
|
||||
search_fields: Joi.array()
|
||||
.items(Joi.string())
|
||||
.single(),
|
||||
sort_field: Joi.string(),
|
||||
has_reference: Joi.object()
|
||||
.keys({
|
||||
type: Joi.string().required(),
|
||||
id: Joi.string().required(),
|
||||
})
|
||||
.optional(),
|
||||
fields: Joi.array()
|
||||
.items(Joi.string())
|
||||
.single(),
|
||||
})
|
||||
.default(),
|
||||
export const findAlertRoute = {
|
||||
method: 'GET',
|
||||
path: '/api/alert/_find',
|
||||
config: {
|
||||
tags: ['access:alerting-read'],
|
||||
validate: {
|
||||
query: Joi.object()
|
||||
.keys({
|
||||
per_page: Joi.number()
|
||||
.min(0)
|
||||
.default(20),
|
||||
page: Joi.number()
|
||||
.min(1)
|
||||
.default(1),
|
||||
search: Joi.string()
|
||||
.allow('')
|
||||
.optional(),
|
||||
default_search_operator: Joi.string()
|
||||
.valid('OR', 'AND')
|
||||
.default('OR'),
|
||||
search_fields: Joi.array()
|
||||
.items(Joi.string())
|
||||
.single(),
|
||||
sort_field: Joi.string(),
|
||||
has_reference: Joi.object()
|
||||
.keys({
|
||||
type: Joi.string().required(),
|
||||
id: Joi.string().required(),
|
||||
})
|
||||
.optional(),
|
||||
fields: Joi.array()
|
||||
.items(Joi.string())
|
||||
.single(),
|
||||
})
|
||||
.default(),
|
||||
},
|
||||
},
|
||||
async handler(request: FindRequest) {
|
||||
const { query } = request;
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
return await alertsClient.find({
|
||||
options: {
|
||||
perPage: query.per_page,
|
||||
page: query.page,
|
||||
search: query.search,
|
||||
defaultSearchOperator: query.default_search_operator,
|
||||
searchFields: query.search_fields,
|
||||
sortField: query.sort_field,
|
||||
hasReference: query.has_reference,
|
||||
fields: query.fields,
|
||||
},
|
||||
},
|
||||
async handler(request: FindRequest) {
|
||||
const { query } = request;
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
return await alertsClient.find({
|
||||
options: {
|
||||
perPage: query.per_page,
|
||||
page: query.page,
|
||||
search: query.search,
|
||||
defaultSearchOperator: query.default_search_operator,
|
||||
searchFields: query.search_fields,
|
||||
sortField: query.sort_field,
|
||||
hasReference: query.has_reference,
|
||||
fields: query.fields,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
*/
|
||||
|
||||
import { createMockServer } from './_mock_server';
|
||||
import { getRoute } from './get';
|
||||
import { getAlertRoute } from './get';
|
||||
|
||||
const { server, alertsClient } = createMockServer();
|
||||
getRoute(server);
|
||||
server.route(getAlertRoute);
|
||||
|
||||
const mockedAlert = {
|
||||
id: '1',
|
||||
|
|
|
@ -13,24 +13,22 @@ interface GetRequest extends Hapi.Request {
|
|||
};
|
||||
}
|
||||
|
||||
export function getRoute(server: Hapi.Server) {
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: `/api/alert/{id}`,
|
||||
options: {
|
||||
tags: ['access:alerting-read'],
|
||||
validate: {
|
||||
params: Joi.object()
|
||||
.keys({
|
||||
id: Joi.string().required(),
|
||||
})
|
||||
.required(),
|
||||
},
|
||||
export const getAlertRoute = {
|
||||
method: 'GET',
|
||||
path: `/api/alert/{id}`,
|
||||
options: {
|
||||
tags: ['access:alerting-read'],
|
||||
validate: {
|
||||
params: Joi.object()
|
||||
.keys({
|
||||
id: Joi.string().required(),
|
||||
})
|
||||
.required(),
|
||||
},
|
||||
async handler(request: GetRequest) {
|
||||
const { id } = request.params;
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
return await alertsClient.get({ id });
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
async handler(request: GetRequest) {
|
||||
const { id } = request.params;
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
return await alertsClient.get({ id });
|
||||
},
|
||||
};
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
export { createAlertRoute } from './create';
|
||||
export { deleteAlertRoute } from './delete';
|
||||
export { findRoute } from './find';
|
||||
export { getRoute } from './get';
|
||||
export { findAlertRoute } from './find';
|
||||
export { getAlertRoute } from './get';
|
||||
export { listAlertTypesRoute } from './list_alert_types';
|
||||
export { updateAlertRoute } from './update';
|
||||
export { enableAlertRoute } from './enable';
|
||||
|
|
|
@ -8,7 +8,7 @@ import { createMockServer } from './_mock_server';
|
|||
import { listAlertTypesRoute } from './list_alert_types';
|
||||
|
||||
const { server, alertTypeRegistry } = createMockServer();
|
||||
listAlertTypesRoute(server);
|
||||
server.route(listAlertTypesRoute);
|
||||
|
||||
beforeEach(() => jest.resetAllMocks());
|
||||
|
||||
|
|
|
@ -6,15 +6,13 @@
|
|||
|
||||
import Hapi from 'hapi';
|
||||
|
||||
export function listAlertTypesRoute(server: Hapi.Server) {
|
||||
server.route({
|
||||
method: 'GET',
|
||||
path: `/api/alert/types`,
|
||||
options: {
|
||||
tags: ['access:alerting-read'],
|
||||
},
|
||||
async handler(request: Hapi.Request) {
|
||||
return request.server.plugins.alerting!.listTypes();
|
||||
},
|
||||
});
|
||||
}
|
||||
export const listAlertTypesRoute = {
|
||||
method: 'GET',
|
||||
path: `/api/alert/types`,
|
||||
config: {
|
||||
tags: ['access:alerting-read'],
|
||||
},
|
||||
async handler(request: Hapi.Request) {
|
||||
return request.server.plugins.alerting!.start.listTypes();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ import { createMockServer } from './_mock_server';
|
|||
import { muteAllAlertRoute } from './mute_all';
|
||||
|
||||
const { server, alertsClient } = createMockServer();
|
||||
muteAllAlertRoute(server);
|
||||
server.route(muteAllAlertRoute);
|
||||
|
||||
test('mutes an alert', async () => {
|
||||
const request = {
|
||||
|
|
|
@ -12,20 +12,18 @@ interface MuteAllRequest extends Hapi.Request {
|
|||
};
|
||||
}
|
||||
|
||||
export function muteAllAlertRoute(server: Hapi.Server) {
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: '/api/alert/{id}/_mute_all',
|
||||
options: {
|
||||
tags: ['access:alerting-all'],
|
||||
response: {
|
||||
emptyStatusCode: 204,
|
||||
},
|
||||
export const muteAllAlertRoute = {
|
||||
method: 'POST',
|
||||
path: '/api/alert/{id}/_mute_all',
|
||||
config: {
|
||||
tags: ['access:alerting-all'],
|
||||
response: {
|
||||
emptyStatusCode: 204,
|
||||
},
|
||||
async handler(request: MuteAllRequest, h: Hapi.ResponseToolkit) {
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
await alertsClient.muteAll(request.params);
|
||||
return h.response();
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
async handler(request: MuteAllRequest, h: Hapi.ResponseToolkit) {
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
await alertsClient.muteAll(request.params);
|
||||
return h.response();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ import { createMockServer } from './_mock_server';
|
|||
import { muteAlertInstanceRoute } from './mute_instance';
|
||||
|
||||
const { server, alertsClient } = createMockServer();
|
||||
muteAlertInstanceRoute(server);
|
||||
server.route(muteAlertInstanceRoute);
|
||||
|
||||
test('mutes an alert instance', async () => {
|
||||
const request = {
|
||||
|
|
|
@ -13,20 +13,18 @@ interface MuteInstanceRequest extends Hapi.Request {
|
|||
};
|
||||
}
|
||||
|
||||
export function muteAlertInstanceRoute(server: Hapi.Server) {
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: '/api/alert/{alertId}/alert_instance/{alertInstanceId}/_mute',
|
||||
options: {
|
||||
tags: ['access:alerting-all'],
|
||||
response: {
|
||||
emptyStatusCode: 204,
|
||||
},
|
||||
export const muteAlertInstanceRoute = {
|
||||
method: 'POST',
|
||||
path: '/api/alert/{alertId}/alert_instance/{alertInstanceId}/_mute',
|
||||
config: {
|
||||
tags: ['access:alerting-all'],
|
||||
response: {
|
||||
emptyStatusCode: 204,
|
||||
},
|
||||
async handler(request: MuteInstanceRequest, h: Hapi.ResponseToolkit) {
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
await alertsClient.muteInstance(request.params);
|
||||
return h.response();
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
async handler(request: MuteInstanceRequest, h: Hapi.ResponseToolkit) {
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
await alertsClient.muteInstance(request.params);
|
||||
return h.response();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ import { createMockServer } from './_mock_server';
|
|||
import { unmuteAllAlertRoute } from './unmute_all';
|
||||
|
||||
const { server, alertsClient } = createMockServer();
|
||||
unmuteAllAlertRoute(server);
|
||||
server.route(unmuteAllAlertRoute);
|
||||
|
||||
test('unmutes an alert', async () => {
|
||||
const request = {
|
||||
|
|
|
@ -12,20 +12,18 @@ interface UnmuteAllRequest extends Hapi.Request {
|
|||
};
|
||||
}
|
||||
|
||||
export function unmuteAllAlertRoute(server: Hapi.Server) {
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: '/api/alert/{id}/_unmute_all',
|
||||
options: {
|
||||
tags: ['access:alerting-all'],
|
||||
response: {
|
||||
emptyStatusCode: 204,
|
||||
},
|
||||
export const unmuteAllAlertRoute = {
|
||||
method: 'POST',
|
||||
path: '/api/alert/{id}/_unmute_all',
|
||||
config: {
|
||||
tags: ['access:alerting-all'],
|
||||
response: {
|
||||
emptyStatusCode: 204,
|
||||
},
|
||||
async handler(request: UnmuteAllRequest, h: Hapi.ResponseToolkit) {
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
await alertsClient.unmuteAll(request.params);
|
||||
return h.response();
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
async handler(request: UnmuteAllRequest, h: Hapi.ResponseToolkit) {
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
await alertsClient.unmuteAll(request.params);
|
||||
return h.response();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ import { createMockServer } from './_mock_server';
|
|||
import { unmuteAlertInstanceRoute } from './unmute_instance';
|
||||
|
||||
const { server, alertsClient } = createMockServer();
|
||||
unmuteAlertInstanceRoute(server);
|
||||
server.route(unmuteAlertInstanceRoute);
|
||||
|
||||
test('unmutes an alert instance', async () => {
|
||||
const request = {
|
||||
|
|
|
@ -13,20 +13,18 @@ interface UnmuteInstanceRequest extends Hapi.Request {
|
|||
};
|
||||
}
|
||||
|
||||
export function unmuteAlertInstanceRoute(server: Hapi.Server) {
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: '/api/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute',
|
||||
options: {
|
||||
tags: ['access:alerting-all'],
|
||||
response: {
|
||||
emptyStatusCode: 204,
|
||||
},
|
||||
export const unmuteAlertInstanceRoute = {
|
||||
method: 'POST',
|
||||
path: '/api/alert/{alertId}/alert_instance/{alertInstanceId}/_unmute',
|
||||
config: {
|
||||
tags: ['access:alerting-all'],
|
||||
response: {
|
||||
emptyStatusCode: 204,
|
||||
},
|
||||
async handler(request: UnmuteInstanceRequest, h: Hapi.ResponseToolkit) {
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
await alertsClient.unmuteInstance(request.params);
|
||||
return h.response();
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
async handler(request: UnmuteInstanceRequest, h: Hapi.ResponseToolkit) {
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
await alertsClient.unmuteInstance(request.params);
|
||||
return h.response();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ import { createMockServer } from './_mock_server';
|
|||
import { updateAlertRoute } from './update';
|
||||
|
||||
const { server, alertsClient } = createMockServer();
|
||||
updateAlertRoute(server);
|
||||
server.route(updateAlertRoute);
|
||||
|
||||
beforeEach(() => jest.resetAllMocks());
|
||||
|
||||
|
|
|
@ -22,40 +22,38 @@ interface UpdateRequest extends Hapi.Request {
|
|||
};
|
||||
}
|
||||
|
||||
export function updateAlertRoute(server: Hapi.Server) {
|
||||
server.route({
|
||||
method: 'PUT',
|
||||
path: '/api/alert/{id}',
|
||||
options: {
|
||||
tags: ['access:alerting-all'],
|
||||
validate: {
|
||||
options: {
|
||||
abortEarly: false,
|
||||
},
|
||||
payload: Joi.object()
|
||||
.keys({
|
||||
throttle: getDurationSchema()
|
||||
.required()
|
||||
.allow(null),
|
||||
interval: getDurationSchema().required(),
|
||||
alertTypeParams: Joi.object().required(),
|
||||
actions: Joi.array()
|
||||
.items(
|
||||
Joi.object().keys({
|
||||
group: Joi.string().required(),
|
||||
id: Joi.string().required(),
|
||||
params: Joi.object().required(),
|
||||
})
|
||||
)
|
||||
.required(),
|
||||
})
|
||||
.required(),
|
||||
export const updateAlertRoute = {
|
||||
method: 'PUT',
|
||||
path: '/api/alert/{id}',
|
||||
options: {
|
||||
tags: ['access:alerting-all'],
|
||||
validate: {
|
||||
options: {
|
||||
abortEarly: false,
|
||||
},
|
||||
payload: Joi.object()
|
||||
.keys({
|
||||
throttle: getDurationSchema()
|
||||
.required()
|
||||
.allow(null),
|
||||
interval: getDurationSchema().required(),
|
||||
alertTypeParams: Joi.object().required(),
|
||||
actions: Joi.array()
|
||||
.items(
|
||||
Joi.object().keys({
|
||||
group: Joi.string().required(),
|
||||
id: Joi.string().required(),
|
||||
params: Joi.object().required(),
|
||||
})
|
||||
)
|
||||
.required(),
|
||||
})
|
||||
.required(),
|
||||
},
|
||||
async handler(request: UpdateRequest) {
|
||||
const { id } = request.params;
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
return await alertsClient.update({ id, data: request.payload });
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
async handler(request: UpdateRequest) {
|
||||
const { id } = request.params;
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
return await alertsClient.update({ id, data: request.payload });
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ import { createMockServer } from './_mock_server';
|
|||
import { updateApiKeyRoute } from './update_api_key';
|
||||
|
||||
const { server, alertsClient } = createMockServer();
|
||||
updateApiKeyRoute(server);
|
||||
server.route(updateApiKeyRoute);
|
||||
|
||||
test('updates api key for an alert', async () => {
|
||||
const request = {
|
||||
|
|
|
@ -6,20 +6,18 @@
|
|||
|
||||
import Hapi from 'hapi';
|
||||
|
||||
export function updateApiKeyRoute(server: Hapi.Server) {
|
||||
server.route({
|
||||
method: 'POST',
|
||||
path: '/api/alert/{id}/_update_api_key',
|
||||
options: {
|
||||
tags: ['access:alerting-all'],
|
||||
response: {
|
||||
emptyStatusCode: 204,
|
||||
},
|
||||
export const updateApiKeyRoute = {
|
||||
method: 'POST',
|
||||
path: '/api/alert/{id}/_update_api_key',
|
||||
config: {
|
||||
tags: ['access:alerting-all'],
|
||||
response: {
|
||||
emptyStatusCode: 204,
|
||||
},
|
||||
async handler(request: Hapi.Request, h: Hapi.ResponseToolkit) {
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
await alertsClient.updateApiKey({ id: request.params.id });
|
||||
return h.response();
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
async handler(request: Hapi.Request, h: Hapi.ResponseToolkit) {
|
||||
const alertsClient = request.getAlertsClient!();
|
||||
await alertsClient.updateApiKey({ id: request.params.id });
|
||||
return h.response();
|
||||
},
|
||||
};
|
||||
|
|
143
x-pack/legacy/plugins/alerting/server/shim.ts
Normal file
143
x-pack/legacy/plugins/alerting/server/shim.ts
Normal file
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* 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 Hapi from 'hapi';
|
||||
import { Legacy } from 'kibana';
|
||||
import { SpacesPlugin as SpacesPluginStartContract } from '../../spaces';
|
||||
import { ActionsPlugin } from '../../actions';
|
||||
import { TaskManager } from '../../task_manager';
|
||||
import { XPackMainPlugin } from '../../xpack_main/xpack_main';
|
||||
import KbnServer from '../../../../../src/legacy/server/kbn_server';
|
||||
import { EncryptedSavedObjectsPlugin } from '../../encrypted_saved_objects';
|
||||
import { PluginSetupContract as SecurityPlugin } from '../../../../plugins/security/server';
|
||||
import {
|
||||
CoreSetup,
|
||||
LoggerFactory,
|
||||
SavedObjectsLegacyService,
|
||||
} from '../../../../../src/core/server';
|
||||
|
||||
// Extend PluginProperties to indicate which plugins are guaranteed to exist
|
||||
// due to being marked as dependencies
|
||||
interface Plugins extends Hapi.PluginProperties {
|
||||
actions: ActionsPlugin;
|
||||
task_manager: TaskManager;
|
||||
encrypted_saved_objects: EncryptedSavedObjectsPlugin;
|
||||
}
|
||||
|
||||
export interface Server extends Legacy.Server {
|
||||
plugins: Plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shim what we're thinking setup and start contracts will look like
|
||||
*/
|
||||
export type ActionsPluginSetupContract = Pick<ActionsPlugin, 'registerType'>;
|
||||
export type ActionsPluginStartContract = Pick<ActionsPlugin, 'listTypes' | 'execute'>;
|
||||
export type TaskManagerStartContract = Pick<TaskManager, 'schedule' | 'fetch' | 'remove'>;
|
||||
export type SecurityPluginSetupContract = Pick<SecurityPlugin, 'config' | 'registerLegacyAPI'>;
|
||||
export type SecurityPluginStartContract = Pick<SecurityPlugin, 'authc'>;
|
||||
export type EncryptedSavedObjectsSetupContract = Pick<EncryptedSavedObjectsPlugin, 'registerType'>;
|
||||
export type XPackMainPluginSetupContract = Pick<XPackMainPlugin, 'registerFeature'>;
|
||||
export type TaskManagerSetupContract = Pick<
|
||||
TaskManager,
|
||||
'addMiddleware' | 'registerTaskDefinitions'
|
||||
>;
|
||||
export type EncryptedSavedObjectsStartContract = Pick<
|
||||
EncryptedSavedObjectsPlugin,
|
||||
'isEncryptionError' | 'getDecryptedAsInternalUser'
|
||||
>;
|
||||
|
||||
/**
|
||||
* New platform interfaces
|
||||
*/
|
||||
export interface AlertingPluginInitializerContext {
|
||||
logger: LoggerFactory;
|
||||
}
|
||||
export interface AlertingCoreSetup {
|
||||
elasticsearch: CoreSetup['elasticsearch'];
|
||||
http: {
|
||||
route: (route: Hapi.ServerRoute) => void;
|
||||
basePath: {
|
||||
serverBasePath: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
export interface AlertingCoreStart {
|
||||
savedObjects: SavedObjectsLegacyService;
|
||||
}
|
||||
export interface AlertingPluginsSetup {
|
||||
security?: SecurityPluginSetupContract;
|
||||
task_manager: TaskManagerSetupContract;
|
||||
actions: ActionsPluginSetupContract;
|
||||
xpack_main: XPackMainPluginSetupContract;
|
||||
encrypted_saved_objects: EncryptedSavedObjectsSetupContract;
|
||||
}
|
||||
export interface AlertingPluginsStart {
|
||||
actions: ActionsPluginStartContract;
|
||||
security?: SecurityPluginStartContract;
|
||||
spaces: () => SpacesPluginStartContract | undefined;
|
||||
encrypted_saved_objects: EncryptedSavedObjectsStartContract;
|
||||
task_manager: TaskManagerStartContract;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shim
|
||||
*
|
||||
* @param server Hapi server instance
|
||||
*/
|
||||
export function shim(
|
||||
server: Server
|
||||
): {
|
||||
initializerContext: AlertingPluginInitializerContext;
|
||||
coreSetup: AlertingCoreSetup;
|
||||
coreStart: AlertingCoreStart;
|
||||
pluginsSetup: AlertingPluginsSetup;
|
||||
pluginsStart: AlertingPluginsStart;
|
||||
} {
|
||||
const newPlatform = ((server as unknown) as KbnServer).newPlatform;
|
||||
|
||||
const initializerContext: AlertingPluginInitializerContext = {
|
||||
logger: newPlatform.coreContext.logger,
|
||||
};
|
||||
|
||||
const coreSetup: AlertingCoreSetup = {
|
||||
elasticsearch: newPlatform.setup.core.elasticsearch,
|
||||
http: {
|
||||
route: server.route.bind(server),
|
||||
basePath: newPlatform.setup.core.http.basePath,
|
||||
},
|
||||
};
|
||||
|
||||
const coreStart: AlertingCoreStart = {
|
||||
savedObjects: server.savedObjects,
|
||||
};
|
||||
|
||||
const pluginsSetup: AlertingPluginsSetup = {
|
||||
security: newPlatform.setup.plugins.security as SecurityPluginSetupContract | undefined,
|
||||
task_manager: server.plugins.task_manager,
|
||||
actions: server.plugins.actions,
|
||||
xpack_main: server.plugins.xpack_main,
|
||||
encrypted_saved_objects: server.plugins.encrypted_saved_objects,
|
||||
};
|
||||
|
||||
const pluginsStart: AlertingPluginsStart = {
|
||||
security: newPlatform.setup.plugins.security as SecurityPluginStartContract | undefined,
|
||||
actions: server.plugins.actions,
|
||||
// TODO: Currently a function because it's an optional dependency that
|
||||
// initializes after this function is called
|
||||
spaces: () => server.plugins.spaces,
|
||||
encrypted_saved_objects: server.plugins.encrypted_saved_objects,
|
||||
task_manager: server.plugins.task_manager,
|
||||
};
|
||||
|
||||
return {
|
||||
initializerContext,
|
||||
coreSetup,
|
||||
coreStart,
|
||||
pluginsSetup,
|
||||
pluginsStart,
|
||||
};
|
||||
}
|
|
@ -4,9 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectAttributes, SavedObjectsClientContract } from 'src/core/server';
|
||||
import { AlertInstance } from './lib';
|
||||
import { AlertTypeRegistry } from './alert_type_registry';
|
||||
import { PluginSetupContract, PluginStartContract } from './plugin';
|
||||
import { SavedObjectAttributes, SavedObjectsClientContract } from '../../../../../src/core/server';
|
||||
|
||||
export type State = Record<string, any>;
|
||||
export type Context = Record<string, any>;
|
||||
|
@ -15,14 +16,7 @@ export type GetServicesFunction = (request: any) => Services;
|
|||
export type GetBasePathFunction = (spaceId?: string) => string;
|
||||
export type SpaceIdToNamespaceFunction = (spaceId?: string) => string | undefined;
|
||||
|
||||
export type Log = (
|
||||
tags: string | string[],
|
||||
data?: string | object | (() => any),
|
||||
timestamp?: number
|
||||
) => void;
|
||||
|
||||
export interface Services {
|
||||
log: Log;
|
||||
callCluster(path: string, opts: any): Promise<any>;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}
|
||||
|
@ -96,8 +90,8 @@ export interface RawAlert extends SavedObjectAttributes {
|
|||
}
|
||||
|
||||
export interface AlertingPlugin {
|
||||
registerType: AlertTypeRegistry['register'];
|
||||
listTypes: AlertTypeRegistry['list'];
|
||||
setup: PluginSetupContract;
|
||||
start: PluginStartContract;
|
||||
}
|
||||
|
||||
export type AlertTypeRegistry = PublicMethodsOf<AlertTypeRegistry>;
|
||||
|
|
|
@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { resolve } from 'path';
|
||||
import { Server } from 'hapi';
|
||||
|
||||
import KbnServer from '../../../../src/legacy/server/kbn_server';
|
||||
import { initServerWithKibana } from './server/kibana.index';
|
||||
import { savedObjectMappings } from './server/saved_objects';
|
||||
|
||||
|
@ -122,8 +123,11 @@ export function siem(kibana: any) {
|
|||
mappings: savedObjectMappings,
|
||||
},
|
||||
init(server: Server) {
|
||||
const newPlatform = ((server as unknown) as KbnServer).newPlatform;
|
||||
if (server.plugins.alerting != null) {
|
||||
server.plugins.alerting.registerType(signalsAlertType);
|
||||
server.plugins.alerting.setup.registerType(
|
||||
signalsAlertType({ logger: newPlatform.coreContext.logger.get('plugins', APP_ID) })
|
||||
);
|
||||
}
|
||||
server.injectUiAppVars('siem', async () => server.getInjectedUiAppVars('kibana'));
|
||||
initServerWithKibana(server);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { SIGNALS_ID } from '../../../../common/constants';
|
||||
import { Logger } from '../../../../../../../../src/core/server';
|
||||
import { AlertType, AlertExecutorOptions } from '../../../../../alerting';
|
||||
|
||||
// TODO: Remove this for the build_events_query call eventually
|
||||
|
@ -15,85 +16,87 @@ import { buildEventsReIndex } from './build_events_reindex';
|
|||
// once scrolling and other things are done with it.
|
||||
// import { buildEventsQuery } from './build_events_query';
|
||||
|
||||
export const signalsAlertType: AlertType = {
|
||||
id: SIGNALS_ID,
|
||||
name: 'SIEM Signals',
|
||||
actionGroups: ['default'],
|
||||
validate: {
|
||||
params: schema.object({
|
||||
description: schema.string(),
|
||||
from: schema.string(),
|
||||
filter: schema.maybe(schema.object({}, { allowUnknowns: true })),
|
||||
id: schema.number(),
|
||||
index: schema.arrayOf(schema.string()),
|
||||
kql: schema.maybe(schema.string({ defaultValue: undefined })),
|
||||
maxSignals: schema.number({ defaultValue: 100 }),
|
||||
name: schema.string(),
|
||||
severity: schema.number(),
|
||||
to: schema.string(),
|
||||
type: schema.string(),
|
||||
references: schema.arrayOf(schema.string(), { defaultValue: [] }),
|
||||
}),
|
||||
},
|
||||
// TODO: Type the params as it is all filled with any
|
||||
async executor({ services, params, state }: AlertExecutorOptions) {
|
||||
const instance = services.alertInstanceFactory('siem-signals');
|
||||
export const signalsAlertType = ({ logger }: { logger: Logger }): AlertType => {
|
||||
return {
|
||||
id: SIGNALS_ID,
|
||||
name: 'SIEM Signals',
|
||||
actionGroups: ['default'],
|
||||
validate: {
|
||||
params: schema.object({
|
||||
description: schema.string(),
|
||||
from: schema.string(),
|
||||
filter: schema.maybe(schema.object({}, { allowUnknowns: true })),
|
||||
id: schema.number(),
|
||||
index: schema.arrayOf(schema.string()),
|
||||
kql: schema.maybe(schema.string({ defaultValue: undefined })),
|
||||
maxSignals: schema.number({ defaultValue: 100 }),
|
||||
name: schema.string(),
|
||||
severity: schema.number(),
|
||||
to: schema.string(),
|
||||
type: schema.string(),
|
||||
references: schema.arrayOf(schema.string(), { defaultValue: [] }),
|
||||
}),
|
||||
},
|
||||
// TODO: Type the params as it is all filled with any
|
||||
async executor({ services, params, state }: AlertExecutorOptions) {
|
||||
const instance = services.alertInstanceFactory('siem-signals');
|
||||
|
||||
// TODO: Comment this in eventually and use the buildEventsQuery()
|
||||
// for scrolling and other fun stuff instead of using the buildEventsReIndex()
|
||||
// const query = buildEventsQuery();
|
||||
// TODO: Comment this in eventually and use the buildEventsQuery()
|
||||
// for scrolling and other fun stuff instead of using the buildEventsReIndex()
|
||||
// const query = buildEventsQuery();
|
||||
|
||||
const {
|
||||
description,
|
||||
filter,
|
||||
from,
|
||||
id,
|
||||
index,
|
||||
kql,
|
||||
maxSignals,
|
||||
name,
|
||||
references,
|
||||
severity,
|
||||
to,
|
||||
type,
|
||||
} = params;
|
||||
const reIndex = buildEventsReIndex({
|
||||
index,
|
||||
from,
|
||||
kql,
|
||||
to,
|
||||
// TODO: Change this out once we have solved
|
||||
// https://github.com/elastic/kibana/issues/47002
|
||||
signalsIndex: process.env.SIGNALS_INDEX || '.siem-signals-10-01-2019',
|
||||
severity,
|
||||
description,
|
||||
name,
|
||||
timeDetected: Date.now(),
|
||||
filter,
|
||||
maxDocs: maxSignals,
|
||||
ruleRevision: 1,
|
||||
id,
|
||||
type,
|
||||
references,
|
||||
});
|
||||
const {
|
||||
description,
|
||||
filter,
|
||||
from,
|
||||
id,
|
||||
index,
|
||||
kql,
|
||||
maxSignals,
|
||||
name,
|
||||
references,
|
||||
severity,
|
||||
to,
|
||||
type,
|
||||
} = params;
|
||||
const reIndex = buildEventsReIndex({
|
||||
index,
|
||||
from,
|
||||
kql,
|
||||
to,
|
||||
// TODO: Change this out once we have solved
|
||||
// https://github.com/elastic/kibana/issues/47002
|
||||
signalsIndex: process.env.SIGNALS_INDEX || '.siem-signals-10-01-2019',
|
||||
severity,
|
||||
description,
|
||||
name,
|
||||
timeDetected: Date.now(),
|
||||
filter,
|
||||
maxDocs: maxSignals,
|
||||
ruleRevision: 1,
|
||||
id,
|
||||
type,
|
||||
references,
|
||||
});
|
||||
|
||||
try {
|
||||
services.log(['info', 'SIEM'], 'Starting SIEM signal job');
|
||||
try {
|
||||
logger.info('Starting SIEM signal job');
|
||||
|
||||
// TODO: Comment this in eventually and use this for manual insertion of the
|
||||
// signals instead of the ReIndex() api
|
||||
// const result = await services.callCluster('search', query);
|
||||
const result = await services.callCluster('reindex', reIndex);
|
||||
// TODO: Comment this in eventually and use this for manual insertion of the
|
||||
// signals instead of the ReIndex() api
|
||||
// const result = await services.callCluster('search', query);
|
||||
const result = await services.callCluster('reindex', reIndex);
|
||||
|
||||
// TODO: Error handling here and writing of any errors that come back from ES by
|
||||
services.log(['info', 'SIEM'], `Result of reindex: ${JSON.stringify(result, null, 2)}`);
|
||||
} catch (err) {
|
||||
// TODO: Error handling and writing of errors into a signal that has error
|
||||
// handling/conditions
|
||||
services.log(['error', 'SIEM'], `You encountered an error of: ${err.message}`);
|
||||
}
|
||||
// TODO: Error handling here and writing of any errors that come back from ES by
|
||||
logger.info(`Result of reindex: ${JSON.stringify(result, null, 2)}`);
|
||||
} catch (err) {
|
||||
// TODO: Error handling and writing of errors into a signal that has error
|
||||
// handling/conditions
|
||||
logger.error(`You encountered an error of: ${err.message}`);
|
||||
}
|
||||
|
||||
// Schedule the default action which is nothing if it's a plain signal.
|
||||
instance.scheduleActions('default');
|
||||
},
|
||||
// Schedule the default action which is nothing if it's a plain signal.
|
||||
instance.scheduleActions('default');
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -12,7 +12,7 @@ import mappings from './mappings.json';
|
|||
import { migrations } from './migrations';
|
||||
|
||||
export { PluginSetupContract as TaskManager };
|
||||
export { TaskInstance, ConcreteTaskInstance, TaskRunCreatorFunction } from './task';
|
||||
export { TaskInstance, ConcreteTaskInstance, TaskRunCreatorFunction, RunContext } from './task';
|
||||
|
||||
export function taskManager(kibana: any) {
|
||||
return new kibana.Plugin({
|
||||
|
|
|
@ -314,12 +314,12 @@ export default function(kibana: any) {
|
|||
actionGroups: [],
|
||||
async executor({ services, params, state }: AlertExecutorOptions) {},
|
||||
};
|
||||
server.plugins.alerting.registerType(alwaysFiringAlertType);
|
||||
server.plugins.alerting.registerType(neverFiringAlertType);
|
||||
server.plugins.alerting.registerType(failingAlertType);
|
||||
server.plugins.alerting.registerType(validationAlertType);
|
||||
server.plugins.alerting.registerType(authorizationAlertType);
|
||||
server.plugins.alerting.registerType(noopAlertType);
|
||||
server.plugins.alerting.setup.registerType(alwaysFiringAlertType);
|
||||
server.plugins.alerting.setup.registerType(neverFiringAlertType);
|
||||
server.plugins.alerting.setup.registerType(failingAlertType);
|
||||
server.plugins.alerting.setup.registerType(validationAlertType);
|
||||
server.plugins.alerting.setup.registerType(authorizationAlertType);
|
||||
server.plugins.alerting.setup.registerType(noopAlertType);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue