[Ingest Manager] Support registration of server side callbacks for Create Datasource API (#69428)
* Ingest: Expose `registerExternalCallback()` method out of Ingest server `start` lifecycle * Ingest: Add support for External Callbacks on REST `createDatasourceHandler()` * Ingest: expose DatasourceServices to Plugin start interface * Endpoint: Added Endpoint Ingest handler for Create Datasources - Also moved the temporary logic from the middleware to the handler (still temporary) Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
904be0249b
commit
a104e5ab0e
|
@ -11,6 +11,7 @@ export {
|
|||
IngestManagerSetupContract,
|
||||
IngestManagerSetupDeps,
|
||||
IngestManagerStartContract,
|
||||
ExternalCallback,
|
||||
} from './plugin';
|
||||
|
||||
export const config = {
|
||||
|
@ -42,6 +43,8 @@ export const config = {
|
|||
|
||||
export type IngestManagerConfigType = TypeOf<typeof config.schema>;
|
||||
|
||||
export { DatasourceServiceInterface } from './services/datasource';
|
||||
|
||||
export const plugin = (initializerContext: PluginInitializerContext) => {
|
||||
return new IngestManagerPlugin(initializerContext);
|
||||
};
|
||||
|
|
36
x-pack/plugins/ingest_manager/server/mocks.ts
Normal file
36
x-pack/plugins/ingest_manager/server/mocks.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { loggingSystemMock, savedObjectsServiceMock } from 'src/core/server/mocks';
|
||||
import { IngestManagerAppContext } from './plugin';
|
||||
import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks';
|
||||
import { securityMock } from '../../security/server/mocks';
|
||||
import { DatasourceServiceInterface } from './services/datasource';
|
||||
|
||||
export const createAppContextStartContractMock = (): IngestManagerAppContext => {
|
||||
return {
|
||||
encryptedSavedObjectsStart: encryptedSavedObjectsMock.createStart(),
|
||||
savedObjects: savedObjectsServiceMock.createStartContract(),
|
||||
security: securityMock.createSetup(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
isProductionMode: true,
|
||||
kibanaVersion: '8.0.0',
|
||||
};
|
||||
};
|
||||
|
||||
export const createDatasourceServiceMock = () => {
|
||||
return {
|
||||
assignPackageStream: jest.fn(),
|
||||
buildDatasourceFromPackage: jest.fn(),
|
||||
bulkCreate: jest.fn(),
|
||||
create: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
get: jest.fn(),
|
||||
getByIDs: jest.fn(),
|
||||
list: jest.fn(),
|
||||
update: jest.fn(),
|
||||
} as jest.Mocked<DatasourceServiceInterface>;
|
||||
};
|
|
@ -45,13 +45,14 @@ import {
|
|||
registerSettingsRoutes,
|
||||
registerAppRoutes,
|
||||
} from './routes';
|
||||
import { IngestManagerConfigType } from '../common';
|
||||
import { IngestManagerConfigType, NewDatasource } from '../common';
|
||||
import {
|
||||
appContextService,
|
||||
licenseService,
|
||||
ESIndexPatternSavedObjectService,
|
||||
ESIndexPatternService,
|
||||
AgentService,
|
||||
datasourceService,
|
||||
} from './services';
|
||||
import { getAgentStatusById } from './services/agents';
|
||||
import { CloudSetup } from '../../cloud/server';
|
||||
|
@ -92,12 +93,31 @@ const allSavedObjectTypes = [
|
|||
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
|
||||
];
|
||||
|
||||
/**
|
||||
* Callbacks supported by the Ingest plugin
|
||||
*/
|
||||
export type ExternalCallback = [
|
||||
'datasourceCreate',
|
||||
(newDatasource: NewDatasource) => Promise<NewDatasource>
|
||||
];
|
||||
|
||||
export type ExternalCallbacksStorage = Map<ExternalCallback[0], Set<ExternalCallback[1]>>;
|
||||
|
||||
/**
|
||||
* Describes public IngestManager plugin contract returned at the `startup` stage.
|
||||
*/
|
||||
export interface IngestManagerStartContract {
|
||||
esIndexPatternService: ESIndexPatternService;
|
||||
agentService: AgentService;
|
||||
/**
|
||||
* Services for Ingest's Datasources
|
||||
*/
|
||||
datasourceService: typeof datasourceService;
|
||||
/**
|
||||
* Register callbacks for inclusion in ingest API processing
|
||||
* @param args
|
||||
*/
|
||||
registerExternalCallback: (...args: ExternalCallback) => void;
|
||||
}
|
||||
|
||||
export class IngestManagerPlugin
|
||||
|
@ -237,6 +257,10 @@ export class IngestManagerPlugin
|
|||
agentService: {
|
||||
getAgentStatusById,
|
||||
},
|
||||
datasourceService,
|
||||
registerExternalCallback: (...args: ExternalCallback) => {
|
||||
return appContextService.addExternalCallback(...args);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { httpServerMock, httpServiceMock } from 'src/core/server/mocks';
|
||||
import { IRouter, KibanaRequest, Logger, RequestHandler, RouteConfig } from 'kibana/server';
|
||||
import { registerRoutes } from './index';
|
||||
import { DATASOURCE_API_ROUTES } from '../../../common/constants';
|
||||
import { xpackMocks } from '../../../../../mocks';
|
||||
import { appContextService } from '../../services';
|
||||
import { createAppContextStartContractMock } from '../../mocks';
|
||||
import { DatasourceServiceInterface, ExternalCallback } from '../..';
|
||||
import { CreateDatasourceRequestSchema } from '../../types/rest_spec';
|
||||
import { datasourceService } from '../../services';
|
||||
|
||||
const datasourceServiceMock = datasourceService as jest.Mocked<DatasourceServiceInterface>;
|
||||
|
||||
jest.mock('../../services/datasource', (): {
|
||||
datasourceService: jest.Mocked<DatasourceServiceInterface>;
|
||||
} => {
|
||||
return {
|
||||
datasourceService: {
|
||||
assignPackageStream: jest.fn((packageInfo, dataInputs) => Promise.resolve(dataInputs)),
|
||||
buildDatasourceFromPackage: jest.fn(),
|
||||
bulkCreate: jest.fn(),
|
||||
create: jest.fn((soClient, newData) =>
|
||||
Promise.resolve({
|
||||
...newData,
|
||||
id: '1',
|
||||
revision: 1,
|
||||
updated_at: new Date().toISOString(),
|
||||
updated_by: 'elastic',
|
||||
created_at: new Date().toISOString(),
|
||||
created_by: 'elastic',
|
||||
})
|
||||
),
|
||||
delete: jest.fn(),
|
||||
get: jest.fn(),
|
||||
getByIDs: jest.fn(),
|
||||
list: jest.fn(),
|
||||
update: jest.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../services/epm/packages', () => {
|
||||
return {
|
||||
ensureInstalledPackage: jest.fn(() => Promise.resolve()),
|
||||
getPackageInfo: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
});
|
||||
|
||||
describe('When calling datasource', () => {
|
||||
let routerMock: jest.Mocked<IRouter>;
|
||||
let routeHandler: RequestHandler<any, any, any>;
|
||||
let routeConfig: RouteConfig<any, any, any, any>;
|
||||
let context: ReturnType<typeof xpackMocks.createRequestHandlerContext>;
|
||||
let response: ReturnType<typeof httpServerMock.createResponseFactory>;
|
||||
|
||||
beforeAll(() => {
|
||||
routerMock = httpServiceMock.createRouter();
|
||||
registerRoutes(routerMock);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
appContextService.start(createAppContextStartContractMock());
|
||||
context = xpackMocks.createRequestHandlerContext();
|
||||
response = httpServerMock.createResponseFactory();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
appContextService.stop();
|
||||
});
|
||||
|
||||
describe('create api handler', () => {
|
||||
const getCreateKibanaRequest = (
|
||||
newData?: typeof CreateDatasourceRequestSchema.body
|
||||
): KibanaRequest<undefined, undefined, typeof CreateDatasourceRequestSchema.body> => {
|
||||
return httpServerMock.createKibanaRequest<
|
||||
undefined,
|
||||
undefined,
|
||||
typeof CreateDatasourceRequestSchema.body
|
||||
>({
|
||||
path: routeConfig.path,
|
||||
method: 'post',
|
||||
body: newData || {
|
||||
name: 'endpoint-1',
|
||||
description: '',
|
||||
config_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c',
|
||||
enabled: true,
|
||||
output_id: '',
|
||||
inputs: [],
|
||||
namespace: 'default',
|
||||
package: { name: 'endpoint', title: 'Elastic Endpoint', version: '0.5.0' },
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Set the routeConfig and routeHandler to the Create API
|
||||
beforeAll(() => {
|
||||
[routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) =>
|
||||
path.startsWith(DATASOURCE_API_ROUTES.CREATE_PATTERN)
|
||||
)!;
|
||||
});
|
||||
|
||||
describe('and external callbacks are registered', () => {
|
||||
const callbackCallingOrder: string[] = [];
|
||||
|
||||
// Callback one adds an input that includes a `config` property
|
||||
const callbackOne: ExternalCallback[1] = jest.fn(async (ds) => {
|
||||
callbackCallingOrder.push('one');
|
||||
const newDs = {
|
||||
...ds,
|
||||
inputs: [
|
||||
{
|
||||
type: 'endpoint',
|
||||
enabled: true,
|
||||
streams: [],
|
||||
config: {
|
||||
one: {
|
||||
value: 'inserted by callbackOne',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
return newDs;
|
||||
});
|
||||
|
||||
// Callback two adds an additional `input[0].config` property
|
||||
const callbackTwo: ExternalCallback[1] = jest.fn(async (ds) => {
|
||||
callbackCallingOrder.push('two');
|
||||
const newDs = {
|
||||
...ds,
|
||||
inputs: [
|
||||
{
|
||||
...ds.inputs[0],
|
||||
config: {
|
||||
...ds.inputs[0].config,
|
||||
two: {
|
||||
value: 'inserted by callbackTwo',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
return newDs;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
appContextService.addExternalCallback('datasourceCreate', callbackOne);
|
||||
appContextService.addExternalCallback('datasourceCreate', callbackTwo);
|
||||
});
|
||||
|
||||
afterEach(() => (callbackCallingOrder.length = 0));
|
||||
|
||||
it('should call external callbacks in expected order', async () => {
|
||||
const request = getCreateKibanaRequest();
|
||||
await routeHandler(context, request, response);
|
||||
expect(response.ok).toHaveBeenCalled();
|
||||
expect(callbackCallingOrder).toEqual(['one', 'two']);
|
||||
});
|
||||
|
||||
it('should feed datasource returned by last callback', async () => {
|
||||
const request = getCreateKibanaRequest();
|
||||
await routeHandler(context, request, response);
|
||||
expect(response.ok).toHaveBeenCalled();
|
||||
expect(callbackOne).toHaveBeenCalledWith({
|
||||
config_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c',
|
||||
description: '',
|
||||
enabled: true,
|
||||
inputs: [],
|
||||
name: 'endpoint-1',
|
||||
namespace: 'default',
|
||||
output_id: '',
|
||||
package: {
|
||||
name: 'endpoint',
|
||||
title: 'Elastic Endpoint',
|
||||
version: '0.5.0',
|
||||
},
|
||||
});
|
||||
expect(callbackTwo).toHaveBeenCalledWith({
|
||||
config_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c',
|
||||
description: '',
|
||||
enabled: true,
|
||||
inputs: [
|
||||
{
|
||||
type: 'endpoint',
|
||||
enabled: true,
|
||||
streams: [],
|
||||
config: {
|
||||
one: {
|
||||
value: 'inserted by callbackOne',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
name: 'endpoint-1',
|
||||
namespace: 'default',
|
||||
output_id: '',
|
||||
package: {
|
||||
name: 'endpoint',
|
||||
title: 'Elastic Endpoint',
|
||||
version: '0.5.0',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should create with data from callback', async () => {
|
||||
const request = getCreateKibanaRequest();
|
||||
await routeHandler(context, request, response);
|
||||
expect(response.ok).toHaveBeenCalled();
|
||||
expect(datasourceServiceMock.create.mock.calls[0][1]).toEqual({
|
||||
config_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c',
|
||||
description: '',
|
||||
enabled: true,
|
||||
inputs: [
|
||||
{
|
||||
config: {
|
||||
one: {
|
||||
value: 'inserted by callbackOne',
|
||||
},
|
||||
two: {
|
||||
value: 'inserted by callbackTwo',
|
||||
},
|
||||
},
|
||||
enabled: true,
|
||||
streams: [],
|
||||
type: 'endpoint',
|
||||
},
|
||||
],
|
||||
name: 'endpoint-1',
|
||||
namespace: 'default',
|
||||
output_id: '',
|
||||
package: {
|
||||
name: 'endpoint',
|
||||
title: 'Elastic Endpoint',
|
||||
version: '0.5.0',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('and a callback throws an exception', () => {
|
||||
const callbackThree: ExternalCallback[1] = jest.fn(async (ds) => {
|
||||
callbackCallingOrder.push('three');
|
||||
throw new Error('callbackThree threw error on purpose');
|
||||
});
|
||||
|
||||
const callbackFour: ExternalCallback[1] = jest.fn(async (ds) => {
|
||||
callbackCallingOrder.push('four');
|
||||
return {
|
||||
...ds,
|
||||
inputs: [
|
||||
{
|
||||
...ds.inputs[0],
|
||||
config: {
|
||||
...ds.inputs[0].config,
|
||||
four: {
|
||||
value: 'inserted by callbackFour',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
appContextService.addExternalCallback('datasourceCreate', callbackThree);
|
||||
appContextService.addExternalCallback('datasourceCreate', callbackFour);
|
||||
});
|
||||
|
||||
it('should skip over callback exceptions and still execute other callbacks', async () => {
|
||||
const request = getCreateKibanaRequest();
|
||||
await routeHandler(context, request, response);
|
||||
expect(response.ok).toHaveBeenCalled();
|
||||
expect(callbackCallingOrder).toEqual(['one', 'two', 'three', 'four']);
|
||||
});
|
||||
|
||||
it('should log errors', async () => {
|
||||
const errorLogger = (appContextService.getLogger() as jest.Mocked<Logger>).error;
|
||||
const request = getCreateKibanaRequest();
|
||||
await routeHandler(context, request, response);
|
||||
expect(response.ok).toHaveBeenCalled();
|
||||
expect(errorLogger.mock.calls).toEqual([
|
||||
['An external registered [datasourceCreate] callback failed when executed'],
|
||||
[new Error('callbackThree threw error on purpose')],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create datasource with last successful returned datasource', async () => {
|
||||
const request = getCreateKibanaRequest();
|
||||
await routeHandler(context, request, response);
|
||||
expect(response.ok).toHaveBeenCalled();
|
||||
expect(datasourceServiceMock.create.mock.calls[0][1]).toEqual({
|
||||
config_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c',
|
||||
description: '',
|
||||
enabled: true,
|
||||
inputs: [
|
||||
{
|
||||
config: {
|
||||
one: {
|
||||
value: 'inserted by callbackOne',
|
||||
},
|
||||
two: {
|
||||
value: 'inserted by callbackTwo',
|
||||
},
|
||||
four: {
|
||||
value: 'inserted by callbackFour',
|
||||
},
|
||||
},
|
||||
enabled: true,
|
||||
streams: [],
|
||||
type: 'endpoint',
|
||||
},
|
||||
],
|
||||
name: 'endpoint-1',
|
||||
namespace: 'default',
|
||||
output_id: '',
|
||||
package: {
|
||||
name: 'endpoint',
|
||||
title: 'Elastic Endpoint',
|
||||
version: '0.5.0',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -14,6 +14,7 @@ import {
|
|||
CreateDatasourceRequestSchema,
|
||||
UpdateDatasourceRequestSchema,
|
||||
DeleteDatasourcesRequestSchema,
|
||||
NewDatasource,
|
||||
} from '../../types';
|
||||
import { CreateDatasourceResponse, DeleteDatasourcesResponse } from '../../../common';
|
||||
|
||||
|
@ -76,23 +77,50 @@ export const createDatasourceHandler: RequestHandler<
|
|||
const soClient = context.core.savedObjects.client;
|
||||
const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser;
|
||||
const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined;
|
||||
const newData = { ...request.body };
|
||||
const logger = appContextService.getLogger();
|
||||
let newData = { ...request.body };
|
||||
try {
|
||||
// If we have external callbacks, then process those now before creating the actual datasource
|
||||
const externalCallbacks = appContextService.getExternalCallbacks('datasourceCreate');
|
||||
if (externalCallbacks && externalCallbacks.size > 0) {
|
||||
let updatedNewData: NewDatasource = newData;
|
||||
|
||||
for (const callback of externalCallbacks) {
|
||||
try {
|
||||
// ensure that the returned value by the callback passes schema validation
|
||||
updatedNewData = CreateDatasourceRequestSchema.body.validate(
|
||||
await callback(updatedNewData)
|
||||
);
|
||||
} catch (error) {
|
||||
// Log the error, but keep going and process the other callbacks
|
||||
logger.error('An external registered [datasourceCreate] callback failed when executed');
|
||||
logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// The type `NewDatasource` and the `DatasourceBaseSchema` are incompatible.
|
||||
// `NewDatasrouce` defines `namespace` as optional string, which means that `undefined` is a
|
||||
// valid value, however, the schema defines it as string with a minimum length of 1.
|
||||
// Here, we need to cast the value back to the schema type and ignore the TS error.
|
||||
// @ts-ignore
|
||||
newData = updatedNewData as typeof CreateDatasourceRequestSchema.body;
|
||||
}
|
||||
|
||||
// Make sure the datasource package is installed
|
||||
if (request.body.package?.name) {
|
||||
if (newData.package?.name) {
|
||||
await ensureInstalledPackage({
|
||||
savedObjectsClient: soClient,
|
||||
pkgName: request.body.package.name,
|
||||
pkgName: newData.package.name,
|
||||
callCluster,
|
||||
});
|
||||
const pkgInfo = await getPackageInfo({
|
||||
savedObjectsClient: soClient,
|
||||
pkgName: request.body.package.name,
|
||||
pkgVersion: request.body.package.version,
|
||||
pkgName: newData.package.name,
|
||||
pkgVersion: newData.package.version,
|
||||
});
|
||||
newData.inputs = (await datasourceService.assignPackageStream(
|
||||
pkgInfo,
|
||||
request.body.inputs
|
||||
newData.inputs
|
||||
)) as TypeOf<typeof CreateDatasourceRequestSchema.body>['inputs'];
|
||||
}
|
||||
|
||||
|
@ -103,6 +131,7 @@ export const createDatasourceHandler: RequestHandler<
|
|||
body,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
return response.customError({
|
||||
statusCode: 500,
|
||||
body: { message: e.message },
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from '../../../encrypted_saved_objects/server';
|
||||
import { SecurityPluginSetup } from '../../../security/server';
|
||||
import { IngestManagerConfigType } from '../../common';
|
||||
import { IngestManagerAppContext } from '../plugin';
|
||||
import { ExternalCallback, ExternalCallbacksStorage, IngestManagerAppContext } from '../plugin';
|
||||
import { CloudSetup } from '../../../cloud/server';
|
||||
|
||||
class AppContextService {
|
||||
|
@ -27,6 +27,7 @@ class AppContextService {
|
|||
private cloud?: CloudSetup;
|
||||
private logger: Logger | undefined;
|
||||
private httpSetup?: HttpServiceSetup;
|
||||
private externalCallbacks: ExternalCallbacksStorage = new Map();
|
||||
|
||||
public async start(appContext: IngestManagerAppContext) {
|
||||
this.encryptedSavedObjects = appContext.encryptedSavedObjectsStart?.getClient();
|
||||
|
@ -47,7 +48,9 @@ class AppContextService {
|
|||
}
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
public stop() {
|
||||
this.externalCallbacks.clear();
|
||||
}
|
||||
|
||||
public getEncryptedSavedObjects() {
|
||||
if (!this.encryptedSavedObjects) {
|
||||
|
@ -121,6 +124,19 @@ class AppContextService {
|
|||
}
|
||||
return this.kibanaVersion;
|
||||
}
|
||||
|
||||
public addExternalCallback(type: ExternalCallback[0], callback: ExternalCallback[1]) {
|
||||
if (!this.externalCallbacks.has(type)) {
|
||||
this.externalCallbacks.set(type, new Set());
|
||||
}
|
||||
this.externalCallbacks.get(type)!.add(callback);
|
||||
}
|
||||
|
||||
public getExternalCallbacks(type: ExternalCallback[0]) {
|
||||
if (this.externalCallbacks) {
|
||||
return this.externalCallbacks.get(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const appContextService = new AppContextService();
|
||||
|
|
|
@ -307,4 +307,5 @@ async function _assignPackageStreamToStream(
|
|||
return { ...stream };
|
||||
}
|
||||
|
||||
export type DatasourceServiceInterface = DatasourceService;
|
||||
export const datasourceService = new DatasourceService();
|
||||
|
|
|
@ -17,7 +17,6 @@ import {
|
|||
sendPutDatasource,
|
||||
} from '../policy_list/services/ingest';
|
||||
import { NewPolicyData, PolicyData } from '../../../../../../common/endpoint/types';
|
||||
import { factory as policyConfigFactory } from '../../../../../../common/endpoint/models/policy_config';
|
||||
import { ImmutableMiddlewareFactory } from '../../../../../common/store';
|
||||
|
||||
export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory<PolicyDetailsState> = (
|
||||
|
@ -43,23 +42,6 @@ export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory<PolicyDe
|
|||
return;
|
||||
}
|
||||
|
||||
// Until we get the Default configuration into the Endpoint package so that the datasource has
|
||||
// the expected data structure, we will add it here manually.
|
||||
if (!policyItem.inputs.length) {
|
||||
policyItem.inputs = [
|
||||
{
|
||||
type: 'endpoint',
|
||||
enabled: true,
|
||||
streams: [],
|
||||
config: {
|
||||
policy: {
|
||||
value: policyConfigFactory(),
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: 'serverReturnedPolicyDetailsData',
|
||||
payload: {
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
} from '../../../../../../../src/core/server/mocks';
|
||||
import { registerAlertRoutes } from '../routes';
|
||||
import { alertingIndexGetQuerySchema } from '../../../../common/endpoint_alerts/schema/alert_index';
|
||||
import { createMockAgentService } from '../../mocks';
|
||||
import { createMockEndpointAppContextServiceStartContract } from '../../mocks';
|
||||
import { EndpointAppContextService } from '../../endpoint_app_context_services';
|
||||
import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__';
|
||||
|
||||
|
@ -28,9 +28,7 @@ describe('test alerts route', () => {
|
|||
routerMock = httpServiceMock.createRouter();
|
||||
|
||||
endpointAppContextService = new EndpointAppContextService();
|
||||
endpointAppContextService.start({
|
||||
agentService: createMockAgentService(),
|
||||
});
|
||||
endpointAppContextService.start(createMockEndpointAppContextServiceStartContract());
|
||||
|
||||
registerAlertRoutes(routerMock, {
|
||||
logFactory: loggingSystemMock.create(),
|
||||
|
|
|
@ -3,7 +3,15 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { AgentService } from '../../../ingest_manager/server';
|
||||
import { AgentService, IngestManagerStartContract } from '../../../ingest_manager/server';
|
||||
import { handleDatasourceCreate } from './ingest_integration';
|
||||
|
||||
export type EndpointAppContextServiceStartContract = Pick<
|
||||
IngestManagerStartContract,
|
||||
'agentService'
|
||||
> & {
|
||||
registerIngestCallback: IngestManagerStartContract['registerExternalCallback'];
|
||||
};
|
||||
|
||||
/**
|
||||
* A singleton that holds shared services that are initialized during the start up phase
|
||||
|
@ -12,8 +20,9 @@ import { AgentService } from '../../../ingest_manager/server';
|
|||
export class EndpointAppContextService {
|
||||
private agentService: AgentService | undefined;
|
||||
|
||||
public start(dependencies: { agentService: AgentService }) {
|
||||
public start(dependencies: EndpointAppContextServiceStartContract) {
|
||||
this.agentService = dependencies.agentService;
|
||||
dependencies.registerIngestCallback('datasourceCreate', handleDatasourceCreate);
|
||||
}
|
||||
|
||||
public stop() {}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { factory as policyConfigFactory } from '../../common/endpoint/models/policy_config';
|
||||
import { NewPolicyData } from '../../common/endpoint/types';
|
||||
import { NewDatasource } from '../../../ingest_manager/common/types/models';
|
||||
|
||||
/**
|
||||
* Callback to handle creation of Datasources in Ingest Manager
|
||||
* @param newDatasource
|
||||
*/
|
||||
export const handleDatasourceCreate = async (
|
||||
newDatasource: NewDatasource
|
||||
): Promise<NewDatasource> => {
|
||||
// We only care about Endpoint datasources
|
||||
if (newDatasource.package?.name !== 'endpoint') {
|
||||
return newDatasource;
|
||||
}
|
||||
|
||||
// We cast the type here so that any changes to the Endpoint specific data
|
||||
// follow the types/schema expected
|
||||
let updatedDatasource = newDatasource as NewPolicyData;
|
||||
|
||||
// Until we get the Default Policy Configuration in the Endpoint package,
|
||||
// we will add it here manually at creation time.
|
||||
// @ts-ignore
|
||||
if (newDatasource.inputs.length === 0) {
|
||||
updatedDatasource = {
|
||||
...newDatasource,
|
||||
inputs: [
|
||||
{
|
||||
type: 'endpoint',
|
||||
enabled: true,
|
||||
streams: [],
|
||||
config: {
|
||||
policy: {
|
||||
value: policyConfigFactory(),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return updatedDatasource;
|
||||
};
|
|
@ -6,7 +6,28 @@
|
|||
|
||||
import { IScopedClusterClient, SavedObjectsClientContract } from 'kibana/server';
|
||||
import { xpackMocks } from '../../../../mocks';
|
||||
import { AgentService, IngestManagerStartContract } from '../../../ingest_manager/server';
|
||||
import {
|
||||
AgentService,
|
||||
IngestManagerStartContract,
|
||||
ExternalCallback,
|
||||
} from '../../../ingest_manager/server';
|
||||
import { EndpointAppContextServiceStartContract } from './endpoint_app_context_services';
|
||||
import { createDatasourceServiceMock } from '../../../ingest_manager/server/mocks';
|
||||
|
||||
/**
|
||||
* Crates a mocked input contract for the `EndpointAppContextService#start()` method
|
||||
*/
|
||||
export const createMockEndpointAppContextServiceStartContract = (): jest.Mocked<
|
||||
EndpointAppContextServiceStartContract
|
||||
> => {
|
||||
return {
|
||||
agentService: createMockAgentService(),
|
||||
registerIngestCallback: jest.fn<
|
||||
ReturnType<IngestManagerStartContract['registerExternalCallback']>,
|
||||
Parameters<IngestManagerStartContract['registerExternalCallback']>
|
||||
>(),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a mock AgentService
|
||||
|
@ -32,6 +53,8 @@ export const createMockIngestManagerStartContract = (
|
|||
getESIndexPattern: jest.fn().mockResolvedValue(indexPattern),
|
||||
},
|
||||
agentService: createMockAgentService(),
|
||||
registerExternalCallback: jest.fn((...args: ExternalCallback) => {}),
|
||||
datasourceService: createDatasourceServiceMock(),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -27,8 +27,10 @@ import {
|
|||
} from '../../../../common/endpoint/types';
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
import { registerEndpointRoutes } from './index';
|
||||
import { createMockAgentService, createRouteHandlerContext } from '../../mocks';
|
||||
import { AgentService } from '../../../../../ingest_manager/server';
|
||||
import {
|
||||
createMockEndpointAppContextServiceStartContract,
|
||||
createRouteHandlerContext,
|
||||
} from '../../mocks';
|
||||
import Boom from 'boom';
|
||||
import { EndpointAppContextService } from '../../endpoint_app_context_services';
|
||||
import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__';
|
||||
|
@ -44,7 +46,9 @@ describe('test endpoint route', () => {
|
|||
let routeHandler: RequestHandler<any, any, any>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let routeConfig: RouteConfig<any, any, any, any>;
|
||||
let mockAgentService: jest.Mocked<AgentService>;
|
||||
let mockAgentService: ReturnType<
|
||||
typeof createMockEndpointAppContextServiceStartContract
|
||||
>['agentService'];
|
||||
let endpointAppContextService: EndpointAppContextService;
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -56,11 +60,10 @@ describe('test endpoint route', () => {
|
|||
mockClusterClient.asScoped.mockReturnValue(mockScopedClient);
|
||||
routerMock = httpServiceMock.createRouter();
|
||||
mockResponse = httpServerMock.createResponseFactory();
|
||||
mockAgentService = createMockAgentService();
|
||||
endpointAppContextService = new EndpointAppContextService();
|
||||
endpointAppContextService.start({
|
||||
agentService: mockAgentService,
|
||||
});
|
||||
const startContract = createMockEndpointAppContextServiceStartContract();
|
||||
endpointAppContextService.start(startContract);
|
||||
mockAgentService = startContract.agentService;
|
||||
|
||||
registerEndpointRoutes(routerMock, {
|
||||
logFactory: loggingSystemMock.create(),
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { EndpointAppContextService } from '../../endpoint_app_context_services';
|
||||
import { createMockAgentService, createRouteHandlerContext } from '../../mocks';
|
||||
import {
|
||||
createMockEndpointAppContextServiceStartContract,
|
||||
createRouteHandlerContext,
|
||||
} from '../../mocks';
|
||||
import { getHostPolicyResponseHandler } from './handlers';
|
||||
import {
|
||||
IScopedClusterClient,
|
||||
|
@ -17,7 +20,6 @@ import {
|
|||
loggingSystemMock,
|
||||
savedObjectsClientMock,
|
||||
} from '../../../../../../../src/core/server/mocks';
|
||||
import { AgentService } from '../../../../../ingest_manager/server/services';
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
import { GetHostPolicyResponse, HostPolicyResponse } from '../../../../common/endpoint/types';
|
||||
import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data';
|
||||
|
@ -28,17 +30,13 @@ describe('test policy response handler', () => {
|
|||
let mockScopedClient: jest.Mocked<IScopedClusterClient>;
|
||||
let mockSavedObjectClient: jest.Mocked<SavedObjectsClientContract>;
|
||||
let mockResponse: jest.Mocked<KibanaResponseFactory>;
|
||||
let mockAgentService: jest.Mocked<AgentService>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockScopedClient = elasticsearchServiceMock.createScopedClusterClient();
|
||||
mockSavedObjectClient = savedObjectsClientMock.create();
|
||||
mockResponse = httpServerMock.createResponseFactory();
|
||||
endpointAppContextService = new EndpointAppContextService();
|
||||
mockAgentService = createMockAgentService();
|
||||
endpointAppContextService.start({
|
||||
agentService: mockAgentService,
|
||||
});
|
||||
endpointAppContextService.start(createMockEndpointAppContextServiceStartContract());
|
||||
});
|
||||
|
||||
afterEach(() => endpointAppContextService.stop());
|
||||
|
|
|
@ -219,7 +219,9 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
public start(core: CoreStart, plugins: StartPlugins) {
|
||||
this.endpointAppContextService.start({
|
||||
agentService: plugins.ingestManager.agentService,
|
||||
registerIngestCallback: plugins.ingestManager.registerExternalCallback,
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue