Skip to content

Commit 86fac6d

Browse files
pmuellrwayneseymour
authored andcommitted
[Alerting] fixes to allow pre-configured actions to be executed (#63432)
resolves #63162 Most of the support for pre-configured actions has already been added to Kibana, except for one small piece. The ability for them to be executed. This PR adds that support.
1 parent 5b04fa9 commit 86fac6d

File tree

12 files changed

+445
-18
lines changed

12 files changed

+445
-18
lines changed

x-pack/plugins/actions/server/create_execute_function.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ describe('execute()', () => {
2323
actionTypeRegistry: actionTypeRegistryMock.create(),
2424
getScopedSavedObjectsClient: jest.fn().mockReturnValueOnce(savedObjectsClient),
2525
isESOUsingEphemeralEncryptionKey: false,
26+
preconfiguredActions: [],
2627
});
2728
savedObjectsClient.get.mockResolvedValueOnce({
2829
id: '123',
@@ -68,6 +69,68 @@ describe('execute()', () => {
6869
});
6970
});
7071

72+
test('schedules the action with all given parameters with a preconfigured action', async () => {
73+
const executeFn = createExecuteFunction({
74+
getBasePath,
75+
taskManager: mockTaskManager,
76+
actionTypeRegistry: actionTypeRegistryMock.create(),
77+
getScopedSavedObjectsClient: jest.fn().mockReturnValueOnce(savedObjectsClient),
78+
isESOUsingEphemeralEncryptionKey: false,
79+
preconfiguredActions: [
80+
{
81+
id: '123',
82+
actionTypeId: 'mock-action-preconfigured',
83+
config: {},
84+
isPreconfigured: true,
85+
name: 'x',
86+
secrets: {},
87+
},
88+
],
89+
});
90+
savedObjectsClient.get.mockResolvedValueOnce({
91+
id: '123',
92+
type: 'action',
93+
attributes: {
94+
actionTypeId: 'mock-action',
95+
},
96+
references: [],
97+
});
98+
savedObjectsClient.create.mockResolvedValueOnce({
99+
id: '234',
100+
type: 'action_task_params',
101+
attributes: {},
102+
references: [],
103+
});
104+
await executeFn({
105+
id: '123',
106+
params: { baz: false },
107+
spaceId: 'default',
108+
apiKey: Buffer.from('123:abc').toString('base64'),
109+
});
110+
expect(mockTaskManager.schedule).toHaveBeenCalledTimes(1);
111+
expect(mockTaskManager.schedule.mock.calls[0]).toMatchInlineSnapshot(`
112+
Array [
113+
Object {
114+
"params": Object {
115+
"actionTaskParamsId": "234",
116+
"spaceId": "default",
117+
},
118+
"scope": Array [
119+
"actions",
120+
],
121+
"state": Object {},
122+
"taskType": "actions:mock-action-preconfigured",
123+
},
124+
]
125+
`);
126+
expect(savedObjectsClient.get).not.toHaveBeenCalled();
127+
expect(savedObjectsClient.create).toHaveBeenCalledWith('action_task_params', {
128+
actionId: '123',
129+
params: { baz: false },
130+
apiKey: Buffer.from('123:abc').toString('base64'),
131+
});
132+
});
133+
71134
test('uses API key when provided', async () => {
72135
const getScopedSavedObjectsClient = jest.fn().mockReturnValueOnce(savedObjectsClient);
73136
const executeFn = createExecuteFunction({
@@ -76,6 +139,7 @@ describe('execute()', () => {
76139
getScopedSavedObjectsClient,
77140
isESOUsingEphemeralEncryptionKey: false,
78141
actionTypeRegistry: actionTypeRegistryMock.create(),
142+
preconfiguredActions: [],
79143
});
80144
savedObjectsClient.get.mockResolvedValueOnce({
81145
id: '123',
@@ -125,6 +189,7 @@ describe('execute()', () => {
125189
getScopedSavedObjectsClient,
126190
isESOUsingEphemeralEncryptionKey: false,
127191
actionTypeRegistry: actionTypeRegistryMock.create(),
192+
preconfiguredActions: [],
128193
});
129194
savedObjectsClient.get.mockResolvedValueOnce({
130195
id: '123',
@@ -171,6 +236,7 @@ describe('execute()', () => {
171236
getScopedSavedObjectsClient,
172237
isESOUsingEphemeralEncryptionKey: true,
173238
actionTypeRegistry: actionTypeRegistryMock.create(),
239+
preconfiguredActions: [],
174240
});
175241
await expect(
176242
executeFn({
@@ -193,6 +259,7 @@ describe('execute()', () => {
193259
getScopedSavedObjectsClient,
194260
isESOUsingEphemeralEncryptionKey: false,
195261
actionTypeRegistry: mockedActionTypeRegistry,
262+
preconfiguredActions: [],
196263
});
197264
mockedActionTypeRegistry.ensureActionTypeEnabled.mockImplementation(() => {
198265
throw new Error('Fail');

x-pack/plugins/actions/server/create_execute_function.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@
66

77
import { SavedObjectsClientContract } from '../../../../src/core/server';
88
import { TaskManagerStartContract } from '../../task_manager/server';
9-
import { GetBasePathFunction, RawAction, ActionTypeRegistryContract } from './types';
9+
import {
10+
GetBasePathFunction,
11+
RawAction,
12+
ActionTypeRegistryContract,
13+
PreConfiguredAction,
14+
} from './types';
1015

1116
interface CreateExecuteFunctionOptions {
1217
taskManager: TaskManagerStartContract;
1318
getScopedSavedObjectsClient: (request: any) => SavedObjectsClientContract;
1419
getBasePath: GetBasePathFunction;
1520
isESOUsingEphemeralEncryptionKey: boolean;
1621
actionTypeRegistry: ActionTypeRegistryContract;
22+
preconfiguredActions: PreConfiguredAction[];
1723
}
1824

1925
export interface ExecuteOptions {
@@ -29,6 +35,7 @@ export function createExecuteFunction({
2935
actionTypeRegistry,
3036
getScopedSavedObjectsClient,
3137
isESOUsingEphemeralEncryptionKey,
38+
preconfiguredActions,
3239
}: CreateExecuteFunctionOptions) {
3340
return async function execute({ id, params, spaceId, apiKey }: ExecuteOptions) {
3441
if (isESOUsingEphemeralEncryptionKey === true) {
@@ -61,9 +68,9 @@ export function createExecuteFunction({
6168
};
6269

6370
const savedObjectsClient = getScopedSavedObjectsClient(fakeRequest);
64-
const actionSavedObject = await savedObjectsClient.get<RawAction>('action', id);
71+
const actionTypeId = await getActionTypeId(id);
6572

66-
actionTypeRegistry.ensureActionTypeEnabled(actionSavedObject.attributes.actionTypeId);
73+
actionTypeRegistry.ensureActionTypeEnabled(actionTypeId);
6774

6875
const actionTaskParamsRecord = await savedObjectsClient.create('action_task_params', {
6976
actionId: id,
@@ -72,13 +79,23 @@ export function createExecuteFunction({
7279
});
7380

7481
await taskManager.schedule({
75-
taskType: `actions:${actionSavedObject.attributes.actionTypeId}`,
82+
taskType: `actions:${actionTypeId}`,
7683
params: {
7784
spaceId,
7885
actionTaskParamsId: actionTaskParamsRecord.id,
7986
},
8087
state: {},
8188
scope: ['actions'],
8289
});
90+
91+
async function getActionTypeId(actionId: string): Promise<string> {
92+
const pcAction = preconfiguredActions.find(action => action.id === actionId);
93+
if (pcAction) {
94+
return pcAction.actionTypeId;
95+
}
96+
97+
const actionSO = await savedObjectsClient.get<RawAction>('action', actionId);
98+
return actionSO.attributes.actionTypeId;
99+
}
83100
};
84101
}

x-pack/plugins/actions/server/lib/action_executor.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ actionExecutor.initialize({
4343
actionTypeRegistry,
4444
encryptedSavedObjectsPlugin,
4545
eventLogger: eventLoggerMock.create(),
46+
preconfiguredActions: [],
4647
});
4748

4849
beforeEach(() => {
@@ -232,6 +233,7 @@ test('throws an error when passing isESOUsingEphemeralEncryptionKey with value o
232233
actionTypeRegistry,
233234
encryptedSavedObjectsPlugin,
234235
eventLogger: eventLoggerMock.create(),
236+
preconfiguredActions: [],
235237
});
236238
await expect(
237239
customActionExecutor.execute(executeParams)

x-pack/plugins/actions/server/lib/action_executor.ts

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
ActionTypeRegistryContract,
1212
GetServicesFunction,
1313
RawAction,
14+
PreConfiguredAction,
15+
Services,
1416
} from '../types';
1517
import { EncryptedSavedObjectsPluginStart } from '../../../encrypted_saved_objects/server';
1618
import { SpacesServiceSetup } from '../../../spaces/server';
@@ -24,6 +26,7 @@ export interface ActionExecutorContext {
2426
encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart;
2527
actionTypeRegistry: ActionTypeRegistryContract;
2628
eventLogger: IEventLogger;
29+
preconfiguredActions: PreConfiguredAction[];
2730
}
2831

2932
export interface ExecuteOptions {
@@ -72,28 +75,22 @@ export class ActionExecutor {
7275
encryptedSavedObjectsPlugin,
7376
actionTypeRegistry,
7477
eventLogger,
78+
preconfiguredActions,
7579
} = this.actionExecutorContext!;
7680

7781
const services = getServices(request);
7882
const spaceId = spaces && spaces.getSpaceId(request);
7983
const namespace = spaceId && spaceId !== 'default' ? { namespace: spaceId } : {};
8084

81-
// Ensure user can read the action before processing
82-
const {
83-
attributes: { actionTypeId, config, name },
84-
} = await services.savedObjectsClient.get<RawAction>('action', actionId);
85-
86-
actionTypeRegistry.ensureActionTypeEnabled(actionTypeId);
87-
88-
// Only get encrypted attributes here, the remaining attributes can be fetched in
89-
// the savedObjectsClient call
90-
const {
91-
attributes: { secrets },
92-
} = await encryptedSavedObjectsPlugin.getDecryptedAsInternalUser<RawAction>(
93-
'action',
85+
const { actionTypeId, name, config, secrets } = await getActionInfo(
86+
services,
87+
encryptedSavedObjectsPlugin,
88+
preconfiguredActions,
9489
actionId,
95-
namespace
90+
namespace.namespace
9691
);
92+
93+
actionTypeRegistry.ensureActionTypeEnabled(actionTypeId);
9794
const actionType = actionTypeRegistry.get(actionTypeId);
9895

9996
let validatedParams: Record<string, any>;
@@ -173,3 +170,50 @@ function actionErrorToMessage(result: ActionTypeExecutorResult): string {
173170

174171
return message;
175172
}
173+
174+
interface ActionInfo {
175+
actionTypeId: string;
176+
name: string;
177+
config: any;
178+
secrets: any;
179+
}
180+
181+
async function getActionInfo(
182+
services: Services,
183+
encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart,
184+
preconfiguredActions: PreConfiguredAction[],
185+
actionId: string,
186+
namespace: string | undefined
187+
): Promise<ActionInfo> {
188+
// check to see if it's a pre-configured action first
189+
const pcAction = preconfiguredActions.find(
190+
preconfiguredAction => preconfiguredAction.id === actionId
191+
);
192+
if (pcAction) {
193+
return {
194+
actionTypeId: pcAction.actionTypeId,
195+
name: pcAction.name,
196+
config: pcAction.config,
197+
secrets: pcAction.secrets,
198+
};
199+
}
200+
201+
// if not pre-configured action, should be a saved object
202+
// ensure user can read the action before processing
203+
const {
204+
attributes: { actionTypeId, config, name },
205+
} = await services.savedObjectsClient.get<RawAction>('action', actionId);
206+
207+
const {
208+
attributes: { secrets },
209+
} = await encryptedSavedObjectsPlugin.getDecryptedAsInternalUser<RawAction>('action', actionId, {
210+
namespace: namespace === 'default' ? undefined : namespace,
211+
});
212+
213+
return {
214+
actionTypeId,
215+
name,
216+
config,
217+
secrets,
218+
};
219+
}

x-pack/plugins/actions/server/lib/task_runner_factory.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const actionExecutorInitializerParams = {
6161
actionTypeRegistry,
6262
encryptedSavedObjectsPlugin: mockedEncryptedSavedObjectsPlugin,
6363
eventLogger: eventLoggerMock.create(),
64+
preconfiguredActions: [],
6465
};
6566
const taskRunnerFactoryInitializerParams = {
6667
spaceIdToNamespace,

x-pack/plugins/actions/server/plugin.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
245245
getServices: this.getServicesFactory(core.savedObjects),
246246
encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects,
247247
actionTypeRegistry: actionTypeRegistry!,
248+
preconfiguredActions,
248249
});
249250

250251
taskRunnerFactory!.initialize({
@@ -265,6 +266,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
265266
getScopedSavedObjectsClient: core.savedObjects.getScopedClient,
266267
getBasePath: this.getBasePath,
267268
isESOUsingEphemeralEncryptionKey: isESOUsingEphemeralEncryptionKey!,
269+
preconfiguredActions,
268270
}),
269271
isActionTypeEnabled: id => {
270272
return this.actionTypeRegistry!.isActionTypeEnabled(id);

x-pack/test/alerting_api_integration/common/config.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,27 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions)
100100
xyzSecret2: 'credential2',
101101
},
102102
},
103+
{
104+
id: 'preconfigured-es-index-action',
105+
actionTypeId: '.index',
106+
name: 'preconfigured_es_index_action',
107+
config: {
108+
index: 'functional-test-actions-index-preconfigured',
109+
refresh: true,
110+
executionTimeField: 'timestamp',
111+
},
112+
},
113+
{
114+
id: 'preconfigured.test.index-record',
115+
actionTypeId: 'test.index-record',
116+
name: 'Test:_Preconfigured_Index_Record',
117+
config: {
118+
unencrypted: 'ignored-but-required',
119+
},
120+
secrets: {
121+
encrypted: 'this-is-also-ignored-and-also-required',
122+
},
123+
},
103124
])}`,
104125
...disabledPlugins.map(key => `--xpack.${key}.enabled=false`),
105126
`--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'alerts')}`,

0 commit comments

Comments
 (0)