diff --git a/x-pack/plugins/actions/server/action_type_registry.test.ts b/x-pack/plugins/actions/server/action_type_registry.test.ts index ce5c1fe8500fb..b25e33400df5d 100644 --- a/x-pack/plugins/actions/server/action_type_registry.test.ts +++ b/x-pack/plugins/actions/server/action_type_registry.test.ts @@ -41,7 +41,7 @@ beforeEach(() => { }; }); -const executor: ExecutorType = async (options) => { +const executor: ExecutorType<{}, {}, {}, void> = async (options) => { return { status: 'ok', actionId: options.actionId }; }; @@ -203,7 +203,9 @@ describe('isActionTypeEnabled', () => { id: 'foo', name: 'Foo', minimumLicenseRequired: 'basic', - executor: async () => {}, + executor: async (options) => { + return { status: 'ok', actionId: options.actionId }; + }, }; beforeEach(() => { @@ -258,7 +260,9 @@ describe('ensureActionTypeEnabled', () => { id: 'foo', name: 'Foo', minimumLicenseRequired: 'basic', - executor: async () => {}, + executor: async (options) => { + return { status: 'ok', actionId: options.actionId }; + }, }; beforeEach(() => { diff --git a/x-pack/plugins/actions/server/action_type_registry.ts b/x-pack/plugins/actions/server/action_type_registry.ts index 1f7409fedd2c2..4015381ff9502 100644 --- a/x-pack/plugins/actions/server/action_type_registry.ts +++ b/x-pack/plugins/actions/server/action_type_registry.ts @@ -8,9 +8,15 @@ import Boom from 'boom'; import { i18n } from '@kbn/i18n'; import { RunContext, TaskManagerSetupContract } from '../../task_manager/server'; import { ExecutorError, TaskRunnerFactory, ILicenseState } from './lib'; -import { ActionType, PreConfiguredAction } from './types'; import { ActionType as CommonActionType } from '../common'; import { ActionsConfigurationUtilities } from './actions_config'; +import { + ActionType, + PreConfiguredAction, + ActionTypeConfig, + ActionTypeSecrets, + ActionTypeParams, +} from './types'; export interface ActionTypeRegistryOpts { taskManager: TaskManagerSetupContract; @@ -77,7 +83,12 @@ export class ActionTypeRegistry { /** * Registers an action type to the action type registry */ - public register(actionType: ActionType) { + public register< + Config extends ActionTypeConfig = ActionTypeConfig, + Secrets extends ActionTypeSecrets = ActionTypeSecrets, + Params extends ActionTypeParams = ActionTypeParams, + ExecutorResultData = void + >(actionType: ActionType) { if (this.has(actionType.id)) { throw new Error( i18n.translate( @@ -91,7 +102,7 @@ export class ActionTypeRegistry { ) ); } - this.actionTypes.set(actionType.id, { ...actionType }); + this.actionTypes.set(actionType.id, { ...actionType } as ActionType); this.taskManager.registerTaskDefinitions({ [`actions:${actionType.id}`]: { title: actionType.name, @@ -112,7 +123,12 @@ export class ActionTypeRegistry { /** * Returns an action type, throws if not registered */ - public get(id: string): ActionType { + public get< + Config extends ActionTypeConfig = ActionTypeConfig, + Secrets extends ActionTypeSecrets = ActionTypeSecrets, + Params extends ActionTypeParams = ActionTypeParams, + ExecutorResultData = void + >(id: string): ActionType { if (!this.has(id)) { throw Boom.badRequest( i18n.translate('xpack.actions.actionTypeRegistry.get.missingActionTypeErrorMessage', { @@ -123,7 +139,7 @@ export class ActionTypeRegistry { }) ); } - return this.actionTypes.get(id)!; + return this.actionTypes.get(id)! as ActionType; } /** diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts index 90b989ac3b52e..16a5a59882dd6 100644 --- a/x-pack/plugins/actions/server/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client.test.ts @@ -39,7 +39,7 @@ let actionsClient: ActionsClient; let mockedLicenseState: jest.Mocked; let actionTypeRegistry: ActionTypeRegistry; let actionTypeRegistryParams: ActionTypeRegistryOpts; -const executor: ExecutorType = async (options) => { +const executor: ExecutorType<{}, {}, {}, void> = async (options) => { return { status: 'ok', actionId: options.actionId }; }; diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index 6744a8d111623..d46ad3e2e2423 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -298,7 +298,7 @@ export class ActionsClient { public async execute({ actionId, params, - }: Omit): Promise { + }: Omit): Promise> { await this.authorization.ensureAuthorized('execute'); return this.actionExecutor.execute({ actionId, params, request: this.request }); } diff --git a/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts b/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts index 676a4776d0055..82dedb09c429e 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/case/utils.ts @@ -10,6 +10,10 @@ import { schema } from '@kbn/config-schema'; import { ActionTypeExecutorOptions, ActionTypeExecutorResult, ActionType } from '../../types'; import { ExecutorParamsSchema } from './schema'; +import { + ExternalIncidentServiceConfiguration, + ExternalIncidentServiceSecretConfiguration, +} from './types'; import { CreateExternalServiceArgs, @@ -23,6 +27,7 @@ import { TransformFieldsArgs, Comment, ExecutorSubActionPushParams, + PushToServiceResponse, } from './types'; import { transformers } from './transformers'; @@ -63,14 +68,17 @@ export const createConnectorExecutor = ({ api, createExternalService, }: CreateExternalServiceBasicArgs) => async ( - execOptions: ActionTypeExecutorOptions -): Promise => { + execOptions: ActionTypeExecutorOptions< + ExternalIncidentServiceConfiguration, + ExternalIncidentServiceSecretConfiguration, + ExecutorParams + > +): Promise> => { const { actionId, config, params, secrets } = execOptions; - const { subAction, subActionParams } = params as ExecutorParams; + const { subAction, subActionParams } = params; let data = {}; - const res: Pick & - Pick = { + const res: ActionTypeExecutorResult = { status: 'ok', actionId, }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts index 1a24622e1cabb..195f6db538ae5 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts @@ -10,7 +10,6 @@ jest.mock('./lib/send_email', () => ({ import { Logger } from '../../../../../src/core/server'; -import { ActionType, ActionTypeExecutorOptions } from '../types'; import { actionsConfigMock } from '../actions_config.mock'; import { validateConfig, validateSecrets, validateParams } from '../lib'; import { createActionTypeRegistry } from './index.test'; @@ -21,6 +20,8 @@ import { ActionTypeConfigType, ActionTypeSecretsType, getActionType, + EmailActionType, + EmailActionTypeExecutorOptions, } from './email'; const sendEmailMock = sendEmail as jest.Mock; @@ -29,13 +30,17 @@ const ACTION_TYPE_ID = '.email'; const services = actionsMock.createServices(); -let actionType: ActionType; +let actionType: EmailActionType; let mockedLogger: jest.Mocked; beforeEach(() => { jest.resetAllMocks(); const { actionTypeRegistry } = createActionTypeRegistry(); - actionType = actionTypeRegistry.get(ACTION_TYPE_ID); + actionType = actionTypeRegistry.get< + ActionTypeConfigType, + ActionTypeSecretsType, + ActionParamsType + >(ACTION_TYPE_ID); }); describe('actionTypeRegistry.get() works', () => { @@ -242,7 +247,7 @@ describe('execute()', () => { }; const actionId = 'some-id'; - const executorOptions: ActionTypeExecutorOptions = { + const executorOptions: EmailActionTypeExecutorOptions = { actionId, config, params, @@ -306,7 +311,7 @@ describe('execute()', () => { }; const actionId = 'some-id'; - const executorOptions: ActionTypeExecutorOptions = { + const executorOptions: EmailActionTypeExecutorOptions = { actionId, config, params, @@ -363,7 +368,7 @@ describe('execute()', () => { }; const actionId = 'some-id'; - const executorOptions: ActionTypeExecutorOptions = { + const executorOptions: EmailActionTypeExecutorOptions = { actionId, config, params, diff --git a/x-pack/plugins/actions/server/builtin_action_types/email.ts b/x-pack/plugins/actions/server/builtin_action_types/email.ts index 7ddb123a4d780..a51a0432a01e0 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/email.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/email.ts @@ -15,6 +15,18 @@ import { Logger } from '../../../../../src/core/server'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; import { ActionsConfigurationUtilities } from '../actions_config'; +export type EmailActionType = ActionType< + ActionTypeConfigType, + ActionTypeSecretsType, + ActionParamsType, + unknown +>; +export type EmailActionTypeExecutorOptions = ActionTypeExecutorOptions< + ActionTypeConfigType, + ActionTypeSecretsType, + ActionParamsType +>; + // config definition export type ActionTypeConfigType = TypeOf; @@ -30,10 +42,9 @@ const ConfigSchema = schema.object(ConfigSchemaProps); function validateConfig( configurationUtilities: ActionsConfigurationUtilities, - configObject: unknown + configObject: ActionTypeConfigType ): string | void { - // avoids circular reference ... - const config = configObject as ActionTypeConfigType; + const config = configObject; // Make sure service is set, or if not, both host/port must be set. // If service is set, host/port are ignored, when the email is sent. @@ -113,7 +124,7 @@ interface GetActionTypeParams { } // action type definition -export function getActionType(params: GetActionTypeParams): ActionType { +export function getActionType(params: GetActionTypeParams): EmailActionType { const { logger, configurationUtilities } = params; return { id: '.email', @@ -136,12 +147,12 @@ export function getActionType(params: GetActionTypeParams): ActionType { async function executor( { logger }: { logger: Logger }, - execOptions: ActionTypeExecutorOptions -): Promise { + execOptions: EmailActionTypeExecutorOptions +): Promise> { const actionId = execOptions.actionId; - const config = execOptions.config as ActionTypeConfigType; - const secrets = execOptions.secrets as ActionTypeSecretsType; - const params = execOptions.params as ActionParamsType; + const config = execOptions.config; + const secrets = execOptions.secrets; + const params = execOptions.params; const transport: Transport = {}; diff --git a/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts b/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts index be60f4c2f28af..7a0e24521a1c6 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/es_index.test.ts @@ -8,21 +8,25 @@ jest.mock('./lib/send_email', () => ({ sendEmail: jest.fn(), })); -import { ActionType, ActionTypeExecutorOptions } from '../types'; import { validateConfig, validateParams } from '../lib'; import { createActionTypeRegistry } from './index.test'; -import { ActionParamsType, ActionTypeConfigType } from './es_index'; import { actionsMock } from '../mocks'; +import { + ActionParamsType, + ActionTypeConfigType, + ESIndexActionType, + ESIndexActionTypeExecutorOptions, +} from './es_index'; const ACTION_TYPE_ID = '.index'; const services = actionsMock.createServices(); -let actionType: ActionType; +let actionType: ESIndexActionType; beforeAll(() => { const { actionTypeRegistry } = createActionTypeRegistry(); - actionType = actionTypeRegistry.get(ACTION_TYPE_ID); + actionType = actionTypeRegistry.get(ACTION_TYPE_ID); }); beforeEach(() => { @@ -144,12 +148,12 @@ describe('params validation', () => { describe('execute()', () => { test('ensure parameters are as expected', async () => { const secrets = {}; - let config: Partial; + let config: ActionTypeConfigType; let params: ActionParamsType; - let executorOptions: ActionTypeExecutorOptions; + let executorOptions: ESIndexActionTypeExecutorOptions; // minimal params - config = { index: 'index-value', refresh: false }; + config = { index: 'index-value', refresh: false, executionTimeField: null }; params = { documents: [{ jim: 'bob' }], }; @@ -215,7 +219,7 @@ describe('execute()', () => { `); // minimal params - config = { index: 'index-value', executionTimeField: undefined, refresh: false }; + config = { index: 'index-value', executionTimeField: null, refresh: false }; params = { documents: [{ jim: 'bob' }], }; @@ -245,7 +249,7 @@ describe('execute()', () => { `); // multiple documents - config = { index: 'index-value', executionTimeField: undefined, refresh: false }; + config = { index: 'index-value', executionTimeField: null, refresh: false }; params = { documents: [{ a: 1 }, { b: 2 }], }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts index 899684367d52d..53bf75651b1e5 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts @@ -11,6 +11,13 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { Logger } from '../../../../../src/core/server'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; +export type ESIndexActionType = ActionType; +export type ESIndexActionTypeExecutorOptions = ActionTypeExecutorOptions< + ActionTypeConfigType, + {}, + ActionParamsType +>; + // config definition export type ActionTypeConfigType = TypeOf; @@ -33,7 +40,7 @@ const ParamsSchema = schema.object({ }); // action type definition -export function getActionType({ logger }: { logger: Logger }): ActionType { +export function getActionType({ logger }: { logger: Logger }): ESIndexActionType { return { id: '.index', minimumLicenseRequired: 'basic', @@ -52,11 +59,11 @@ export function getActionType({ logger }: { logger: Logger }): ActionType { async function executor( { logger }: { logger: Logger }, - execOptions: ActionTypeExecutorOptions -): Promise { + execOptions: ESIndexActionTypeExecutorOptions +): Promise> { const actionId = execOptions.actionId; - const config = execOptions.config as ActionTypeConfigType; - const params = execOptions.params as ActionParamsType; + const config = execOptions.config; + const params = execOptions.params; const services = execOptions.services; const index = config.index; diff --git a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts index b1ed3728edfae..c379c05ee88e3 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.test.ts @@ -8,14 +8,21 @@ jest.mock('./lib/post_pagerduty', () => ({ postPagerduty: jest.fn(), })); -import { getActionType } from './pagerduty'; -import { ActionType, Services, ActionTypeExecutorOptions } from '../types'; +import { Services } from '../types'; import { validateConfig, validateSecrets, validateParams } from '../lib'; import { postPagerduty } from './lib/post_pagerduty'; import { createActionTypeRegistry } from './index.test'; import { Logger } from '../../../../../src/core/server'; import { actionsConfigMock } from '../actions_config.mock'; import { actionsMock } from '../mocks'; +import { + ActionParamsType, + ActionTypeConfigType, + ActionTypeSecretsType, + getActionType, + PagerDutyActionType, + PagerDutyActionTypeExecutorOptions, +} from './pagerduty'; const postPagerdutyMock = postPagerduty as jest.Mock; @@ -23,12 +30,16 @@ const ACTION_TYPE_ID = '.pagerduty'; const services: Services = actionsMock.createServices(); -let actionType: ActionType; +let actionType: PagerDutyActionType; let mockedLogger: jest.Mocked; beforeAll(() => { const { logger, actionTypeRegistry } = createActionTypeRegistry(); - actionType = actionTypeRegistry.get(ACTION_TYPE_ID); + actionType = actionTypeRegistry.get< + ActionTypeConfigType, + ActionTypeSecretsType, + ActionParamsType + >(ACTION_TYPE_ID); mockedLogger = logger; }); @@ -167,7 +178,7 @@ describe('execute()', () => { test('should succeed with minimal valid params', async () => { const secrets = { routingKey: 'super-secret' }; - const config = {}; + const config = { apiUrl: null }; const params = {}; postPagerdutyMock.mockImplementation(() => { @@ -175,7 +186,7 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: ActionTypeExecutorOptions = { + const executorOptions: PagerDutyActionTypeExecutorOptions = { actionId, config, params, @@ -219,7 +230,7 @@ describe('execute()', () => { const config = { apiUrl: 'the-api-url', }; - const params = { + const params: ActionParamsType = { eventAction: 'trigger', dedupKey: 'a-dedup-key', summary: 'the summary', @@ -236,7 +247,7 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: ActionTypeExecutorOptions = { + const executorOptions: PagerDutyActionTypeExecutorOptions = { actionId, config, params, @@ -284,7 +295,7 @@ describe('execute()', () => { const config = { apiUrl: 'the-api-url', }; - const params = { + const params: ActionParamsType = { eventAction: 'acknowledge', dedupKey: 'a-dedup-key', summary: 'the summary', @@ -301,7 +312,7 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: ActionTypeExecutorOptions = { + const executorOptions: PagerDutyActionTypeExecutorOptions = { actionId, config, params, @@ -340,7 +351,7 @@ describe('execute()', () => { const config = { apiUrl: 'the-api-url', }; - const params = { + const params: ActionParamsType = { eventAction: 'resolve', dedupKey: 'a-dedup-key', summary: 'the summary', @@ -357,7 +368,7 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: ActionTypeExecutorOptions = { + const executorOptions: PagerDutyActionTypeExecutorOptions = { actionId, config, params, @@ -390,7 +401,7 @@ describe('execute()', () => { test('should fail when sendPagerdury throws', async () => { const secrets = { routingKey: 'super-secret' }; - const config = {}; + const config = { apiUrl: null }; const params = {}; postPagerdutyMock.mockImplementation(() => { @@ -398,7 +409,7 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: ActionTypeExecutorOptions = { + const executorOptions: PagerDutyActionTypeExecutorOptions = { actionId, config, params, @@ -418,7 +429,7 @@ describe('execute()', () => { test('should fail when sendPagerdury returns 429', async () => { const secrets = { routingKey: 'super-secret' }; - const config = {}; + const config = { apiUrl: null }; const params = {}; postPagerdutyMock.mockImplementation(() => { @@ -426,7 +437,7 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: ActionTypeExecutorOptions = { + const executorOptions: PagerDutyActionTypeExecutorOptions = { actionId, config, params, @@ -446,7 +457,7 @@ describe('execute()', () => { test('should fail when sendPagerdury returns 501', async () => { const secrets = { routingKey: 'super-secret' }; - const config = {}; + const config = { apiUrl: null }; const params = {}; postPagerdutyMock.mockImplementation(() => { @@ -454,7 +465,7 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: ActionTypeExecutorOptions = { + const executorOptions: PagerDutyActionTypeExecutorOptions = { actionId, config, params, @@ -474,7 +485,7 @@ describe('execute()', () => { test('should fail when sendPagerdury returns 418', async () => { const secrets = { routingKey: 'super-secret' }; - const config = {}; + const config = { apiUrl: null }; const params = {}; postPagerdutyMock.mockImplementation(() => { @@ -482,7 +493,7 @@ describe('execute()', () => { }); const actionId = 'some-action-id'; - const executorOptions: ActionTypeExecutorOptions = { + const executorOptions: PagerDutyActionTypeExecutorOptions = { actionId, config, params, diff --git a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts index 0c8802060164d..b76e57419bc56 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts @@ -16,6 +16,18 @@ import { ActionsConfigurationUtilities } from '../actions_config'; // https://v2.developer.pagerduty.com/docs/events-api-v2 const PAGER_DUTY_API_URL = 'https://events.pagerduty.com/v2/enqueue'; +export type PagerDutyActionType = ActionType< + ActionTypeConfigType, + ActionTypeSecretsType, + ActionParamsType, + unknown +>; +export type PagerDutyActionTypeExecutorOptions = ActionTypeExecutorOptions< + ActionTypeConfigType, + ActionTypeSecretsType, + ActionParamsType +>; + // config definition export type ActionTypeConfigType = TypeOf; @@ -100,7 +112,7 @@ export function getActionType({ }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities; -}): ActionType { +}): PagerDutyActionType { return { id: '.pagerduty', minimumLicenseRequired: 'gold', @@ -142,12 +154,12 @@ function getPagerDutyApiUrl(config: ActionTypeConfigType): string { async function executor( { logger }: { logger: Logger }, - execOptions: ActionTypeExecutorOptions -): Promise { + execOptions: PagerDutyActionTypeExecutorOptions +): Promise> { const actionId = execOptions.actionId; - const config = execOptions.config as ActionTypeConfigType; - const secrets = execOptions.secrets as ActionTypeSecretsType; - const params = execOptions.params as ActionParamsType; + const config = execOptions.config; + const secrets = execOptions.secrets; + const params = execOptions.params; const services = execOptions.services; const apiUrl = getPagerDutyApiUrl(config); diff --git a/x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts b/x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts index d5a9c0cc1ccd2..e4828f33765ce 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/server_log.test.ts @@ -4,20 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ActionType } from '../types'; import { validateParams } from '../lib'; import { Logger } from '../../../../../src/core/server'; import { createActionTypeRegistry } from './index.test'; import { actionsMock } from '../mocks'; +import { + ActionParamsType, + ServerLogActionType, + ServerLogActionTypeExecutorOptions, +} from './server_log'; const ACTION_TYPE_ID = '.server-log'; -let actionType: ActionType; +let actionType: ServerLogActionType; let mockedLogger: jest.Mocked; beforeAll(() => { const { logger, actionTypeRegistry } = createActionTypeRegistry(); - actionType = actionTypeRegistry.get(ACTION_TYPE_ID); + actionType = actionTypeRegistry.get<{}, {}, ActionParamsType>(ACTION_TYPE_ID); mockedLogger = logger; expect(actionType).toBeTruthy(); }); @@ -88,13 +92,14 @@ describe('validateParams()', () => { describe('execute()', () => { test('calls the executor with proper params', async () => { const actionId = 'some-id'; - await actionType.executor({ + const executorOptions: ServerLogActionTypeExecutorOptions = { actionId, services: actionsMock.createServices(), params: { message: 'message text here', level: 'info' }, config: {}, secrets: {}, - }); + }; + await actionType.executor(executorOptions); expect(mockedLogger.info).toHaveBeenCalledWith('Server log: message text here'); }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/server_log.ts b/x-pack/plugins/actions/server/builtin_action_types/server_log.ts index bf8a3d8032cc5..490764fb16bfd 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/server_log.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/server_log.ts @@ -12,6 +12,13 @@ import { Logger } from '../../../../../src/core/server'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; import { withoutControlCharacters } from './lib/string_utils'; +export type ServerLogActionType = ActionType<{}, {}, ActionParamsType>; +export type ServerLogActionTypeExecutorOptions = ActionTypeExecutorOptions< + {}, + {}, + ActionParamsType +>; + // params definition export type ActionParamsType = TypeOf; @@ -32,7 +39,7 @@ const ParamsSchema = schema.object({ }); // action type definition -export function getActionType({ logger }: { logger: Logger }): ActionType { +export function getActionType({ logger }: { logger: Logger }): ServerLogActionType { return { id: '.server-log', minimumLicenseRequired: 'basic', @@ -50,10 +57,10 @@ export function getActionType({ logger }: { logger: Logger }): ActionType { async function executor( { logger }: { logger: Logger }, - execOptions: ActionTypeExecutorOptions -): Promise { + execOptions: ServerLogActionTypeExecutorOptions +): Promise> { const actionId = execOptions.actionId; - const params = execOptions.params as ActionParamsType; + const params = execOptions.params; const sanitizedMessage = withoutControlCharacters(params.message); try { diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts index e62ca465f30f8..109008b8fc9fb 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts @@ -17,9 +17,14 @@ import { ActionsConfigurationUtilities } from '../../actions_config'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../../types'; import { createExternalService } from './service'; import { api } from './api'; -import { ExecutorParams, ExecutorSubActionPushParams } from './types'; import * as i18n from './translations'; import { Logger } from '../../../../../../src/core/server'; +import { + ExecutorParams, + ExecutorSubActionPushParams, + ServiceNowPublicConfigurationType, + ServiceNowSecretConfigurationType, +} from './types'; // TODO: to remove, need to support Case import { buildMap, mapParams } from '../case/utils'; @@ -31,7 +36,14 @@ interface GetActionTypeParams { } // action type definition -export function getActionType(params: GetActionTypeParams): ActionType { +export function getActionType( + params: GetActionTypeParams +): ActionType< + ServiceNowPublicConfigurationType, + ServiceNowSecretConfigurationType, + ExecutorParams, + PushToServiceResponse | {} +> { const { logger, configurationUtilities } = params; return { id: '.servicenow', @@ -54,10 +66,14 @@ export function getActionType(params: GetActionTypeParams): ActionType { async function executor( { logger }: { logger: Logger }, - execOptions: ActionTypeExecutorOptions -): Promise { + execOptions: ActionTypeExecutorOptions< + ServiceNowPublicConfigurationType, + ServiceNowSecretConfigurationType, + ExecutorParams + > +): Promise> { const { actionId, config, params, secrets } = execOptions; - const { subAction, subActionParams } = params as ExecutorParams; + const { subAction, subActionParams } = params; let data: PushToServiceResponse | null = null; const externalService = createExternalService({ @@ -81,9 +97,8 @@ async function executor( const pushToServiceParams = subActionParams as ExecutorSubActionPushParams; const { comments, externalId, ...restParams } = pushToServiceParams; - const mapping = config.incidentConfiguration - ? buildMap(config.incidentConfiguration.mapping) - : null; + const incidentConfiguration = config.incidentConfiguration; + const mapping = incidentConfiguration ? buildMap(incidentConfiguration.mapping) : null; const externalObject = config.incidentConfiguration && mapping ? mapParams(restParams, mapping) : {}; diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts index d1a739c2304f2..6d4176067c3ba 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts @@ -4,14 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - ActionType, - Services, - ActionTypeExecutorOptions, - ActionTypeExecutorResult, -} from '../types'; +import { Services, ActionTypeExecutorResult } from '../types'; import { validateParams, validateSecrets } from '../lib'; -import { getActionType } from './slack'; +import { getActionType, SlackActionType, SlackActionTypeExecutorOptions } from './slack'; import { actionsConfigMock } from '../actions_config.mock'; import { actionsMock } from '../mocks'; @@ -19,11 +14,13 @@ const ACTION_TYPE_ID = '.slack'; const services: Services = actionsMock.createServices(); -let actionType: ActionType; +let actionType: SlackActionType; beforeAll(() => { actionType = getActionType({ - async executor() {}, + async executor(options) { + return { status: 'ok', actionId: options.actionId }; + }, configurationUtilities: actionsConfigMock.create(), }); }); @@ -119,7 +116,7 @@ describe('validateActionTypeSecrets()', () => { describe('execute()', () => { beforeAll(() => { - async function mockSlackExecutor(options: ActionTypeExecutorOptions) { + async function mockSlackExecutor(options: SlackActionTypeExecutorOptions) { const { params } = options; const { message } = params; if (message == null) throw new Error('message property required in parameter'); @@ -134,7 +131,7 @@ describe('execute()', () => { text: `slack mockExecutor success: ${message}`, actionId: '', status: 'ok', - } as ActionTypeExecutorResult; + } as ActionTypeExecutorResult; } actionType = getActionType({ diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.ts index 55c373f14cd69..209582585256b 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/slack.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/slack.ts @@ -21,6 +21,13 @@ import { } from '../types'; import { ActionsConfigurationUtilities } from '../actions_config'; +export type SlackActionType = ActionType<{}, ActionTypeSecretsType, ActionParamsType, unknown>; +export type SlackActionTypeExecutorOptions = ActionTypeExecutorOptions< + {}, + ActionTypeSecretsType, + ActionParamsType +>; + // secrets definition export type ActionTypeSecretsType = TypeOf; @@ -46,8 +53,8 @@ export function getActionType({ executor = slackExecutor, }: { configurationUtilities: ActionsConfigurationUtilities; - executor?: ExecutorType; -}): ActionType { + executor?: ExecutorType<{}, ActionTypeSecretsType, ActionParamsType, unknown>; +}): SlackActionType { return { id: '.slack', minimumLicenseRequired: 'gold', @@ -92,11 +99,11 @@ function valdiateActionTypeConfig( // action executor async function slackExecutor( - execOptions: ActionTypeExecutorOptions -): Promise { + execOptions: SlackActionTypeExecutorOptions +): Promise> { const actionId = execOptions.actionId; - const secrets = execOptions.secrets as ActionTypeSecretsType; - const params = execOptions.params as ActionParamsType; + const secrets = execOptions.secrets; + const params = execOptions.params; let result: IncomingWebhookResult; const { webhookUrl } = secrets; @@ -156,18 +163,21 @@ async function slackExecutor( return successResult(actionId, result); } -function successResult(actionId: string, data: unknown): ActionTypeExecutorResult { +function successResult(actionId: string, data: unknown): ActionTypeExecutorResult { return { status: 'ok', data, actionId }; } -function errorResult(actionId: string, message: string): ActionTypeExecutorResult { +function errorResult(actionId: string, message: string): ActionTypeExecutorResult { return { status: 'error', message, actionId, }; } -function serviceErrorResult(actionId: string, serviceMessage: string): ActionTypeExecutorResult { +function serviceErrorResult( + actionId: string, + serviceMessage: string +): ActionTypeExecutorResult { const errMessage = i18n.translate('xpack.actions.builtin.slack.errorPostingErrorMessage', { defaultMessage: 'error posting slack message', }); @@ -179,7 +189,7 @@ function serviceErrorResult(actionId: string, serviceMessage: string): ActionTyp }; } -function retryResult(actionId: string, message: string): ActionTypeExecutorResult { +function retryResult(actionId: string, message: string): ActionTypeExecutorResult { const errMessage = i18n.translate( 'xpack.actions.builtin.slack.errorPostingRetryLaterErrorMessage', { @@ -198,7 +208,7 @@ function retryResultSeconds( actionId: string, message: string, retryAfter: number -): ActionTypeExecutorResult { +): ActionTypeExecutorResult { const retryEpoch = Date.now() + retryAfter * 1000; const retry = new Date(retryEpoch); const retryString = retry.toISOString(); diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts index 53b17f58d6e18..26dd8a1a1402a 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts @@ -8,14 +8,21 @@ jest.mock('axios', () => ({ request: jest.fn(), })); -import { getActionType } from './webhook'; -import { ActionType, Services } from '../types'; +import { Services } from '../types'; import { validateConfig, validateSecrets, validateParams } from '../lib'; import { actionsConfigMock } from '../actions_config.mock'; import { createActionTypeRegistry } from './index.test'; import { Logger } from '../../../../../src/core/server'; import { actionsMock } from '../mocks'; import axios from 'axios'; +import { + ActionParamsType, + ActionTypeConfigType, + ActionTypeSecretsType, + getActionType, + WebhookActionType, + WebhookMethods, +} from './webhook'; const axiosRequestMock = axios.request as jest.Mock; @@ -23,12 +30,16 @@ const ACTION_TYPE_ID = '.webhook'; const services: Services = actionsMock.createServices(); -let actionType: ActionType; +let actionType: WebhookActionType; let mockedLogger: jest.Mocked; beforeAll(() => { const { logger, actionTypeRegistry } = createActionTypeRegistry(); - actionType = actionTypeRegistry.get(ACTION_TYPE_ID); + actionType = actionTypeRegistry.get< + ActionTypeConfigType, + ActionTypeSecretsType, + ActionParamsType + >(ACTION_TYPE_ID); mockedLogger = logger; }); @@ -235,16 +246,17 @@ describe('execute()', () => { }); test('execute with username/password sends request with basic auth', async () => { + const config: ActionTypeConfigType = { + url: 'https://abc.def/my-webhook', + method: WebhookMethods.POST, + headers: { + aheader: 'a value', + }, + }; await actionType.executor({ actionId: 'some-id', services, - config: { - url: 'https://abc.def/my-webhook', - method: 'post', - headers: { - aheader: 'a value', - }, - }, + config, secrets: { user: 'abc', password: '123' }, params: { body: 'some data' }, }); @@ -266,17 +278,19 @@ describe('execute()', () => { }); test('execute without username/password sends request without basic auth', async () => { + const config: ActionTypeConfigType = { + url: 'https://abc.def/my-webhook', + method: WebhookMethods.POST, + headers: { + aheader: 'a value', + }, + }; + const secrets: ActionTypeSecretsType = { user: null, password: null }; await actionType.executor({ actionId: 'some-id', services, - config: { - url: 'https://abc.def/my-webhook', - method: 'post', - headers: { - aheader: 'a value', - }, - }, - secrets: {}, + config, + secrets, params: { body: 'some data' }, }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts index 0b8b27b278928..be75742fa882e 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts @@ -17,11 +17,23 @@ import { ActionsConfigurationUtilities } from '../actions_config'; import { Logger } from '../../../../../src/core/server'; // config definition -enum WebhookMethods { +export enum WebhookMethods { POST = 'post', PUT = 'put', } +export type WebhookActionType = ActionType< + ActionTypeConfigType, + ActionTypeSecretsType, + ActionParamsType, + unknown +>; +export type WebhookActionTypeExecutorOptions = ActionTypeExecutorOptions< + ActionTypeConfigType, + ActionTypeSecretsType, + ActionParamsType +>; + const HeadersSchema = schema.recordOf(schema.string(), schema.string()); const configSchemaProps = { url: schema.string(), @@ -31,7 +43,7 @@ const configSchemaProps = { headers: nullableType(HeadersSchema), }; const ConfigSchema = schema.object(configSchemaProps); -type ActionTypeConfigType = TypeOf; +export type ActionTypeConfigType = TypeOf; // secrets definition export type ActionTypeSecretsType = TypeOf; @@ -51,7 +63,7 @@ const SecretsSchema = schema.object(secretSchemaProps, { }); // params definition -type ActionParamsType = TypeOf; +export type ActionParamsType = TypeOf; const ParamsSchema = schema.object({ body: schema.maybe(schema.string()), }); @@ -63,7 +75,7 @@ export function getActionType({ }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities; -}): ActionType { +}): WebhookActionType { return { id: '.webhook', minimumLicenseRequired: 'gold', @@ -112,13 +124,13 @@ function validateActionTypeConfig( // action executor export async function executor( { logger }: { logger: Logger }, - execOptions: ActionTypeExecutorOptions -): Promise { + execOptions: WebhookActionTypeExecutorOptions +): Promise> { const actionId = execOptions.actionId; - const { method, url, headers = {} } = execOptions.config as ActionTypeConfigType; - const { body: data } = execOptions.params as ActionParamsType; + const { method, url, headers = {} } = execOptions.config; + const { body: data } = execOptions.params; - const secrets: ActionTypeSecretsType = execOptions.secrets as ActionTypeSecretsType; + const secrets: ActionTypeSecretsType = execOptions.secrets; const basicAuth = isString(secrets.user) && isString(secrets.password) ? { auth: { username: secrets.user, password: secrets.password } } @@ -172,11 +184,14 @@ export async function executor( } // Action Executor Result w/ internationalisation -function successResult(actionId: string, data: unknown): ActionTypeExecutorResult { +function successResult(actionId: string, data: unknown): ActionTypeExecutorResult { return { status: 'ok', data, actionId }; } -function errorResultInvalid(actionId: string, serviceMessage: string): ActionTypeExecutorResult { +function errorResultInvalid( + actionId: string, + serviceMessage: string +): ActionTypeExecutorResult { const errMessage = i18n.translate('xpack.actions.builtin.webhook.invalidResponseErrorMessage', { defaultMessage: 'error calling webhook, invalid response', }); @@ -188,7 +203,7 @@ function errorResultInvalid(actionId: string, serviceMessage: string): ActionTyp }; } -function errorResultUnexpectedError(actionId: string): ActionTypeExecutorResult { +function errorResultUnexpectedError(actionId: string): ActionTypeExecutorResult { const errMessage = i18n.translate('xpack.actions.builtin.webhook.unreachableErrorMessage', { defaultMessage: 'error calling webhook, unexpected error', }); @@ -199,7 +214,7 @@ function errorResultUnexpectedError(actionId: string): ActionTypeExecutorResult }; } -function retryResult(actionId: string, serviceMessage: string): ActionTypeExecutorResult { +function retryResult(actionId: string, serviceMessage: string): ActionTypeExecutorResult { const errMessage = i18n.translate( 'xpack.actions.builtin.webhook.invalidResponseRetryLaterErrorMessage', { @@ -220,7 +235,7 @@ function retryResultSeconds( serviceMessage: string, retryAfter: number -): ActionTypeExecutorResult { +): ActionTypeExecutorResult { const retryEpoch = Date.now() + retryAfter * 1000; const retry = new Date(retryEpoch); const retryString = retry.toISOString(); diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index 0e63cc8f5956e..bce06c829b1bc 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -59,7 +59,7 @@ export class ActionExecutor { actionId, params, request, - }: ExecuteOptions): Promise { + }: ExecuteOptions): Promise> { if (!this.isInitialized) { throw new Error('ActionExecutor not initialized'); } @@ -125,7 +125,7 @@ export class ActionExecutor { }; eventLogger.startTiming(event); - let rawResult: ActionTypeExecutorResult | null | undefined | void; + let rawResult: ActionTypeExecutorResult; try { rawResult = await actionType.executor({ actionId, @@ -173,7 +173,7 @@ export class ActionExecutor { } } -function actionErrorToMessage(result: ActionTypeExecutorResult): string { +function actionErrorToMessage(result: ActionTypeExecutorResult): string { let message = result.message || 'unknown error running action'; if (result.serviceMessage) { diff --git a/x-pack/plugins/actions/server/lib/license_state.test.ts b/x-pack/plugins/actions/server/lib/license_state.test.ts index 0a474ec3ae3ea..32c3c54faf007 100644 --- a/x-pack/plugins/actions/server/lib/license_state.test.ts +++ b/x-pack/plugins/actions/server/lib/license_state.test.ts @@ -59,7 +59,9 @@ describe('isLicenseValidForActionType', () => { id: 'foo', name: 'Foo', minimumLicenseRequired: 'gold', - executor: async () => {}, + executor: async (options) => { + return { status: 'ok', actionId: options.actionId }; + }, }; beforeEach(() => { @@ -120,7 +122,9 @@ describe('ensureLicenseForActionType()', () => { id: 'foo', name: 'Foo', minimumLicenseRequired: 'gold', - executor: async () => {}, + executor: async (options) => { + return { status: 'ok', actionId: options.actionId }; + }, }; beforeEach(() => { diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.ts index 9204c41b9288c..10a8501e856d2 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.ts @@ -94,7 +94,7 @@ export class TaskRunnerFactory { }, } as unknown) as KibanaRequest; - let executorResult: ActionTypeExecutorResult; + let executorResult: ActionTypeExecutorResult; try { executorResult = await actionExecutor.execute({ params, diff --git a/x-pack/plugins/actions/server/lib/validate_with_schema.test.ts b/x-pack/plugins/actions/server/lib/validate_with_schema.test.ts index 03ae7a9b35a81..10c688c075eab 100644 --- a/x-pack/plugins/actions/server/lib/validate_with_schema.test.ts +++ b/x-pack/plugins/actions/server/lib/validate_with_schema.test.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { validateParams, validateConfig, validateSecrets } from './validate_with_schema'; import { ActionType, ExecutorType } from '../types'; -const executor: ExecutorType = async (options) => { +const executor: ExecutorType<{}, {}, {}, void> = async (options) => { return { status: 'ok', actionId: options.actionId }; }; diff --git a/x-pack/plugins/actions/server/lib/validate_with_schema.ts b/x-pack/plugins/actions/server/lib/validate_with_schema.ts index 021c460f4c815..50231f1c9a3a1 100644 --- a/x-pack/plugins/actions/server/lib/validate_with_schema.ts +++ b/x-pack/plugins/actions/server/lib/validate_with_schema.ts @@ -5,24 +5,44 @@ */ import Boom from 'boom'; -import { ActionType } from '../types'; +import { ActionType, ActionTypeConfig, ActionTypeSecrets, ActionTypeParams } from '../types'; -export function validateParams(actionType: ActionType, value: unknown) { +export function validateParams< + Config extends ActionTypeConfig = ActionTypeConfig, + Secrets extends ActionTypeSecrets = ActionTypeSecrets, + Params extends ActionTypeParams = ActionTypeParams, + ExecutorResultData = void +>(actionType: ActionType, value: unknown) { return validateWithSchema(actionType, 'params', value); } -export function validateConfig(actionType: ActionType, value: unknown) { +export function validateConfig< + Config extends ActionTypeConfig = ActionTypeConfig, + Secrets extends ActionTypeSecrets = ActionTypeSecrets, + Params extends ActionTypeParams = ActionTypeParams, + ExecutorResultData = void +>(actionType: ActionType, value: unknown) { return validateWithSchema(actionType, 'config', value); } -export function validateSecrets(actionType: ActionType, value: unknown) { +export function validateSecrets< + Config extends ActionTypeConfig = ActionTypeConfig, + Secrets extends ActionTypeSecrets = ActionTypeSecrets, + Params extends ActionTypeParams = ActionTypeParams, + ExecutorResultData = void +>(actionType: ActionType, value: unknown) { return validateWithSchema(actionType, 'secrets', value); } type ValidKeys = 'params' | 'config' | 'secrets'; -function validateWithSchema( - actionType: ActionType, +function validateWithSchema< + Config extends ActionTypeConfig = ActionTypeConfig, + Secrets extends ActionTypeSecrets = ActionTypeSecrets, + Params extends ActionTypeParams = ActionTypeParams, + ExecutorResultData = void +>( + actionType: ActionType, key: ValidKeys, value: unknown ): Record { diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts index ac4b332e7fd7a..ca93e88d01203 100644 --- a/x-pack/plugins/actions/server/plugin.test.ts +++ b/x-pack/plugins/actions/server/plugin.test.ts @@ -125,7 +125,9 @@ describe('Actions Plugin', () => { id: 'test', name: 'test', minimumLicenseRequired: 'basic', - async executor() {}, + async executor(options) { + return { status: 'ok', actionId: options.actionId }; + }, }; beforeEach(async () => { diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 5b8b25d02658b..54d137cc0f617 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -33,13 +33,20 @@ import { PluginSetupContract as FeaturesPluginSetup } from '../../features/serve import { SecurityPluginSetup } from '../../security/server'; import { ActionsConfig } from './config'; -import { Services, ActionType, PreConfiguredAction } from './types'; import { ActionExecutor, TaskRunnerFactory, LicenseState, ILicenseState } from './lib'; import { ActionsClient } from './actions_client'; import { ActionTypeRegistry } from './action_type_registry'; import { createExecutionEnqueuerFunction } from './create_execute_function'; import { registerBuiltInActionTypes } from './builtin_action_types'; import { registerActionsUsageCollector } from './usage'; +import { + Services, + ActionType, + PreConfiguredAction, + ActionTypeConfig, + ActionTypeSecrets, + ActionTypeParams, +} from './types'; import { getActionsConfigurationUtilities } from './actions_config'; @@ -70,7 +77,13 @@ export const EVENT_LOG_ACTIONS = { }; export interface PluginSetupContract { - registerType: (actionType: ActionType) => void; + registerType< + Config extends ActionTypeConfig = ActionTypeConfig, + Secrets extends ActionTypeSecrets = ActionTypeSecrets, + Params extends ActionTypeParams = ActionTypeParams + >( + actionType: ActionType + ): void; } export interface PluginStartContract { @@ -219,7 +232,13 @@ export class ActionsPlugin implements Plugin, Plugi executeActionRoute(router, this.licenseState); return { - registerType: (actionType: ActionType) => { + registerType: < + Config extends ActionTypeConfig = ActionTypeConfig, + Secrets extends ActionTypeSecrets = ActionTypeSecrets, + Params extends ActionTypeParams = ActionTypeParams + >( + actionType: ActionType + ) => { if (!(actionType.minimumLicenseRequired in LICENSE_TYPE)) { throw new Error(`"${actionType.minimumLicenseRequired}" is not a valid license type`); } diff --git a/x-pack/plugins/actions/server/routes/execute.test.ts b/x-pack/plugins/actions/server/routes/execute.test.ts index 38fca656bef5a..b668e3460828a 100644 --- a/x-pack/plugins/actions/server/routes/execute.test.ts +++ b/x-pack/plugins/actions/server/routes/execute.test.ts @@ -71,7 +71,9 @@ describe('executeActionRoute', () => { const router = httpServiceMock.createRouter(); const actionsClient = actionsClientMock.create(); - actionsClient.execute.mockResolvedValueOnce((null as unknown) as ActionTypeExecutorResult); + actionsClient.execute.mockResolvedValueOnce( + (null as unknown) as ActionTypeExecutorResult + ); const [context, req, res] = mockHandlerArguments( { actionsClient }, diff --git a/x-pack/plugins/actions/server/routes/execute.ts b/x-pack/plugins/actions/server/routes/execute.ts index 0d49d9a3a256e..f15a117106210 100644 --- a/x-pack/plugins/actions/server/routes/execute.ts +++ b/x-pack/plugins/actions/server/routes/execute.ts @@ -48,7 +48,7 @@ export const executeActionRoute = (router: IRouter, licenseState: ILicenseState) const { params } = req.body; const { id } = req.params; try { - const body: ActionTypeExecutorResult = await actionsClient.execute({ + const body: ActionTypeExecutorResult = await actionsClient.execute({ params, actionId: id, }); diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index a8e19e3ff2e79..ecec45ade0460 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -21,6 +21,9 @@ export type GetServicesFunction = (request: KibanaRequest) => Services; export type ActionTypeRegistryContract = PublicMethodsOf; export type GetBasePathFunction = (spaceId?: string) => string; export type SpaceIdToNamespaceFunction = (spaceId?: string) => string | undefined; +export type ActionTypeConfig = Record; +export type ActionTypeSecrets = Record; +export type ActionTypeParams = Record; export interface Services { callCluster: ILegacyScopedClusterClient['callAsCurrentUser']; @@ -49,32 +52,27 @@ export interface ActionsConfigType { } // the parameters passed to an action type executor function -export interface ActionTypeExecutorOptions { +export interface ActionTypeExecutorOptions { actionId: string; services: Services; - // This will have to remain `any` until we can extend Action Executors with generics - // eslint-disable-next-line @typescript-eslint/no-explicit-any - config: Record; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - secrets: Record; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - params: Record; + config: Config; + secrets: Secrets; + params: Params; } -export interface ActionResult { +export interface ActionResult { id: string; actionTypeId: string; name: string; - // This will have to remain `any` until we can extend Action Executors with generics - // eslint-disable-next-line @typescript-eslint/no-explicit-any - config?: Record; + config?: Config; isPreconfigured: boolean; } -export interface PreConfiguredAction extends ActionResult { - // This will have to remain `any` until we can extend Action Executors with generics - // eslint-disable-next-line @typescript-eslint/no-explicit-any - secrets: Record; +export interface PreConfiguredAction< + Config extends ActionTypeConfig = ActionTypeConfig, + Secrets extends ActionTypeSecrets = ActionTypeSecrets +> extends ActionResult { + secrets: Secrets; } export interface FindActionResult extends ActionResult { @@ -82,38 +80,45 @@ export interface FindActionResult extends ActionResult { } // the result returned from an action type executor function -export interface ActionTypeExecutorResult { +export interface ActionTypeExecutorResult { actionId: string; status: 'ok' | 'error'; message?: string; serviceMessage?: string; - // This will have to remain `any` until we can extend Action Executors with generics - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data?: any; + data?: Data; retry?: null | boolean | Date; } // signature of the action type executor function -export type ExecutorType = ( - options: ActionTypeExecutorOptions -) => Promise; +export type ExecutorType = ( + options: ActionTypeExecutorOptions +) => Promise>; + +interface ValidatorType { + validate(value: unknown): Type; +} -interface ValidatorType { - validate(value: unknown): Record; +export interface ActionValidationService { + isWhitelistedHostname(hostname: string): boolean; + isWhitelistedUri(uri: string): boolean; } -export type ActionTypeCreator = (config?: ActionsConfigType) => ActionType; -export interface ActionType { +export interface ActionType< + Config extends ActionTypeConfig = ActionTypeConfig, + Secrets extends ActionTypeSecrets = ActionTypeSecrets, + Params extends ActionTypeParams = ActionTypeParams, + ExecutorResultData = void +> { id: string; name: string; maxAttempts?: number; minimumLicenseRequired: LicenseType; validate?: { - params?: ValidatorType; - config?: ValidatorType; - secrets?: ValidatorType; + params?: ValidatorType; + config?: ValidatorType; + secrets?: ValidatorType; }; - executor: ExecutorType; + executor: ExecutorType; } export interface RawAction extends SavedObjectAttributes { diff --git a/x-pack/plugins/security_solution/public/cases/containers/api.ts b/x-pack/plugins/security_solution/public/cases/containers/api.ts index 4f7ef290370cc..35422fa3c5fef 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/api.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/api.ts @@ -241,16 +241,15 @@ export const pushToService = async ( casePushParams: ServiceConnectorCaseParams, signal: AbortSignal ): Promise => { - const response = await KibanaServices.get().http.fetch( - `${ACTION_URL}/action/${connectorId}/_execute`, - { - method: 'POST', - body: JSON.stringify({ - params: { subAction: 'pushToService', subActionParams: casePushParams }, - }), - signal, - } - ); + const response = await KibanaServices.get().http.fetch< + ActionTypeExecutorResult> + >(`${ACTION_URL}/action/${connectorId}/_execute`, { + method: 'POST', + body: JSON.stringify({ + params: { subAction: 'pushToService', subActionParams: casePushParams }, + }), + signal, + }); if (response.status === 'error') { throw new Error(response.serviceMessage ?? response.message ?? i18n.ERROR_PUSH_TO_SERVICE); diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/action_types.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/action_types.ts index fd0d03dc18410..7c43ac0bbe56f 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/action_types.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/action_types.ts @@ -5,16 +5,14 @@ */ import { CoreSetup } from 'src/core/server'; -import { schema } from '@kbn/config-schema'; +import { schema, TypeOf } from '@kbn/config-schema'; import { FixtureStartDeps, FixtureSetupDeps } from './plugin'; -import { ActionType, ActionTypeExecutorOptions } from '../../../../../../../plugins/actions/server'; +import { ActionType } from '../../../../../../../plugins/actions/server'; export function defineActionTypes( core: CoreSetup, { actions }: Pick ) { - const clusterClient = core.elasticsearch.legacy.client; - // Action types const noopActionType: ActionType = { id: 'test.noop', @@ -32,24 +30,39 @@ export function defineActionTypes( throw new Error('this action is intended to fail'); }, }; - const indexRecordActionType: ActionType = { + actions.registerType(noopActionType); + actions.registerType(throwActionType); + actions.registerType(getIndexRecordActionType()); + actions.registerType(getFailingActionType()); + actions.registerType(getRateLimitedActionType()); + actions.registerType(getAuthorizationActionType(core)); +} + +function getIndexRecordActionType() { + const paramsSchema = schema.object({ + index: schema.string(), + reference: schema.string(), + message: schema.string(), + }); + type ParamsType = TypeOf; + const configSchema = schema.object({ + unencrypted: schema.string(), + }); + type ConfigType = TypeOf; + const secretsSchema = schema.object({ + encrypted: schema.string(), + }); + type SecretsType = TypeOf; + const result: ActionType = { id: 'test.index-record', name: 'Test: Index Record', minimumLicenseRequired: 'gold', validate: { - params: schema.object({ - index: schema.string(), - reference: schema.string(), - message: schema.string(), - }), - config: schema.object({ - unencrypted: schema.string(), - }), - secrets: schema.object({ - encrypted: schema.string(), - }), + params: paramsSchema, + config: configSchema, + secrets: secretsSchema, }, - async executor({ config, secrets, params, services, actionId }: ActionTypeExecutorOptions) { + async executor({ config, secrets, params, services, actionId }) { await services.callCluster('index', { index: params.index, refresh: 'wait_for', @@ -64,17 +77,23 @@ export function defineActionTypes( return { status: 'ok', actionId }; }, }; - const failingActionType: ActionType = { + return result; +} + +function getFailingActionType() { + const paramsSchema = schema.object({ + index: schema.string(), + reference: schema.string(), + }); + type ParamsType = TypeOf; + const result: ActionType<{}, {}, ParamsType> = { id: 'test.failing', name: 'Test: Failing', minimumLicenseRequired: 'gold', validate: { - params: schema.object({ - index: schema.string(), - reference: schema.string(), - }), + params: paramsSchema, }, - async executor({ config, secrets, params, services }: ActionTypeExecutorOptions) { + async executor({ config, secrets, params, services }) { await services.callCluster('index', { index: params.index, refresh: 'wait_for', @@ -89,19 +108,25 @@ export function defineActionTypes( throw new Error(`expected failure for ${params.index} ${params.reference}`); }, }; - const rateLimitedActionType: ActionType = { + return result; +} + +function getRateLimitedActionType() { + const paramsSchema = schema.object({ + index: schema.string(), + reference: schema.string(), + retryAt: schema.number(), + }); + type ParamsType = TypeOf; + const result: ActionType<{}, {}, ParamsType> = { id: 'test.rate-limit', name: 'Test: Rate Limit', minimumLicenseRequired: 'gold', maxAttempts: 2, validate: { - params: schema.object({ - index: schema.string(), - reference: schema.string(), - retryAt: schema.number(), - }), + params: paramsSchema, }, - async executor({ config, params, services }: ActionTypeExecutorOptions) { + async executor({ config, params, services }) { await services.callCluster('index', { index: params.index, refresh: 'wait_for', @@ -119,20 +144,27 @@ export function defineActionTypes( }; }, }; - const authorizationActionType: ActionType = { + return result; +} + +function getAuthorizationActionType(core: CoreSetup) { + const clusterClient = core.elasticsearch.legacy.client; + const paramsSchema = schema.object({ + callClusterAuthorizationIndex: schema.string(), + savedObjectsClientType: schema.string(), + savedObjectsClientId: schema.string(), + index: schema.string(), + reference: schema.string(), + }); + type ParamsType = TypeOf; + const result: ActionType<{}, {}, ParamsType> = { id: 'test.authorization', name: 'Test: Authorization', minimumLicenseRequired: 'gold', validate: { - params: schema.object({ - callClusterAuthorizationIndex: schema.string(), - savedObjectsClientType: schema.string(), - savedObjectsClientId: schema.string(), - index: schema.string(), - reference: schema.string(), - }), + params: paramsSchema, }, - async executor({ params, services, actionId }: ActionTypeExecutorOptions) { + async executor({ params, services, actionId }) { // Call cluster let callClusterSuccess = false; let callClusterError; @@ -200,10 +232,5 @@ export function defineActionTypes( }; }, }; - actions.registerType(noopActionType); - actions.registerType(throwActionType); - actions.registerType(indexRecordActionType); - actions.registerType(failingActionType); - actions.registerType(rateLimitedActionType); - actions.registerType(authorizationActionType); + return result; }