From f570c0da3762500157056cccbffd3c9e26ab2432 Mon Sep 17 00:00:00 2001 From: nnamdifrankie <56440728+nnamdifrankie@users.noreply.github.com> Date: Wed, 22 Apr 2020 08:55:41 -0400 Subject: [PATCH] [Endpoint]EMT-146: use ingest agent for status info (#63921) (#64122) [Endpoint]EMT-146: use ingest agent for status info --- x-pack/plugins/endpoint/server/mocks.ts | 17 ++- x-pack/plugins/endpoint/server/plugin.test.ts | 4 +- x-pack/plugins/endpoint/server/plugin.ts | 1 + .../server/routes/alerts/alerts.test.ts | 3 +- .../server/routes/alerts/details/handlers.ts | 8 +- .../endpoint/server/routes/metadata/index.ts | 99 ++++++++++++++---- .../server/routes/metadata/metadata.test.ts | 76 +++++++++++++- .../routes/metadata/query_builders.test.ts | 8 +- x-pack/plugins/endpoint/server/types.ts | 2 + .../ingest_manager/common/types/index.ts | 15 +++ .../plugins/ingest_manager/server/plugin.ts | 7 +- .../server/services/agents/status.test.ts | 43 ++++++++ .../server/services/agents/status.ts | 10 +- .../alerts/host_api_feature/data.json.gz | Bin 753 -> 849 bytes 14 files changed, 260 insertions(+), 33 deletions(-) create mode 100644 x-pack/plugins/ingest_manager/server/services/agents/status.test.ts diff --git a/x-pack/plugins/endpoint/server/mocks.ts b/x-pack/plugins/endpoint/server/mocks.ts index 903aa19cd88431..3881840efe9df0 100644 --- a/x-pack/plugins/endpoint/server/mocks.ts +++ b/x-pack/plugins/endpoint/server/mocks.ts @@ -4,6 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { IngestManagerSetupContract } from '../../ingest_manager/server'; +import { AgentService } from '../../ingest_manager/common/types'; + /** * Creates a mock IndexPatternRetriever for use in tests. * @@ -28,6 +31,15 @@ export const createMockMetadataIndexPatternRetriever = () => { return createMockIndexPatternRetriever(MetadataIndexPattern); }; +/** + * Creates a mock AgentService + */ +export const createMockAgentService = (): jest.Mocked => { + return { + getAgentStatusById: jest.fn(), + }; +}; + /** * Creates a mock IndexPatternService for use in tests that need to interact with the Ingest Manager's * ESIndexPatternService. @@ -35,10 +47,13 @@ export const createMockMetadataIndexPatternRetriever = () => { * @param indexPattern a string index pattern to return when called by a test * @returns the same value as `indexPattern` parameter */ -export const createMockIndexPatternService = (indexPattern: string) => { +export const createMockIngestManagerSetupContract = ( + indexPattern: string +): IngestManagerSetupContract => { return { esIndexPatternService: { getESIndexPattern: jest.fn().mockResolvedValue(indexPattern), }, + agentService: createMockAgentService(), }; }; diff --git a/x-pack/plugins/endpoint/server/plugin.test.ts b/x-pack/plugins/endpoint/server/plugin.test.ts index 8d55e64f16dcfa..c380bc5c3e3d05 100644 --- a/x-pack/plugins/endpoint/server/plugin.test.ts +++ b/x-pack/plugins/endpoint/server/plugin.test.ts @@ -7,7 +7,7 @@ import { EndpointPlugin, EndpointPluginSetupDependencies } from './plugin'; import { coreMock } from '../../../../src/core/server/mocks'; import { PluginSetupContract } from '../../features/server'; -import { createMockIndexPatternService } from './mocks'; +import { createMockIngestManagerSetupContract } from './mocks'; describe('test endpoint plugin', () => { let plugin: EndpointPlugin; @@ -31,7 +31,7 @@ describe('test endpoint plugin', () => { }; mockedEndpointPluginSetupDependencies = { features: mockedPluginSetupContract, - ingestManager: createMockIndexPatternService(''), + ingestManager: createMockIngestManagerSetupContract(''), }; }); diff --git a/x-pack/plugins/endpoint/server/plugin.ts b/x-pack/plugins/endpoint/server/plugin.ts index 6a42014e911303..ce6be5aeaf6db4 100644 --- a/x-pack/plugins/endpoint/server/plugin.ts +++ b/x-pack/plugins/endpoint/server/plugin.ts @@ -70,6 +70,7 @@ export class EndpointPlugin plugins.ingestManager.esIndexPatternService, this.initializerContext.logger ), + agentService: plugins.ingestManager.agentService, logFactory: this.initializerContext.logger, config: (): Promise => { return createConfig$(this.initializerContext) diff --git a/x-pack/plugins/endpoint/server/routes/alerts/alerts.test.ts b/x-pack/plugins/endpoint/server/routes/alerts/alerts.test.ts index 6be7b268982060..39fc2ba4c74bb2 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/alerts.test.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/alerts.test.ts @@ -12,7 +12,7 @@ import { import { registerAlertRoutes } from './index'; import { EndpointConfigSchema } from '../../config'; import { alertingIndexGetQuerySchema } from '../../../common/schema/alert_index'; -import { createMockIndexPatternRetriever } from '../../mocks'; +import { createMockAgentService, createMockIndexPatternRetriever } from '../../mocks'; describe('test alerts route', () => { let routerMock: jest.Mocked; @@ -26,6 +26,7 @@ describe('test alerts route', () => { routerMock = httpServiceMock.createRouter(); registerAlertRoutes(routerMock, { indexPatternRetriever: createMockIndexPatternRetriever('events-endpoint-*'), + agentService: createMockAgentService(), logFactory: loggingServiceMock.create(), config: () => Promise.resolve(EndpointConfigSchema.validate({})), }); diff --git a/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts b/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts index 86e9f55da56970..9055ee4110fbba 100644 --- a/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts +++ b/x-pack/plugins/endpoint/server/routes/alerts/details/handlers.ts @@ -37,7 +37,13 @@ export const alertDetailsHandlerWrapper = function( indexPattern ); - const currentHostInfo = await getHostData(ctx, response._source.host.id, indexPattern); + const currentHostInfo = await getHostData( + { + endpointAppContext, + requestHandlerContext: ctx, + }, + response._source.host.id + ); return res.ok({ body: { diff --git a/x-pack/plugins/endpoint/server/routes/metadata/index.ts b/x-pack/plugins/endpoint/server/routes/metadata/index.ts index 883bb88204fd4f..bc79b828576e04 100644 --- a/x-pack/plugins/endpoint/server/routes/metadata/index.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/index.ts @@ -4,18 +4,29 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IRouter, RequestHandlerContext } from 'kibana/server'; +import { IRouter, Logger, RequestHandlerContext } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; import { schema } from '@kbn/config-schema'; -import { kibanaRequestToMetadataListESQuery, getESQueryHostMetadataByID } from './query_builders'; +import { getESQueryHostMetadataByID, kibanaRequestToMetadataListESQuery } from './query_builders'; import { HostInfo, HostMetadata, HostResultList, HostStatus } from '../../../common/types'; import { EndpointAppContext } from '../../types'; +import { AgentStatus } from '../../../../ingest_manager/common/types/models'; interface HitSource { _source: HostMetadata; } +interface MetadataRequestContext { + requestHandlerContext: RequestHandlerContext; + endpointAppContext: EndpointAppContext; +} + +const HOST_STATUS_MAPPING = new Map([ + ['online', HostStatus.ONLINE], + ['offline', HostStatus.OFFLINE], +]); + export function registerEndpointRoutes(router: IRouter, endpointAppContext: EndpointAppContext) { router.post( { @@ -62,7 +73,12 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp 'search', queryParams )) as SearchResponse; - return res.ok({ body: mapToHostResultList(queryParams, response) }); + return res.ok({ + body: await mapToHostResultList(queryParams, response, { + endpointAppContext, + requestHandlerContext: context, + }), + }); } catch (err) { return res.internalError({ body: err }); } @@ -79,11 +95,13 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp }, async (context, req, res) => { try { - const index = await endpointAppContext.indexPatternRetriever.getMetadataIndexPattern( - context + const doc = await getHostData( + { + endpointAppContext, + requestHandlerContext: context, + }, + req.params.id ); - - const doc = await getHostData(context, req.params.id, index); if (doc) { return res.ok({ body: doc }); } @@ -96,12 +114,14 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp } export async function getHostData( - context: RequestHandlerContext, - id: string, - index: string + metadataRequestContext: MetadataRequestContext, + id: string ): Promise { + const index = await metadataRequestContext.endpointAppContext.indexPatternRetriever.getMetadataIndexPattern( + metadataRequestContext.requestHandlerContext + ); const query = getESQueryHostMetadataByID(id, index); - const response = (await context.core.elasticsearch.dataClient.callAsCurrentUser( + const response = (await metadataRequestContext.requestHandlerContext.core.elasticsearch.dataClient.callAsCurrentUser( 'search', query )) as SearchResponse; @@ -110,22 +130,25 @@ export async function getHostData( return undefined; } - return enrichHostMetadata(response.hits.hits[0]._source); + return await enrichHostMetadata(response.hits.hits[0]._source, metadataRequestContext); } -function mapToHostResultList( +async function mapToHostResultList( queryParams: Record, - searchResponse: SearchResponse -): HostResultList { + searchResponse: SearchResponse, + metadataRequestContext: MetadataRequestContext +): Promise { const totalNumberOfHosts = searchResponse?.aggregations?.total?.value || 0; if (searchResponse.hits.hits.length > 0) { return { request_page_size: queryParams.size, request_page_index: queryParams.from, - hosts: searchResponse.hits.hits - .map(response => response.inner_hits.most_recent.hits.hits) - .flatMap(data => data as HitSource) - .map(entry => enrichHostMetadata(entry._source)), + hosts: await Promise.all( + searchResponse.hits.hits + .map(response => response.inner_hits.most_recent.hits.hits) + .flatMap(data => data as HitSource) + .map(async entry => enrichHostMetadata(entry._source, metadataRequestContext)) + ), total: totalNumberOfHosts, }; } else { @@ -138,9 +161,43 @@ function mapToHostResultList( } } -function enrichHostMetadata(hostMetadata: HostMetadata): HostInfo { +async function enrichHostMetadata( + hostMetadata: HostMetadata, + metadataRequestContext: MetadataRequestContext +): Promise { + let hostStatus = HostStatus.ERROR; + let elasticAgentId = hostMetadata?.elastic?.agent?.id; + const log = logger(metadataRequestContext.endpointAppContext); + try { + /** + * Get agent status by elastic agent id if available or use the host id. + * https://github.com/elastic/endpoint-app-team/issues/354 + */ + + if (!elasticAgentId) { + elasticAgentId = hostMetadata.host.id; + log.warn(`Missing elastic agent id, using host id instead ${elasticAgentId}`); + } + + const status = await metadataRequestContext.endpointAppContext.agentService.getAgentStatusById( + metadataRequestContext.requestHandlerContext.core.savedObjects.client, + elasticAgentId + ); + hostStatus = HOST_STATUS_MAPPING.get(status) || HostStatus.ERROR; + } catch (e) { + if (e.isBoom && e.output.statusCode === 404) { + log.warn(`agent with id ${elasticAgentId} not found`); + } else { + log.error(e); + throw e; + } + } return { metadata: hostMetadata, - host_status: HostStatus.ERROR, + host_status: hostStatus, }; } + +const logger = (endpointAppContext: EndpointAppContext): Logger => { + return endpointAppContext.logFactory.get('metadata'); +}; diff --git a/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts b/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts index 9a7d3fb3188a64..a1186aabc7a66a 100644 --- a/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts @@ -25,7 +25,9 @@ import { SearchResponse } from 'elasticsearch'; import { registerEndpointRoutes } from './index'; import { EndpointConfigSchema } from '../../config'; import * as data from '../../test_data/all_metadata_data.json'; -import { createMockMetadataIndexPatternRetriever } from '../../mocks'; +import { createMockAgentService, createMockMetadataIndexPatternRetriever } from '../../mocks'; +import { AgentService } from '../../../../ingest_manager/common/types'; +import Boom from 'boom'; describe('test endpoint route', () => { let routerMock: jest.Mocked; @@ -35,6 +37,7 @@ describe('test endpoint route', () => { let mockSavedObjectClient: jest.Mocked; let routeHandler: RequestHandler; let routeConfig: RouteConfig; + let mockAgentService: jest.Mocked; beforeEach(() => { mockClusterClient = elasticsearchServiceMock.createClusterClient() as jest.Mocked< @@ -45,8 +48,10 @@ describe('test endpoint route', () => { mockClusterClient.asScoped.mockReturnValue(mockScopedClient); routerMock = httpServiceMock.createRouter(); mockResponse = httpServerMock.createResponseFactory(); + mockAgentService = createMockAgentService(); registerEndpointRoutes(routerMock, { indexPatternRetriever: createMockMetadataIndexPatternRetriever(), + agentService: mockAgentService, logFactory: loggingServiceMock.create(), config: () => Promise.resolve(EndpointConfigSchema.validate({})), }); @@ -83,7 +88,7 @@ describe('test endpoint route', () => { [routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) => path.startsWith('/api/endpoint/metadata') )!; - + mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); await routeHandler( createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), mockRequest, @@ -113,6 +118,8 @@ describe('test endpoint route', () => { ], }, }); + + mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve((data as unknown) as SearchResponse) ); @@ -154,6 +161,8 @@ describe('test endpoint route', () => { filter: 'not host.ip:10.140.73.246', }, }); + + mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve((data as unknown) as SearchResponse) ); @@ -216,10 +225,10 @@ describe('test endpoint route', () => { }, }) ); + mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => path.startsWith('/api/endpoint/metadata') )!; - await routeHandler( createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), mockRequest, @@ -233,13 +242,14 @@ describe('test endpoint route', () => { expect(message).toEqual('Endpoint Not Found'); }); - it('should return a single endpoint with status error', async () => { + it('should return a single endpoint with status online', async () => { const mockRequest = httpServerMock.createKibanaRequest({ params: { id: (data as any).hits.hits[0]._id }, }); const response: SearchResponse = (data as unknown) as SearchResponse< HostMetadata >; + mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('online'); mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response)); [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => path.startsWith('/api/endpoint/metadata') @@ -256,6 +266,64 @@ describe('test endpoint route', () => { expect(mockResponse.ok).toBeCalled(); const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo; expect(result).toHaveProperty('metadata.endpoint'); + expect(result.host_status).toEqual(HostStatus.ONLINE); + }); + + it('should return a single endpoint with status error when AgentService throw 404', async () => { + const response: SearchResponse = (data as unknown) as SearchResponse< + HostMetadata + >; + + const mockRequest = httpServerMock.createKibanaRequest({ + params: { id: response.hits.hits[0]._id }, + }); + + mockAgentService.getAgentStatusById = jest.fn().mockImplementation(() => { + throw Boom.notFound('Agent not found'); + }); + mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response)); + [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => + path.startsWith('/api/endpoint/metadata') + )!; + + await routeHandler( + createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), + mockRequest, + mockResponse + ); + + expect(mockScopedClient.callAsCurrentUser).toBeCalled(); + expect(routeConfig.options).toEqual({ authRequired: true }); + expect(mockResponse.ok).toBeCalled(); + const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo; + expect(result.host_status).toEqual(HostStatus.ERROR); + }); + + it('should return a single endpoint with status error when status is not offline or online', async () => { + const response: SearchResponse = (data as unknown) as SearchResponse< + HostMetadata + >; + + const mockRequest = httpServerMock.createKibanaRequest({ + params: { id: response.hits.hits[0]._id }, + }); + + mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('warning'); + mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response)); + [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => + path.startsWith('/api/endpoint/metadata') + )!; + + await routeHandler( + createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), + mockRequest, + mockResponse + ); + + expect(mockScopedClient.callAsCurrentUser).toBeCalled(); + expect(routeConfig.options).toEqual({ authRequired: true }); + expect(mockResponse.ok).toBeCalled(); + const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo; expect(result.host_status).toEqual(HostStatus.ERROR); }); }); diff --git a/x-pack/plugins/endpoint/server/routes/metadata/query_builders.test.ts b/x-pack/plugins/endpoint/server/routes/metadata/query_builders.test.ts index c8143fbdda1ea1..7e6e3f875cd4ca 100644 --- a/x-pack/plugins/endpoint/server/routes/metadata/query_builders.test.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/query_builders.test.ts @@ -6,7 +6,11 @@ import { httpServerMock, loggingServiceMock } from '../../../../../../src/core/server/mocks'; import { EndpointConfigSchema } from '../../config'; import { kibanaRequestToMetadataListESQuery, getESQueryHostMetadataByID } from './query_builders'; -import { createMockMetadataIndexPatternRetriever, MetadataIndexPattern } from '../../mocks'; +import { + createMockAgentService, + createMockMetadataIndexPatternRetriever, + MetadataIndexPattern, +} from '../../mocks'; describe('query builder', () => { describe('MetadataListESQuery', () => { @@ -18,6 +22,7 @@ describe('query builder', () => { mockRequest, { indexPatternRetriever: createMockMetadataIndexPatternRetriever(), + agentService: createMockAgentService(), logFactory: loggingServiceMock.create(), config: () => Promise.resolve(EndpointConfigSchema.validate({})), }, @@ -69,6 +74,7 @@ describe('query builder', () => { mockRequest, { indexPatternRetriever: createMockMetadataIndexPatternRetriever(), + agentService: createMockAgentService(), logFactory: loggingServiceMock.create(), config: () => Promise.resolve(EndpointConfigSchema.validate({})), }, diff --git a/x-pack/plugins/endpoint/server/types.ts b/x-pack/plugins/endpoint/server/types.ts index 46a23060339f41..d43ec58aec4282 100644 --- a/x-pack/plugins/endpoint/server/types.ts +++ b/x-pack/plugins/endpoint/server/types.ts @@ -6,12 +6,14 @@ import { LoggerFactory } from 'kibana/server'; import { EndpointConfigType } from './config'; import { IndexPatternRetriever } from './index_pattern'; +import { AgentService } from '../../ingest_manager/common/types'; /** * The context for Endpoint apps. */ export interface EndpointAppContext { indexPatternRetriever: IndexPatternRetriever; + agentService: AgentService; logFactory: LoggerFactory; config(): Promise; } diff --git a/x-pack/plugins/ingest_manager/common/types/index.ts b/x-pack/plugins/ingest_manager/common/types/index.ts index 42f7a9333118e5..150a4c9d602802 100644 --- a/x-pack/plugins/ingest_manager/common/types/index.ts +++ b/x-pack/plugins/ingest_manager/common/types/index.ts @@ -3,9 +3,24 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { SavedObjectsClientContract } from 'kibana/server'; +import { AgentStatus } from './models'; + export * from './models'; export * from './rest_spec'; +/** + * A service that provides exported functions that return information about an Agent + */ +export interface AgentService { + /** + * Return the status by the Agent's id + * @param soClient + * @param agentId + */ + getAgentStatusById(soClient: SavedObjectsClientContract, agentId: string): Promise; +} + export interface IngestManagerConfigType { enabled: boolean; epm: { diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/ingest_manager/server/plugin.ts index 4dd070a7414f06..075a0917b9faed 100644 --- a/x-pack/plugins/ingest_manager/server/plugin.ts +++ b/x-pack/plugins/ingest_manager/server/plugin.ts @@ -39,18 +39,20 @@ import { registerInstallScriptRoutes, } from './routes'; -import { IngestManagerConfigType } from '../common'; +import { AgentService, IngestManagerConfigType } from '../common'; import { appContextService, ESIndexPatternService, ESIndexPatternSavedObjectService, } from './services'; +import { getAgentStatusById } from './services/agents'; /** * Describes public IngestManager plugin contract returned at the `setup` stage. */ export interface IngestManagerSetupContract { esIndexPatternService: ESIndexPatternService; + agentService: AgentService; } export interface IngestManagerSetupDeps { @@ -148,6 +150,9 @@ export class IngestManagerPlugin implements Plugin { } return deepFreeze({ esIndexPatternService: new ESIndexPatternSavedObjectService(), + agentService: { + getAgentStatusById, + }, }); } diff --git a/x-pack/plugins/ingest_manager/server/services/agents/status.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/status.test.ts new file mode 100644 index 00000000000000..d19fe883a7780e --- /dev/null +++ b/x-pack/plugins/ingest_manager/server/services/agents/status.test.ts @@ -0,0 +1,43 @@ +/* + * 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 { savedObjectsClientMock } from '../../../../../../src/core/server/saved_objects/service/saved_objects_client.mock'; +import { getAgentStatusById } from './status'; +import { AGENT_TYPE_PERMANENT } from '../../../common/constants'; +import { AgentSOAttributes } from '../../../common/types/models'; +import { SavedObject } from 'kibana/server'; + +describe('Agent status service', () => { + it('should return inactive when agent is not active', async () => { + const mockSavedObjectsClient = savedObjectsClientMock.create(); + mockSavedObjectsClient.get = jest.fn().mockReturnValue({ + id: 'id', + type: AGENT_TYPE_PERMANENT, + attributes: { + active: false, + local_metadata: '{}', + user_provided_metadata: '{}', + }, + } as SavedObject); + const status = await getAgentStatusById(mockSavedObjectsClient, 'id'); + expect(status).toEqual('inactive'); + }); + + it('should return online when agent is active', async () => { + const mockSavedObjectsClient = savedObjectsClientMock.create(); + mockSavedObjectsClient.get = jest.fn().mockReturnValue({ + id: 'id', + type: AGENT_TYPE_PERMANENT, + attributes: { + active: true, + local_metadata: '{}', + user_provided_metadata: '{}', + }, + } as SavedObject); + const status = await getAgentStatusById(mockSavedObjectsClient, 'id'); + expect(status).toEqual('online'); + }); +}); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/status.ts b/x-pack/plugins/ingest_manager/server/services/agents/status.ts index 21e200d701e69d..001b6d01f078ee 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/status.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/status.ts @@ -5,7 +5,7 @@ */ import { SavedObjectsClientContract } from 'src/core/server'; -import { listAgents } from './crud'; +import { getAgent, listAgents } from './crud'; import { AGENT_EVENT_SAVED_OBJECT_TYPE } from '../../constants'; import { AgentStatus, Agent } from '../../types'; @@ -17,6 +17,14 @@ import { } from '../../constants'; import { AgentStatusKueryHelper } from '../../../common/services'; +export async function getAgentStatusById( + soClient: SavedObjectsClientContract, + agentId: string +): Promise { + const agent = await getAgent(soClient, agentId); + return getAgentStatus(agent); +} + export function getAgentStatus(agent: Agent, now: number = Date.now()): AgentStatus { const { type, last_checkin: lastCheckIn } = agent; const msLastCheckIn = new Date(lastCheckIn || 0).getTime(); diff --git a/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/data.json.gz b/x-pack/test/functional/es_archives/endpoint/alerts/host_api_feature/data.json.gz index a71281c0ecfec93e0a51aa3473dbbbf565a10f9b..3d4f0e11a7cc670366fa6098143b6306a1a1c72b 100644 GIT binary patch literal 849 zcmV-X1FrlZiwFo(DwQOZ*BnHmQ8QtHV}sI{uPGL8G#%QsbNp`rYZIi z6fG9$q9}Ucr{mVP3|sbY8sxv1$|hMyaz2pmUIJK#M9yf2e20(bYBU~C+RJsC{4k!x zCQMGcfxTPox{<5$P#)*8o4q{$WhvfI-^HJ5{qg+o^FMyezu%a57OR*(cSg(9ZsuVN zX%*MaeAPls+3glB$UD<*v)hEPFE1lMw)17$wr;sDqf#>oBBeBGdva&uW@*(P_}nLz zT=JBBgzQ2B3Rr_DVSr5_>71xp_{qsvuYKBV=gq2fHI=IDT#g;YP>h;B}*3Pri>V2V}6vXXS5*Ix&rmM-*T=~8nRBtf!JwD97%XS3}qnRkoD z7e!XF`LrE>NSjicV-ymPOZVy4&{RExz1z&)jaa>x^~??nACG81y8FK#U5`drFXzHL z-{Zn;w*H=6FxtF;3p$#RD1(xLx>H%oNJfO(r6kJX(U$F_Trd^?e=cb6rS*j1HD@5= zazH5ws70t<#fR@o3d_%vq7K>S+9^PGrhIT@0`EzK5ZXE9EIEoN zNbwCi2%)U@2@NQ%3Mq`>!VFVBC|+^NKoeW_W=UcBHKYii|JAcaDTMWr4#FoELMSlS zbm$kkoHQpPTM7z2QL_43QV>_N^a6-@q=6{9WBCnd1a>`ydZY_DLJCeD2)qS!z#1a= bNFl(AQj>tC8akZ3k@n(0L$8A>b_@Uj^{t;- literal 753 zcmVQOZ*BnHl}k_CKoEfM`4v&ljJ0p?_EbZuIH0N( zQc+d)u+PM8Y)6h0post8bs!musT1Ah2*=*TtobuWm=8c1(tp{dF z3Wgy8QdowyMyW(*bo6Mo^z}Ti%GOjQG*P36rMt~hYtMI2n^n`?-t5RWKAezDAw^H& zlt=~a1Z9wAjDb9%Bx9I;&eCklhiF&oVwLdAyqOG7moc`y9FH~{cYUXpz4&eIb>m$( zWj)hLHSbuqlC=kC@g1}&!lbbL&tf}O_rY0a9JVF^PE`ubc?^nJ0))ww8b!I3XveB8 z;?M11F-kCsPl^)4(%nR~YnUbk#Ygp)BuykvFcXid`L%Uap>6jp9(zCmX5baXS{St7 zvYK}*85Yy&eML%FUCoCVzK*RjBnXrJrMr4i6iG*6sq0*?iq+Yyqqg0MWKZn}>;JpK z-C%HgIH$*^!|8ZDdrM9Q7YA_4ov@w-px82CK8S2DNcmw^Gjqx zC{L(7m{LZggJ3KK0z!lCkk1(i;3i&6;2e1NT81%t9^L3XdXe3)b@e2;s}24mxwc2{ z