Skip to content

Commit

Permalink
[Alerting] Remove predefined connectors from rule reference array (#1…
Browse files Browse the repository at this point in the history
…09437)

* Exposing preconfigured connectors through actions setup contract

* Adding stub for migration using preconfigured connectors

* Adding isPreconfigured fn to actions client

* Updating rules client logic to not extract predefined connector ids

* Functional tests

* Adding migration

* Adding functional test for migration

* Adding functional test for migration

* Adding note to docs about referenced_by_count if is_preconfigured

* Fixing functional test

* Changing to isPreconfiguredConnector fn in actions plugin setup contract

* Update docs/api/actions-and-connectors/get_all.asciidoc

Co-authored-by: Mike Côté <mikecote@users.noreply.github.com>

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Mike Côté <mikecote@users.noreply.github.com>
  • Loading branch information
3 people authored Aug 26, 2021
1 parent 3854d3a commit a3d03ec
Show file tree
Hide file tree
Showing 20 changed files with 1,475 additions and 66 deletions.
4 changes: 3 additions & 1 deletion docs/api/actions-and-connectors/get_all.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The API returns the following:
"connector_type_id": ".email",
"name": "email: preconfigured-mail-connector",
"is_preconfigured": true,
"referenced_by_count": 1
"referenced_by_count": 0 <1>
},
{
"id": "c55b6eb0-6bad-11eb-9f3b-611eebc6c3ad",
Expand All @@ -61,3 +61,5 @@ The API returns the following:
}
]
--------------------------------------------------

<1> `referenced_by_count` - The number of saved-objects referencing this connector. This value is not calculated if `is_preconfigured: true`.
1 change: 1 addition & 0 deletions x-pack/plugins/actions/server/actions_client.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const createActionsClientMock = () => {
ephemeralEnqueuedExecution: jest.fn(),
listTypes: jest.fn(),
isActionTypeEnabled: jest.fn(),
isPreconfigured: jest.fn(),
};
return mocked;
};
Expand Down
62 changes: 62 additions & 0 deletions x-pack/plugins/actions/server/actions_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1823,3 +1823,65 @@ describe('isActionTypeEnabled()', () => {
});
});
});

describe('isPreconfigured()', () => {
test('should return true if connector id is in list of preconfigured connectors', () => {
actionsClient = new ActionsClient({
actionTypeRegistry,
unsecuredSavedObjectsClient,
scopedClusterClient,
defaultKibanaIndex,
actionExecutor,
executionEnqueuer,
ephemeralExecutionEnqueuer,
request,
authorization: (authorization as unknown) as ActionsAuthorization,
preconfiguredActions: [
{
id: 'testPreconfigured',
actionTypeId: 'my-action-type',
secrets: {
test: 'test1',
},
isPreconfigured: true,
name: 'test',
config: {
foo: 'bar',
},
},
],
});

expect(actionsClient.isPreconfigured('testPreconfigured')).toEqual(true);
});

test('should return false if connector id is not in list of preconfigured connectors', () => {
actionsClient = new ActionsClient({
actionTypeRegistry,
unsecuredSavedObjectsClient,
scopedClusterClient,
defaultKibanaIndex,
actionExecutor,
executionEnqueuer,
ephemeralExecutionEnqueuer,
request,
authorization: (authorization as unknown) as ActionsAuthorization,
preconfiguredActions: [
{
id: 'testPreconfigured',
actionTypeId: 'my-action-type',
secrets: {
test: 'test1',
},
isPreconfigured: true,
name: 'test',
config: {
foo: 'bar',
},
},
],
});

expect(actionsClient.isPreconfigured(uuid.v4())).toEqual(false);
});
});
4 changes: 4 additions & 0 deletions x-pack/plugins/actions/server/actions_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,10 @@ export class ActionsClient {
) {
return this.actionTypeRegistry.isActionTypeEnabled(actionTypeId, options);
}

public isPreconfigured(connectorId: string): boolean {
return !!this.preconfiguredActions.find((preconfigured) => preconfigured.id === connectorId);
}
}

function actionFromSavedObject(savedObject: SavedObject<RawAction>): ActionResult {
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/actions/server/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export { actionsClientMock };
const createSetupMock = () => {
const mock: jest.Mocked<PluginSetupContract> = {
registerType: jest.fn(),
isPreconfiguredConnector: jest.fn(),
};
return mock;
};
Expand Down
56 changes: 56 additions & 0 deletions x-pack/plugins/actions/server/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,62 @@ describe('Actions Plugin', () => {
});
});
});

describe('isPreconfiguredConnector', () => {
function getConfig(overrides = {}) {
return {
enabled: true,
enabledActionTypes: ['*'],
allowedHosts: ['*'],
preconfiguredAlertHistoryEsIndex: false,
preconfigured: {
preconfiguredServerLog: {
actionTypeId: '.server-log',
name: 'preconfigured-server-log',
config: {},
secrets: {},
},
},
proxyRejectUnauthorizedCertificates: true,
proxyBypassHosts: undefined,
proxyOnlyHosts: undefined,
rejectUnauthorized: true,
maxResponseContentLength: new ByteSizeValue(1000000),
responseTimeout: moment.duration('60s'),
cleanupFailedExecutionsTask: {
enabled: true,
cleanupInterval: schema.duration().validate('5m'),
idleInterval: schema.duration().validate('1h'),
pageSize: 100,
},
...overrides,
};
}

function setup(config: ActionsConfig) {
context = coreMock.createPluginInitializerContext<ActionsConfig>(config);
plugin = new ActionsPlugin(context);
coreSetup = coreMock.createSetup();
pluginsSetup = {
taskManager: taskManagerMock.createSetup(),
encryptedSavedObjects: encryptedSavedObjectsMock.createSetup(),
licensing: licensingMock.createSetup(),
eventLog: eventLogMock.createSetup(),
usageCollection: usageCollectionPluginMock.createSetupContract(),
features: featuresPluginMock.createSetup(),
};
}

it('should correctly return whether connector is preconfigured', async () => {
setup(getConfig());
// coreMock.createSetup doesn't support Plugin generics
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const pluginSetup = await plugin.setup(coreSetup as any, pluginsSetup);

expect(pluginSetup.isPreconfiguredConnector('preconfiguredServerLog')).toEqual(true);
expect(pluginSetup.isPreconfiguredConnector('anotherConnectorId')).toEqual(false);
});
});
});

describe('start()', () => {
Expand Down
6 changes: 6 additions & 0 deletions x-pack/plugins/actions/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export interface PluginSetupContract {
>(
actionType: ActionType<Config, Secrets, Params, ExecutorResultData>
): void;
isPreconfiguredConnector(connectorId: string): boolean;
}

export interface PluginStartContract {
Expand Down Expand Up @@ -289,6 +290,11 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
ensureSufficientLicense(actionType);
actionTypeRegistry.register(actionType);
},
isPreconfiguredConnector: (connectorId: string): boolean => {
return !!this.preconfiguredActions.find(
(preconfigured) => preconfigured.id === connectorId
);
},
};
}

Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/alerting/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,8 @@ export class AlertingPlugin {
core.savedObjects,
plugins.encryptedSavedObjects,
this.ruleTypeRegistry,
this.logger
this.logger,
plugins.actions.isPreconfiguredConnector
);

initializeApiKeyInvalidator(
Expand Down
40 changes: 29 additions & 11 deletions x-pack/plugins/alerting/server/rules_client/rules_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,9 @@ export interface GetAlertInstanceSummaryParams {
// NOTE: Changing this prefix will require a migration to update the prefix in all existing `rule` saved objects
const extractedSavedObjectParamReferenceNamePrefix = 'param:';

// NOTE: Changing this prefix will require a migration to update the prefix in all existing `rule` saved objects
const preconfiguredConnectorActionRefPrefix = 'preconfigured:';

const alertingAuthorizationFilterOpts: AlertingAuthorizationFilterOpts = {
type: AlertingAuthorizationFilterType.KQL,
fieldNames: { ruleTypeId: 'alert.attributes.alertTypeId', consumer: 'alert.attributes.consumer' },
Expand Down Expand Up @@ -1508,6 +1511,13 @@ export class RulesClient {
references: SavedObjectReference[]
) {
return actions.map((action) => {
if (action.actionRef.startsWith(preconfiguredConnectorActionRefPrefix)) {
return {
...omit(action, 'actionRef'),
id: action.actionRef.replace(preconfiguredConnectorActionRefPrefix, ''),
};
}

const reference = references.find((ref) => ref.name === action.actionRef);
if (!reference) {
throw new Error(`Action reference "${action.actionRef}" not found in alert id: ${alertId}`);
Expand Down Expand Up @@ -1700,17 +1710,25 @@ export class RulesClient {
alertActions.forEach(({ id, ...alertAction }, i) => {
const actionResultValue = actionResults.find((action) => action.id === id);
if (actionResultValue) {
const actionRef = `action_${i}`;
references.push({
id,
name: actionRef,
type: 'action',
});
actions.push({
...alertAction,
actionRef,
actionTypeId: actionResultValue.actionTypeId,
});
if (actionsClient.isPreconfigured(id)) {
actions.push({
...alertAction,
actionRef: `${preconfiguredConnectorActionRefPrefix}${id}`,
actionTypeId: actionResultValue.actionTypeId,
});
} else {
const actionRef = `action_${i}`;
references.push({
id,
name: actionRef,
type: 'action',
});
actions.push({
...alertAction,
actionRef,
actionTypeId: actionResultValue.actionTypeId,
});
}
} else {
actions.push({
...alertAction,
Expand Down
Loading

0 comments on commit a3d03ec

Please sign in to comment.