mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Notify the response ops when there is change on connector config (#175981)
Resolves: #175018 This Pr adds an integration test to check the changes on connectorTypes config, secrets and params schemas. I used `validate.schema` field as all the connector types have it. ConnectorTypes has config, secrets and params schemas on `validate.schema` whereas SubActionConnectorTypes has only config and secrets. They have multiple params schema as well but only registered and used during action execution. e.g. https://github.com/ersin-erdal/kibana/blob/main/x-pack/plugins/stack_connectors/server/connector_types/bedrock/bedrock.ts#L57 And here is the explanation why they are not listed in a definition: https://github.com/ersin-erdal/kibana/blob/main/x-pack/plugins/actions/server/sub_action_framework/validators.ts#L38 We need to do some refactoring to list those schemas on the connector types. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
68d6ab2135
commit
9488c93b3d
10 changed files with 11400 additions and 14 deletions
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_integration_node',
|
||||
preset: '@kbn/test/jest_integration',
|
||||
rootDir: '../../..',
|
||||
roots: ['<rootDir>/x-pack/plugins/actions'],
|
||||
};
|
||||
|
|
11170
x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap
generated
Normal file
11170
x-pack/plugins/actions/server/integration_tests/__snapshots__/connector_types.test.ts.snap
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 { TestElasticsearchUtils, TestKibanaUtils } from '@kbn/core-test-helpers-kbn-server';
|
||||
import { ActionTypeRegistry } from '../action_type_registry';
|
||||
import { setupTestServers } from './lib';
|
||||
import { connectorTypes } from './mocks/connector_types';
|
||||
|
||||
jest.mock('../action_type_registry', () => {
|
||||
const actual = jest.requireActual('../action_type_registry');
|
||||
return {
|
||||
...actual,
|
||||
ActionTypeRegistry: jest.fn().mockImplementation((opts) => {
|
||||
return new actual.ActionTypeRegistry(opts);
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Connector type config checks', () => {
|
||||
let esServer: TestElasticsearchUtils;
|
||||
let kibanaServer: TestKibanaUtils;
|
||||
let actionTypeRegistry: ActionTypeRegistry;
|
||||
|
||||
beforeAll(async () => {
|
||||
const setupResult = await setupTestServers();
|
||||
esServer = setupResult.esServer;
|
||||
kibanaServer = setupResult.kibanaServer;
|
||||
|
||||
const mockedActionTypeRegistry = jest.requireMock('../action_type_registry');
|
||||
expect(mockedActionTypeRegistry.ActionTypeRegistry).toHaveBeenCalledTimes(1);
|
||||
actionTypeRegistry = mockedActionTypeRegistry.ActionTypeRegistry.mock.results[0].value;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (kibanaServer) {
|
||||
await kibanaServer.stop();
|
||||
}
|
||||
if (esServer) {
|
||||
await esServer.stop();
|
||||
}
|
||||
});
|
||||
|
||||
test('ensure connector types list up to date', () => {
|
||||
expect(connectorTypes).toEqual(actionTypeRegistry.getAllTypes());
|
||||
});
|
||||
|
||||
for (const connectorTypeId of connectorTypes) {
|
||||
test(`detect connector type changes for: ${connectorTypeId}`, async () => {
|
||||
const connectorType = actionTypeRegistry.get(connectorTypeId);
|
||||
|
||||
expect(connectorType?.validate.config.schema.getSchema!().describe()).toMatchSnapshot();
|
||||
expect(connectorType.validate.secrets.schema.getSchema!().describe()).toMatchSnapshot();
|
||||
expect(connectorType.validate.params.schema.getSchema!().describe()).toMatchSnapshot();
|
||||
});
|
||||
}
|
||||
});
|
|
@ -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 { setupTestServers } from './setup_test_servers';
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 { createTestServers, createRootWithCorePlugins } from '@kbn/core-test-helpers-kbn-server';
|
||||
|
||||
export async function setupTestServers(settings = {}) {
|
||||
const { startES } = createTestServers({
|
||||
adjustTimeout: (t) => jest.setTimeout(t),
|
||||
settings: {
|
||||
es: {
|
||||
license: 'trial',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const esServer = await startES();
|
||||
|
||||
const root = createRootWithCorePlugins(settings, { oss: false });
|
||||
|
||||
await root.preboot();
|
||||
const coreSetup = await root.setup();
|
||||
const coreStart = await root.start();
|
||||
|
||||
return {
|
||||
esServer,
|
||||
kibanaServer: {
|
||||
root,
|
||||
coreSetup,
|
||||
coreStart,
|
||||
stop: async () => await root.shutdown(),
|
||||
},
|
||||
};
|
||||
}
|
|
@ -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.
|
||||
*/
|
||||
|
||||
export const connectorTypes: string[] = [
|
||||
'.email',
|
||||
'.index',
|
||||
'.pagerduty',
|
||||
'.swimlane',
|
||||
'.server-log',
|
||||
'.slack',
|
||||
'.slack_api',
|
||||
'.webhook',
|
||||
'.cases-webhook',
|
||||
'.xmatters',
|
||||
'.servicenow',
|
||||
'.servicenow-sir',
|
||||
'.servicenow-itom',
|
||||
'.jira',
|
||||
'.resilient',
|
||||
'.teams',
|
||||
'.torq',
|
||||
'.opsgenie',
|
||||
'.tines',
|
||||
'.gen-ai',
|
||||
'.bedrock',
|
||||
'.d3security',
|
||||
'.sentinelone',
|
||||
];
|
|
@ -16,6 +16,7 @@ import {
|
|||
SavedObjectReference,
|
||||
Logger,
|
||||
} from '@kbn/core/server';
|
||||
import { AnySchema } from 'joi';
|
||||
import { ActionTypeRegistry } from './action_type_registry';
|
||||
import { PluginSetupContract, PluginStartContract } from './plugin';
|
||||
import { ActionsClient } from './actions_client';
|
||||
|
@ -101,11 +102,12 @@ export type ExecutorType<
|
|||
options: ActionTypeExecutorOptions<Config, Secrets, Params>
|
||||
) => Promise<ActionTypeExecutorResult<ResultData>>;
|
||||
|
||||
export interface ValidatorType<Type> {
|
||||
export interface ValidatorType<T> {
|
||||
schema: {
|
||||
validate(value: unknown): Type;
|
||||
validate(value: unknown): T;
|
||||
getSchema?: () => AnySchema;
|
||||
};
|
||||
customValidator?: (value: Type, validatorServices: ValidatorServices) => void;
|
||||
customValidator?: (value: T, validatorServices: ValidatorServices) => void;
|
||||
}
|
||||
|
||||
export interface ValidatorServices {
|
||||
|
|
|
@ -44,7 +44,8 @@
|
|||
"@kbn/core-elasticsearch-server-mocks",
|
||||
"@kbn/core-logging-server-mocks",
|
||||
"@kbn/serverless",
|
||||
"@kbn/actions-types"
|
||||
"@kbn/actions-types",
|
||||
"@kbn/core-test-helpers-kbn-server"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server';
|
||||
import { RuleTaskState } from '../../types';
|
||||
import { taskInstanceToAlertTaskInstance } from '../../task_runner/alert_task_instance';
|
||||
import { ReadOperations, AlertingAuthorizationEntity } from '../../authorization';
|
||||
|
@ -18,18 +19,28 @@ export async function getAlertState(
|
|||
context: RulesClientContext,
|
||||
{ id }: GetAlertStateParams
|
||||
): Promise<RuleTaskState | void> {
|
||||
const alert = await get(context, { id });
|
||||
const rule = await get(context, { id });
|
||||
await context.authorization.ensureAuthorized({
|
||||
ruleTypeId: alert.alertTypeId,
|
||||
consumer: alert.consumer,
|
||||
ruleTypeId: rule.alertTypeId,
|
||||
consumer: rule.consumer,
|
||||
operation: ReadOperations.GetRuleState,
|
||||
entity: AlertingAuthorizationEntity.Rule,
|
||||
});
|
||||
if (alert.scheduledTaskId) {
|
||||
const { state } = taskInstanceToAlertTaskInstance(
|
||||
await context.taskManager.get(alert.scheduledTaskId),
|
||||
alert
|
||||
);
|
||||
return state;
|
||||
if (rule.scheduledTaskId) {
|
||||
try {
|
||||
const { state } = taskInstanceToAlertTaskInstance(
|
||||
await context.taskManager.get(rule.scheduledTaskId),
|
||||
rule
|
||||
);
|
||||
return state;
|
||||
} catch (e) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(e)) {
|
||||
context.logger.warn(`Task (${rule.scheduledTaskId}) not found`);
|
||||
} else {
|
||||
context.logger.warn(
|
||||
`An error occurred when getting the task state for (${rule.scheduledTaskId})`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import { AlertingAuthorization } from '../../authorization/alerting_authorizatio
|
|||
import { ActionsAuthorization } from '@kbn/actions-plugin/server';
|
||||
import { getBeforeSetup } from './lib';
|
||||
import { RULE_SAVED_OBJECT_TYPE } from '../../saved_objects';
|
||||
import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server';
|
||||
|
||||
const taskManager = taskManagerMock.createStart();
|
||||
const ruleTypeRegistry = ruleTypeRegistryMock.create();
|
||||
|
@ -175,6 +176,70 @@ describe('getAlertState()', () => {
|
|||
expect(taskManager.get).toHaveBeenCalledWith(scheduledTaskId);
|
||||
});
|
||||
|
||||
test('logs a warning if the task not found', async () => {
|
||||
const rulesClient = new RulesClient(rulesClientParams);
|
||||
|
||||
const scheduledTaskId = 'task-123';
|
||||
|
||||
unsecuredSavedObjectsClient.get.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
attributes: {
|
||||
alertTypeId: '123',
|
||||
schedule: { interval: '10s' },
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
actions: [],
|
||||
enabled: true,
|
||||
scheduledTaskId,
|
||||
mutedInstanceIds: [],
|
||||
muteAll: true,
|
||||
},
|
||||
references: [],
|
||||
});
|
||||
|
||||
taskManager.get.mockRejectedValueOnce(SavedObjectsErrorHelpers.createGenericNotFoundError());
|
||||
|
||||
await rulesClient.getAlertState({ id: '1' });
|
||||
|
||||
expect(rulesClientParams.logger.warn).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClientParams.logger.warn).toHaveBeenCalledWith('Task (task-123) not found');
|
||||
});
|
||||
|
||||
test('logs a warning if the taskManager throws an error', async () => {
|
||||
const rulesClient = new RulesClient(rulesClientParams);
|
||||
|
||||
const scheduledTaskId = 'task-123';
|
||||
|
||||
unsecuredSavedObjectsClient.get.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
type: RULE_SAVED_OBJECT_TYPE,
|
||||
attributes: {
|
||||
alertTypeId: '123',
|
||||
schedule: { interval: '10s' },
|
||||
params: {
|
||||
bar: true,
|
||||
},
|
||||
actions: [],
|
||||
enabled: true,
|
||||
scheduledTaskId,
|
||||
mutedInstanceIds: [],
|
||||
muteAll: true,
|
||||
},
|
||||
references: [],
|
||||
});
|
||||
|
||||
taskManager.get.mockRejectedValueOnce(SavedObjectsErrorHelpers.createBadRequestError());
|
||||
|
||||
await rulesClient.getAlertState({ id: '1' });
|
||||
|
||||
expect(rulesClientParams.logger.warn).toHaveBeenCalledTimes(1);
|
||||
expect(rulesClientParams.logger.warn).toHaveBeenCalledWith(
|
||||
'An error occurred when getting the task state for (task-123)'
|
||||
);
|
||||
});
|
||||
|
||||
describe('authorization', () => {
|
||||
beforeEach(() => {
|
||||
unsecuredSavedObjectsClient.get.mockResolvedValueOnce({
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue