Skip to content

Commit

Permalink
[ResponseOps] Prepare the connector create HTTP API for versioning (#…
Browse files Browse the repository at this point in the history
…194879)

Towards elastic/response-ops-team#125

## Summary

Preparing the `POST ${BASE_ACTION_API_PATH}/connector/{id?}` HTTP API
for versioning
  • Loading branch information
doakalexi authored Oct 23, 2024
1 parent ab021a5 commit 5adb0ea
Show file tree
Hide file tree
Showing 24 changed files with 427 additions and 296 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/actions/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export * from './mustache_template';
export * from './validate_email_addresses';
export * from './connector_feature_config';
export * from './execution_log_types';
export * from './validate_empty_strings';

export const BASE_ACTION_API_PATH = '/api/actions';
export const INTERNAL_BASE_ACTION_API_PATH = '/internal/actions';
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
createConnectorRequestParamsSchema,
createConnectorRequestBodySchema,
} from './schemas/latest';
export type { CreateConnectorRequestParams, CreateConnectorRequestBody } from './types/latest';

export {
createConnectorRequestParamsSchema as createConnectorRequestParamsSchemaV1,
createConnectorRequestBodySchema as createConnectorRequestBodySchemaV1,
} from './schemas/v1';
export type {
CreateConnectorRequestParams as CreateConnectorRequestParamsV1,
CreateConnectorRequestBody as CreateConnectorRequestBodyV1,
} from './types/v1';
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
* 2.0.
*/

export { transformGetConnectorResponse } from './v1';
export * from './v1';
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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';
import { validateEmptyStrings } from '../../../../../validate_empty_strings';

export const createConnectorRequestParamsSchema = schema.maybe(
schema.object({
id: schema.maybe(schema.string({ meta: { description: 'An identifier for the connector.' } })),
})
);

export const createConnectorRequestBodySchema = schema.object({
name: schema.string({
validate: validateEmptyStrings,
meta: { description: 'The display name for the connector.' },
}),
connector_type_id: schema.string({
validate: validateEmptyStrings,
meta: { description: 'The type of connector.' },
}),
config: schema.recordOf(schema.string(), schema.any({ validate: validateEmptyStrings }), {
defaultValue: {},
}),
secrets: schema.recordOf(schema.string(), schema.any({ validate: validateEmptyStrings }), {
defaultValue: {},
}),
});
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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 { createConnectorRequestParamsSchemaV1, createConnectorRequestBodySchemaV1 } from '..';

export type CreateConnectorRequestParams = TypeOf<typeof createConnectorRequestParamsSchemaV1>;
export type CreateConnectorRequestBody = TypeOf<typeof createConnectorRequestBodySchemaV1>;
176 changes: 6 additions & 170 deletions x-pack/plugins/actions/server/actions_client/actions_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ import { compact, uniq } from 'lodash';
import {
IScopedClusterClient,
SavedObjectsClientContract,
SavedObjectAttributes,
KibanaRequest,
SavedObjectsUtils,
Logger,
} from '@kbn/core/server';
import { AuditLogger } from '@kbn/security-plugin/server';
Expand All @@ -29,22 +27,15 @@ 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 { create } from '../application/connector/methods/create';
import { execute } from '../application/connector/methods/execute';
import {
GetGlobalExecutionKPIParams,
GetGlobalExecutionLogParams,
IExecutionLogResult,
} from '../../common';
import { ActionTypeRegistry } from '../action_type_registry';
import {
validateConfig,
validateSecrets,
ActionExecutorContract,
validateConnector,
ActionExecutionSource,
parseDate,
tryCatch,
} from '../lib';
import { ActionExecutorContract, ActionExecutionSource, parseDate } from '../lib';
import {
ActionResult,
RawAction,
Expand Down Expand Up @@ -94,20 +85,11 @@ import {
import { connectorFromSavedObject, isConnectorDeprecated } from '../application/connector/lib';
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 { ConnectorCreateParams } from '../application/connector/methods/create/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;
}

export interface CreateOptions {
action: Action;
options?: { id?: string };
}

export interface ConstructorOptions {
logger: Logger;
kibanaIndices: string[];
Expand Down Expand Up @@ -187,156 +169,10 @@ export class ActionsClient {
* Create an action
*/
public async create({
action: { actionTypeId, name, config, secrets },
action,
options,
}: CreateOptions): Promise<ActionResult> {
const id = options?.id || SavedObjectsUtils.generateId();

try {
await this.context.authorization.ensureAuthorized({
operation: 'create',
actionTypeId,
});
} catch (error) {
this.context.auditLogger?.log(
connectorAuditEvent({
action: ConnectorAuditAction.CREATE,
savedObject: { type: 'action', id },
error,
})
);
throw error;
}

const foundInMemoryConnector = this.context.inMemoryConnectors.find(
(connector) => connector.id === id
);

if (
this.context.actionTypeRegistry.isSystemActionType(actionTypeId) ||
foundInMemoryConnector?.isSystemAction
) {
throw Boom.badRequest(
i18n.translate('xpack.actions.serverSideErrors.systemActionCreationForbidden', {
defaultMessage: 'System action creation is forbidden. Action type: {actionTypeId}.',
values: {
actionTypeId,
},
})
);
}

if (foundInMemoryConnector?.isPreconfigured) {
throw Boom.badRequest(
i18n.translate('xpack.actions.serverSideErrors.predefinedIdConnectorAlreadyExists', {
defaultMessage: 'This {id} already exists in a preconfigured action.',
values: {
id,
},
})
);
}

const actionType = this.context.actionTypeRegistry.get(actionTypeId);
const configurationUtilities = this.context.actionTypeRegistry.getUtils();
const validatedActionTypeConfig = validateConfig(actionType, config, {
configurationUtilities,
});
const validatedActionTypeSecrets = validateSecrets(actionType, secrets, {
configurationUtilities,
});
if (actionType.validate?.connector) {
validateConnector(actionType, { config, secrets });
}
this.context.actionTypeRegistry.ensureActionTypeEnabled(actionTypeId);

const hookServices: HookServices = {
scopedClusterClient: this.context.scopedClusterClient,
};

if (actionType.preSaveHook) {
try {
await actionType.preSaveHook({
connectorId: id,
config,
secrets,
logger: this.context.logger,
request: this.context.request,
services: hookServices,
isUpdate: false,
});
} catch (error) {
this.context.auditLogger?.log(
connectorAuditEvent({
action: ConnectorAuditAction.CREATE,
savedObject: { type: 'action', id },
error,
})
);
throw error;
}
}

this.context.auditLogger?.log(
connectorAuditEvent({
action: ConnectorAuditAction.CREATE,
savedObject: { type: 'action', id },
outcome: 'unknown',
})
);

const result = await tryCatch(
async () =>
await this.context.unsecuredSavedObjectsClient.create(
'action',
{
actionTypeId,
name,
isMissingSecrets: false,
config: validatedActionTypeConfig as SavedObjectAttributes,
secrets: validatedActionTypeSecrets as SavedObjectAttributes,
},
{ id }
)
);

const wasSuccessful = !(result instanceof Error);
const label = `connectorId: "${id}"; type: ${actionTypeId}`;
const tags = ['post-save-hook', id];

if (actionType.postSaveHook) {
try {
await actionType.postSaveHook({
connectorId: id,
config,
secrets,
logger: this.context.logger,
request: this.context.request,
services: hookServices,
isUpdate: false,
wasSuccessful,
});
} catch (err) {
this.context.logger.error(`postSaveHook create error for ${label}: ${err.message}`, {
tags,
});
}
}

if (!wasSuccessful) {
throw result;
}

return {
id: result.id,
actionTypeId: result.attributes.actionTypeId,
isMissingSecrets: result.attributes.isMissingSecrets,
name: result.attributes.name,
config: result.attributes.config,
isPreconfigured: false,
isSystemAction: false,
isDeprecated: isConnectorDeprecated(result.attributes),
};
}: Omit<ConnectorCreateParams, 'context'>): Promise<ActionResult> {
return create({ context: this.context, action, options });
}

/**
Expand Down
Loading

0 comments on commit 5adb0ea

Please sign in to comment.