[8.x] [ResponseOps] Prepare the connector `execute` HTTP API for versioning (#194481) (#196555)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[ResponseOps] Prepare the connector `execute` HTTP API for
versioning (#194481)](https://github.com/elastic/kibana/pull/194481)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Alexi
Doak","email":"109488926+doakalexi@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-10-16T14:25:41Z","message":"[ResponseOps]
Prepare the connector `execute` HTTP API for versioning
(#194481)\n\nTowards
https://github.com/elastic/response-ops-team/issues/125\r\n\r\n##
Summary\r\n\r\nPreparing the `POST
${BASE_ACTION_API_PATH}/connector/{id}/_execute`\r\nHTTP API for
versioning\r\n\r\n---------\r\n\r\nCo-authored-by: Ying Mao
<ying.mao@elastic.co>","sha":"241a05aa68cbaa737f5ccd92c1e6f91252a3cc9b","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:ResponseOps","v9.0.0","backport:prev-minor","v8.16.0"],"title":"[ResponseOps]
Prepare the connector `execute` HTTP API for
versioning","number":194481,"url":"https://github.com/elastic/kibana/pull/194481","mergeCommit":{"message":"[ResponseOps]
Prepare the connector `execute` HTTP API for versioning
(#194481)\n\nTowards
https://github.com/elastic/response-ops-team/issues/125\r\n\r\n##
Summary\r\n\r\nPreparing the `POST
${BASE_ACTION_API_PATH}/connector/{id}/_execute`\r\nHTTP API for
versioning\r\n\r\n---------\r\n\r\nCo-authored-by: Ying Mao
<ying.mao@elastic.co>","sha":"241a05aa68cbaa737f5ccd92c1e6f91252a3cc9b"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/194481","number":194481,"mergeCommit":{"message":"[ResponseOps]
Prepare the connector `execute` HTTP API for versioning
(#194481)\n\nTowards
https://github.com/elastic/response-ops-team/issues/125\r\n\r\n##
Summary\r\n\r\nPreparing the `POST
${BASE_ACTION_API_PATH}/connector/{id}/_execute`\r\nHTTP API for
versioning\r\n\r\n---------\r\n\r\nCo-authored-by: Ying Mao
<ying.mao@elastic.co>","sha":"241a05aa68cbaa737f5ccd92c1e6f91252a3cc9b"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Alexi Doak <109488926+doakalexi@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2024-10-17 03:12:48 +11:00 committed by GitHub
parent 44eb0ca393
commit 7059fcab53
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 425 additions and 163 deletions

View file

@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export {
executeConnectorRequestParamsSchema,
executeConnectorRequestBodySchema,
} from './schemas/latest';
export type { ExecuteConnectorRequestParams, ExecuteConnectorRequestBody } from './types/latest';
export {
executeConnectorRequestParamsSchema as executeConnectorRequestParamsSchemaV1,
executeConnectorRequestBodySchema as executeConnectorRequestBodySchemaV1,
} from './schemas/v1';
export type {
ExecuteConnectorRequestParams as ExecuteConnectorRequestParamsV1,
ExecuteConnectorRequestBody as ExecuteConnectorRequestBodyV1,
} from './types/v1';

View file

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

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { schema } from '@kbn/config-schema';
export const executeConnectorRequestParamsSchema = schema.object({
id: schema.string({
meta: {
description: 'An identifier for the connector.',
},
}),
});
export const executeConnectorRequestBodySchema = schema.object({
params: schema.recordOf(schema.string(), schema.any()),
});

View file

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

View file

@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { TypeOf } from '@kbn/config-schema';
import { executeConnectorRequestParamsSchemaV1, executeConnectorRequestBodySchemaV1 } from '..';
export type ExecuteConnectorRequestParams = TypeOf<typeof executeConnectorRequestParamsSchemaV1>;
export type ExecuteConnectorRequestBody = TypeOf<typeof executeConnectorRequestBodySchemaV1>;

View file

@ -6,11 +6,16 @@
*/
// Latest
export type { ConnectorResponse, AllConnectorsResponse } from './types/latest';
export type {
ConnectorResponse,
AllConnectorsResponse,
ConnectorExecuteResponse,
} from './types/latest';
export {
connectorResponseSchema,
allConnectorsResponseSchema,
connectorTypesResponseSchema,
connectorExecuteResponseSchema,
} from './schemas/latest';
// v1
@ -18,9 +23,11 @@ export type {
ConnectorResponse as ConnectorResponseV1,
AllConnectorsResponse as AllConnectorsResponseV1,
ConnectorTypesResponse as ConnectorTypesResponseV1,
ConnectorExecuteResponse as ConnectorExecuteResponseV1,
} from './types/v1';
export {
connectorResponseSchema as connectorResponseSchemaV1,
allConnectorsResponseSchema as connectorWithExtraFindDataSchemaV1,
connectorTypesResponseSchema as connectorTypesResponseSchemaV1,
connectorExecuteResponseSchema as connectorExecuteResponseSchemaV1,
} from './schemas/v1';

View file

@ -8,3 +8,4 @@
export { connectorResponseSchema } from './v1';
export { allConnectorsResponseSchema } from './v1';
export { connectorTypesResponseSchema } from './v1';
export { connectorExecuteResponseSchema } from './v1';

View file

@ -98,3 +98,55 @@ export const connectorTypesResponseSchema = schema.object({
meta: { description: 'Indicates whether the action is a system action.' },
}),
});
export const connectorExecuteResponseSchema = schema.object({
connector_id: schema.string({
meta: {
description: 'The identifier for the connector.',
},
}),
status: schema.oneOf([schema.literal('ok'), schema.literal('error')], {
meta: {
description: 'The outcome of the connector execution.',
},
}),
message: schema.maybe(
schema.string({
meta: {
description: 'The connector execution error message.',
},
})
),
service_message: schema.maybe(
schema.string({
meta: {
description: 'An error message that contains additional details.',
},
})
),
data: schema.maybe(
schema.any({
meta: {
description: 'The connector execution data.',
},
})
),
retry: schema.maybe(
schema.nullable(
schema.oneOf([schema.boolean(), schema.string()], {
meta: {
description:
'When the status is error, identifies whether the connector execution will retry .',
},
})
)
),
errorSource: schema.maybe(
schema.oneOf([schema.literal('user'), schema.literal('framework')], {
meta: {
description:
'When the status is error, identifies whether the error is a framework error or a user error.',
},
})
),
});

View file

@ -10,6 +10,7 @@ import {
connectorResponseSchemaV1,
connectorTypesResponseSchemaV1,
allConnectorsResponseSchema,
connectorExecuteResponseSchema,
} from '..';
type ConnectorResponseSchemaType = TypeOf<typeof connectorResponseSchemaV1>;
@ -41,3 +42,14 @@ export interface ConnectorTypesResponse {
supported_feature_ids: ConnectorTypesResponseSchemaType['supported_feature_ids'];
is_system_action_type: ConnectorTypesResponseSchemaType['is_system_action_type'];
}
type ConnectorExecuteResponseSchemaType = TypeOf<typeof connectorExecuteResponseSchema>;
export interface ConnectorExecuteResponse {
connector_id: ConnectorExecuteResponseSchemaType['connector_id'];
status: ConnectorExecuteResponseSchemaType['status'];
message?: ConnectorExecuteResponseSchemaType['message'];
service_message?: ConnectorExecuteResponseSchemaType['service_message'];
data?: ConnectorExecuteResponseSchemaType['data'];
retry?: ConnectorExecuteResponseSchemaType['retry'];
errorSource?: ConnectorExecuteResponseSchemaType['errorSource'];
}

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import { v4 as uuidv4 } from 'uuid';
import Boom from '@hapi/boom';
import url from 'url';
import { UsageCounter } from '@kbn/usage-collection-plugin/server';
@ -30,6 +29,7 @@ import { get } from '../application/connector/methods/get';
import { getAll, getAllSystemConnectors } from '../application/connector/methods/get_all';
import { update } from '../application/connector/methods/update';
import { listTypes } from '../application/connector/methods/list_types';
import { execute } from '../application/connector/methods/execute';
import {
GetGlobalExecutionKPIParams,
GetGlobalExecutionLogParams,
@ -54,7 +54,6 @@ import {
HookServices,
} from '../types';
import { PreconfiguredActionDisabledModificationError } from '../lib/errors/preconfigured_action_disabled_modification';
import { ExecuteOptions } from '../lib/action_executor';
import {
ExecutionEnqueuer,
ExecuteOptions as EnqueueExecutionOptions,
@ -96,6 +95,9 @@ import { connectorFromSavedObject, isConnectorDeprecated } from '../application/
import { ListTypesParams } from '../application/connector/methods/list_types/types';
import { ConnectorUpdateParams } from '../application/connector/methods/update/types';
import { ConnectorUpdate } from '../application/connector/methods/update/types/types';
import { isPreconfigured } from '../lib/is_preconfigured';
import { isSystemAction } from '../lib/is_system_action';
import { ConnectorExecuteParams } from '../application/connector/methods/execute/types';
interface Action extends ConnectorUpdate {
actionTypeId: string;
@ -649,75 +651,10 @@ export class ActionsClient {
return result;
}
private getSystemActionKibanaPrivileges(connectorId: string, params?: ExecuteOptions['params']) {
const inMemoryConnector = this.context.inMemoryConnectors.find(
(connector) => connector.id === connectorId
);
const additionalPrivileges = inMemoryConnector?.isSystemAction
? this.context.actionTypeRegistry.getSystemActionKibanaPrivileges(
inMemoryConnector.actionTypeId,
params
)
: [];
return additionalPrivileges;
}
public async execute({
actionId,
params,
source,
relatedSavedObjects,
}: Omit<ExecuteOptions, 'request' | 'actionExecutionId'>): Promise<
ActionTypeExecutorResult<unknown>
> {
const log = this.context.logger;
if (
(await getAuthorizationModeBySource(this.context.unsecuredSavedObjectsClient, source)) ===
AuthorizationMode.RBAC
) {
const additionalPrivileges = this.getSystemActionKibanaPrivileges(actionId, params);
let actionTypeId: string | undefined;
try {
if (this.isPreconfigured(actionId) || this.isSystemAction(actionId)) {
const connector = this.context.inMemoryConnectors.find(
(inMemoryConnector) => inMemoryConnector.id === actionId
);
actionTypeId = connector?.actionTypeId;
} else {
// TODO: Optimize so we don't do another get on top of getAuthorizationModeBySource and within the actionExecutor.execute
const { attributes } = await this.context.unsecuredSavedObjectsClient.get<RawAction>(
'action',
actionId
);
actionTypeId = attributes.actionTypeId;
}
} catch (err) {
log.debug(`Failed to retrieve actionTypeId for action [${actionId}]`, err);
}
await this.context.authorization.ensureAuthorized({
operation: 'execute',
additionalPrivileges,
actionTypeId,
});
} else {
trackLegacyRBACExemption('execute', this.context.usageCounter);
}
return this.context.actionExecutor.execute({
actionId,
params,
source,
request: this.context.request,
relatedSavedObjects,
actionExecutionId: uuidv4(),
});
public async execute(
connectorExecuteParams: ConnectorExecuteParams
): Promise<ActionTypeExecutorResult<unknown>> {
return execute(this.context, connectorExecuteParams);
}
public async bulkEnqueueExecution(
@ -789,15 +726,11 @@ export class ActionsClient {
}
public isPreconfigured(connectorId: string): boolean {
return !!this.context.inMemoryConnectors.find(
(connector) => connector.isPreconfigured && connector.id === connectorId
);
return isPreconfigured(this.context, connectorId);
}
public isSystemAction(connectorId: string): boolean {
return !!this.context.inMemoryConnectors.find(
(connector) => connector.isSystemAction && connector.id === connectorId
);
return isSystemAction(this.context, connectorId);
}
public async getGlobalExecutionLogWithAuth({

View file

@ -0,0 +1,73 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { v4 as uuidv4 } from 'uuid';
import { RawAction, ActionTypeExecutorResult } from '../../../../types';
import { getSystemActionKibanaPrivileges } from '../../../../lib/get_system_action_kibana_privileges';
import { isPreconfigured } from '../../../../lib/is_preconfigured';
import { isSystemAction } from '../../../../lib/is_system_action';
import {
getAuthorizationModeBySource,
AuthorizationMode,
} from '../../../../authorization/get_authorization_mode_by_source';
import { trackLegacyRBACExemption } from '../../../../lib/track_legacy_rbac_exemption';
import { ConnectorExecuteParams } from './types';
import { ACTION_SAVED_OBJECT_TYPE } from '../../../../constants/saved_objects';
import { ActionsClientContext } from '../../../../actions_client';
export async function execute(
context: ActionsClientContext,
connectorExecuteParams: ConnectorExecuteParams
): Promise<ActionTypeExecutorResult<unknown>> {
const log = context.logger;
const { actionId, params, source, relatedSavedObjects } = connectorExecuteParams;
if (
(await getAuthorizationModeBySource(context.unsecuredSavedObjectsClient, source)) ===
AuthorizationMode.RBAC
) {
const additionalPrivileges = getSystemActionKibanaPrivileges(context, actionId, params);
let actionTypeId: string | undefined;
try {
if (isPreconfigured(context, actionId) || isSystemAction(context, actionId)) {
const connector = context.inMemoryConnectors.find(
(inMemoryConnector) => inMemoryConnector.id === actionId
);
actionTypeId = connector?.actionTypeId;
} else {
// TODO: Optimize so we don't do another get on top of getAuthorizationModeBySource and within the actionExecutor.execute
const { attributes } = await context.unsecuredSavedObjectsClient.get<RawAction>(
ACTION_SAVED_OBJECT_TYPE,
actionId
);
actionTypeId = attributes.actionTypeId;
}
} catch (err) {
log.debug(`Failed to retrieve actionTypeId for action [${actionId}]`, err);
}
await context.authorization.ensureAuthorized({
operation: 'execute',
additionalPrivileges,
actionTypeId,
});
} else {
trackLegacyRBACExemption('execute', context.usageCounter);
}
return context.actionExecutor.execute({
actionId,
params,
source,
request: context.request,
relatedSavedObjects,
actionExecutionId: uuidv4(),
});
}

View file

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

View file

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

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ExecuteOptions } from '../../../../../lib/action_executor';
export type ConnectorExecuteParams = Omit<ExecuteOptions, 'request' | 'actionExecutionId'>;

View file

@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ActionsClientContext } from '../actions_client';
import { ExecuteOptions } from './action_executor';
export function getSystemActionKibanaPrivileges(
context: ActionsClientContext,
connectorId: string,
params?: ExecuteOptions['params']
) {
const inMemoryConnector = context.inMemoryConnectors.find(
(connector) => connector.id === connectorId
);
const additionalPrivileges = inMemoryConnector?.isSystemAction
? context.actionTypeRegistry.getSystemActionKibanaPrivileges(
inMemoryConnector.actionTypeId,
params
)
: [];
return additionalPrivileges;
}

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ActionsClientContext } from '../actions_client';
export function isPreconfigured(context: ActionsClientContext, connectorId: string): boolean {
return !!context.inMemoryConnectors.find(
(connector) => connector.isPreconfigured && connector.id === connectorId
);
}

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ActionsClientContext } from '../actions_client';
export function isSystemAction(context: ActionsClientContext, connectorId: string): boolean {
return !!context.inMemoryConnectors.find(
(connector) => connector.isSystemAction && connector.id === connectorId
);
}

View file

@ -5,16 +5,16 @@
* 2.0.
*/
import { executeActionRoute } from './execute';
import { executeConnectorRoute } from './execute';
import { httpServiceMock } from '@kbn/core/server/mocks';
import { licenseStateMock } from '../lib/license_state.mock';
import { mockHandlerArguments } from './legacy/_mock_handler_arguments';
import { asHttpRequestExecutionSource } from '../lib';
import { actionsClientMock } from '../actions_client/actions_client.mock';
import { ActionTypeExecutorResult } from '../types';
import { verifyAccessAndContext } from './verify_access_and_context';
import { licenseStateMock } from '../../../lib/license_state.mock';
import { mockHandlerArguments } from '../../legacy/_mock_handler_arguments';
import { asHttpRequestExecutionSource } from '../../../lib';
import { actionsClientMock } from '../../../actions_client/actions_client.mock';
import { ActionTypeExecutorResult } from '../../../types';
import { verifyAccessAndContext } from '../../verify_access_and_context';
jest.mock('./verify_access_and_context', () => ({
jest.mock('../../verify_access_and_context', () => ({
verifyAccessAndContext: jest.fn(),
}));
@ -23,7 +23,7 @@ beforeEach(() => {
(verifyAccessAndContext as jest.Mock).mockImplementation((license, handler) => handler);
});
describe('executeActionRoute', () => {
describe('executeConnectorRoute', () => {
beforeEach(() => {
jest.clearAllMocks();
});
@ -55,7 +55,7 @@ describe('executeActionRoute', () => {
status: 'ok',
};
executeActionRoute(router, licenseState);
executeConnectorRoute(router, licenseState);
const [config, handler] = router.post.mock.calls[0];
@ -95,7 +95,7 @@ describe('executeActionRoute', () => {
['noContent']
);
executeActionRoute(router, licenseState);
executeConnectorRoute(router, licenseState);
const [, handler] = router.post.mock.calls[0];
@ -131,7 +131,7 @@ describe('executeActionRoute', () => {
['ok']
);
executeActionRoute(router, licenseState);
executeConnectorRoute(router, licenseState);
const [, handler] = router.post.mock.calls[0];
@ -163,7 +163,7 @@ describe('executeActionRoute', () => {
['ok']
);
executeActionRoute(router, licenseState);
executeConnectorRoute(router, licenseState);
const [, handler] = router.post.mock.calls[0];
@ -192,7 +192,7 @@ describe('executeActionRoute', () => {
['ok']
);
executeActionRoute(router, licenseState);
executeConnectorRoute(router, licenseState);
const [_, handler] = router.post.mock.calls[0];

View file

@ -5,37 +5,23 @@
* 2.0.
*/
import { schema } from '@kbn/config-schema';
import { IRouter } from '@kbn/core/server';
import { ILicenseState } from '../lib';
import { ILicenseState } from '../../../lib';
import { ActionTypeExecutorResult, ActionsRequestHandlerContext } from '../types';
import { BASE_ACTION_API_PATH, RewriteResponseCase } from '../../common';
import { asHttpRequestExecutionSource } from '../lib/action_execution_source';
import { verifyAccessAndContext } from './verify_access_and_context';
import { connectorResponseSchemaV1 } from '../../common/routes/connector/response';
import { ActionTypeExecutorResult, ActionsRequestHandlerContext } from '../../../types';
import { BASE_ACTION_API_PATH } from '../../../../common';
import { asHttpRequestExecutionSource } from '../../../lib/action_execution_source';
import { verifyAccessAndContext } from '../../verify_access_and_context';
import { connectorResponseSchemaV1 } from '../../../../common/routes/connector/response';
import {
executeConnectorRequestBodySchemaV1,
ExecuteConnectorRequestBodyV1,
executeConnectorRequestParamsSchemaV1,
ExecuteConnectorRequestParamsV1,
} from '../../../../common/routes/connector/apis/execute';
import { transformExecuteConnectorResponseV1 } from './transforms';
const paramSchema = schema.object({
id: schema.string({
meta: { description: 'An identifier for the connector.' },
}),
});
const bodySchema = schema.object({
params: schema.recordOf(schema.string(), schema.any()),
});
const rewriteBodyRes: RewriteResponseCase<ActionTypeExecutorResult<unknown>> = ({
actionId,
serviceMessage,
...res
}) => ({
...res,
connector_id: actionId,
...(serviceMessage ? { service_message: serviceMessage } : {}),
});
export const executeActionRoute = (
export const executeConnectorRoute = (
router: IRouter<ActionsRequestHandlerContext>,
licenseState: ILicenseState
) => {
@ -51,8 +37,8 @@ export const executeActionRoute = (
},
validate: {
request: {
body: bodySchema,
params: paramSchema,
body: executeConnectorRequestBodySchemaV1,
params: executeConnectorRequestParamsSchemaV1,
},
response: {
200: {
@ -65,8 +51,8 @@ export const executeActionRoute = (
router.handleLegacyErrors(
verifyAccessAndContext(licenseState, async function (context, req, res) {
const actionsClient = (await context.actions).getActionsClient();
const { params } = req.body;
const { id } = req.params;
const { params }: ExecuteConnectorRequestBodyV1 = req.body;
const { id }: ExecuteConnectorRequestParamsV1 = req.params;
if (actionsClient.isSystemAction(id)) {
return res.badRequest({ body: 'Execution of system action is not allowed' });
@ -81,7 +67,7 @@ export const executeActionRoute = (
return body
? res.ok({
body: rewriteBodyRes(body),
body: transformExecuteConnectorResponseV1(body),
})
: res.noContent();
})

View file

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

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { transformExecuteConnectorResponse } from './transform_connector_response/latest';
export { transformExecuteConnectorResponse as transformExecuteConnectorResponseV1 } from './transform_connector_response/v1';

View file

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

View file

@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ConnectorExecuteResponseV1 } from '../../../../../../common/routes/connector/response';
import { ActionTypeExecutorResult } from '../../../../../types';
export const transformExecuteConnectorResponse = ({
actionId,
retry,
serviceMessage,
...res
}: ActionTypeExecutorResult<unknown>): ConnectorExecuteResponseV1 => ({
...res,
connector_id: actionId,
...(retry && retry instanceof Date ? { retry: retry.toISOString() } : { retry }),
...(serviceMessage ? { service_message: serviceMessage } : {}),
});

View file

@ -15,7 +15,7 @@ import { ILicenseState } from '../lib';
import { ActionsRequestHandlerContext } from '../types';
import { createActionRoute } from './create';
import { deleteConnectorRoute } from './connector/delete';
import { executeActionRoute } from './execute';
import { executeConnectorRoute } from './connector/execute';
import { getConnectorRoute } from './connector/get';
import { updateConnectorRoute } from './connector/update';
import { getOAuthAccessToken } from './get_oauth_access_token';
@ -42,7 +42,7 @@ export function defineRoutes(opts: RouteOptions) {
getAllConnectorsRoute(router, licenseState);
updateConnectorRoute(router, licenseState);
listTypesRoute(router, licenseState);
executeActionRoute(router, licenseState);
executeConnectorRoute(router, licenseState);
getGlobalExecutionLogRoute(router, licenseState);
getGlobalExecutionKPIRoute(router, licenseState);

View file

@ -246,12 +246,12 @@ export default function casesWebhookTest({ getService }: FtrProviderContext) {
params: {},
})
.then((resp: any) => {
expect(Object.keys(resp.body)).to.eql([
'status',
expect(Object.keys(resp.body).sort()).to.eql([
'connector_id',
'errorSource',
'message',
'retry',
'errorSource',
'connector_id',
'status',
]);
expect(resp.body.connector_id).to.eql(simulatedActionId);
expect(resp.body.status).to.eql('error');

View file

@ -236,12 +236,12 @@ export default function jiraTest({ getService }: FtrProviderContext) {
params: {},
})
.then((resp: any) => {
expect(Object.keys(resp.body)).to.eql([
'status',
expect(Object.keys(resp.body).sort()).to.eql([
'connector_id',
'errorSource',
'message',
'retry',
'errorSource',
'connector_id',
'status',
]);
expect(resp.body.connector_id).to.eql(simulatedActionId);
expect(resp.body.status).to.eql('error');

View file

@ -169,12 +169,12 @@ export default function opsgenieTest({ getService }: FtrProviderContext) {
});
expect(200);
expect(Object.keys(body)).to.eql([
'status',
expect(Object.keys(body).sort()).to.eql([
'connector_id',
'errorSource',
'message',
'retry',
'errorSource',
'connector_id',
'status',
]);
expect(body.connector_id).to.eql(opsgenieActionId);
expect(body.status).to.eql('error');

View file

@ -230,12 +230,12 @@ export default function resilientTest({ getService }: FtrProviderContext) {
params: {},
})
.then((resp: any) => {
expect(Object.keys(resp.body)).to.eql([
'status',
expect(Object.keys(resp.body).sort()).to.eql([
'connector_id',
'errorSource',
'message',
'retry',
'errorSource',
'connector_id',
'status',
]);
expect(resp.body.connector_id).to.eql(resilientActionId);
expect(resp.body.status).to.eql('error');

View file

@ -416,12 +416,12 @@ export default function serviceNowITOMTest({ getService }: FtrProviderContext) {
params: {},
})
.then((resp: any) => {
expect(Object.keys(resp.body)).to.eql([
'status',
expect(Object.keys(resp.body).sort()).to.eql([
'connector_id',
'errorSource',
'message',
'retry',
'errorSource',
'connector_id',
'status',
]);
expect(resp.body.connector_id).to.eql(simulatedActionId);
expect(resp.body.status).to.eql('error');

View file

@ -452,12 +452,12 @@ export default function serviceNowITSMTest({ getService }: FtrProviderContext) {
params: {},
})
.then((resp: any) => {
expect(Object.keys(resp.body)).to.eql([
'status',
expect(Object.keys(resp.body).sort()).to.eql([
'connector_id',
'errorSource',
'message',
'retry',
'errorSource',
'connector_id',
'status',
]);
expect(resp.body.connector_id).to.eql(simulatedActionId);
expect(resp.body.status).to.eql('error');

View file

@ -465,12 +465,12 @@ export default function serviceNowSIRTest({ getService }: FtrProviderContext) {
params: {},
})
.then((resp: any) => {
expect(Object.keys(resp.body)).to.eql([
'status',
expect(Object.keys(resp.body).sort()).to.eql([
'connector_id',
'errorSource',
'message',
'retry',
'errorSource',
'connector_id',
'status',
]);
expect(resp.body.connector_id).to.eql(simulatedActionId);
expect(resp.body.status).to.eql('error');

View file

@ -327,12 +327,12 @@ export default function swimlaneTest({ getService }: FtrProviderContext) {
params: {},
})
.then((resp: any) => {
expect(Object.keys(resp.body)).to.eql([
'status',
expect(Object.keys(resp.body).sort()).to.eql([
'connector_id',
'errorSource',
'message',
'retry',
'errorSource',
'connector_id',
'status',
]);
expect(resp.body.connector_id).to.eql(simulatedActionId);
expect(resp.body.status).to.eql('error');

View file

@ -188,12 +188,12 @@ export default function tinesTest({ getService }: FtrProviderContext) {
});
expect(200);
expect(Object.keys(body)).to.eql([
'status',
expect(Object.keys(body).sort()).to.eql([
'connector_id',
'errorSource',
'message',
'retry',
'errorSource',
'connector_id',
'status',
]);
expect(body.connector_id).to.eql(tinesActionId);
expect(body.status).to.eql('error');