From 82e33f05b915d15b7a25ea20fdee7283e0ced677 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Mon, 29 Jun 2020 18:48:18 -0400 Subject: [PATCH] [IngestManager] Allow to filter agent by packages (#69731) --- .../common/types/models/agent.ts | 2 + .../server/saved_objects/index.ts | 1 + .../server/services/agents/acks.test.ts | 269 ++++++++++++++++++ .../server/services/agents/acks.ts | 27 +- .../server/services/agents/saved_objects.ts | 1 + 5 files changed, 292 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/ingest_manager/common/types/models/agent.ts b/x-pack/plugins/ingest_manager/common/types/models/agent.ts index 7644e2ca57f268..d2a2a3f5705ae3 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/agent.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/agent.ts @@ -90,8 +90,10 @@ export interface Agent extends AgentBase { current_error_events: AgentEvent[]; access_api_key?: string; status?: string; + packages: string[]; } export interface AgentSOAttributes extends AgentBase { current_error_events?: string; + packages?: string[]; } diff --git a/x-pack/plugins/ingest_manager/server/saved_objects/index.ts b/x-pack/plugins/ingest_manager/server/saved_objects/index.ts index 9afc06c193a3b1..c9548600cf1c4a 100644 --- a/x-pack/plugins/ingest_manager/server/saved_objects/index.ts +++ b/x-pack/plugins/ingest_manager/server/saved_objects/index.ts @@ -68,6 +68,7 @@ const savedObjectTypes: { [key: string]: SavedObjectsType } = { default_api_key: { type: 'keyword' }, updated_at: { type: 'date' }, current_error_events: { type: 'text' }, + packages: { type: 'keyword' }, }, }, }, diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts index efdcbdb5c36bb9..abc4518e70573b 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts @@ -93,6 +93,275 @@ describe('test agent acks services', () => { ]); }); + it('should update config field on the agent if a config change is acknowledged', async () => { + const mockSavedObjectsClient = savedObjectsClientMock.create(); + const mockStartEncryptedSOPlugin = encryptedSavedObjectsMock.createStart(); + appContextService.start(({ + encryptedSavedObjectsStart: mockStartEncryptedSOPlugin, + } as unknown) as IngestManagerAppContext); + + const [ + { value: mockStartEncryptedSOClient }, + ] = mockStartEncryptedSOPlugin.getClient.mock.results; + + mockStartEncryptedSOClient.getDecryptedAsInternalUser.mockReturnValue( + Promise.resolve({ + id: 'action1', + references: [], + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + attributes: { + type: 'CONFIG_CHANGE', + agent_id: 'id', + sent_at: '2020-03-14T19:45:02.620Z', + timestamp: '2019-01-04T14:32:03.36764-05:00', + created_at: '2020-03-14T19:45:02.620Z', + data: JSON.stringify({ + config: { + id: 'config1', + revision: 4, + settings: { + monitoring: { + enabled: true, + use_output: 'default', + logs: true, + metrics: true, + }, + }, + outputs: { + default: { + type: 'elasticsearch', + hosts: ['http://localhost:9200'], + }, + }, + inputs: [ + { + id: 'f2293360-b57c-11ea-8bd3-7bd51e425399', + name: 'system-1', + type: 'logs', + use_output: 'default', + package: { + name: 'system', + version: '0.3.0', + }, + dataset: { + namespace: 'default', + }, + streams: [ + { + id: 'logs-system.syslog', + dataset: { + name: 'system.syslog', + }, + paths: ['/var/log/messages*', '/var/log/syslog*'], + exclude_files: ['.gz$'], + multiline: { + pattern: '^\\s', + match: 'after', + }, + processors: [ + { + add_locale: null, + }, + { + add_fields: { + target: '', + fields: { + 'ecs.version': '1.5.0', + }, + }, + }, + ], + }, + ], + }, + ], + }, + }), + }, + }) + ); + + mockSavedObjectsClient.bulkGet.mockReturnValue( + Promise.resolve({ + saved_objects: [ + { + id: 'action1', + references: [], + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + attributes: { + type: 'CONFIG_CHANGE', + agent_id: 'id', + sent_at: '2020-03-14T19:45:02.620Z', + timestamp: '2019-01-04T14:32:03.36764-05:00', + created_at: '2020-03-14T19:45:02.620Z', + }, + }, + ], + } as SavedObjectsBulkResponse) + ); + + await acknowledgeAgentActions( + mockSavedObjectsClient, + ({ + id: 'id', + type: AGENT_TYPE_PERMANENT, + config_id: 'config1', + } as unknown) as Agent, + [ + { + type: 'ACTION_RESULT', + subtype: 'CONFIG', + timestamp: '2019-01-04T14:32:03.36764-05:00', + action_id: 'action1', + agent_id: 'id', + } as AgentEvent, + ] + ); + expect(mockSavedObjectsClient.bulkUpdate).toBeCalled(); + expect(mockSavedObjectsClient.bulkUpdate.mock.calls[0][0]).toHaveLength(2); + expect(mockSavedObjectsClient.bulkUpdate.mock.calls[0][0][0]).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "config_revision": 4, + "packages": Array [ + "system", + ], + }, + "id": "id", + "type": "fleet-agents", + } + `); + }); + + it('should not update config field on the agent if a config change for an old revision is acknowledged', async () => { + const mockSavedObjectsClient = savedObjectsClientMock.create(); + const mockStartEncryptedSOPlugin = encryptedSavedObjectsMock.createStart(); + appContextService.start(({ + encryptedSavedObjectsStart: mockStartEncryptedSOPlugin, + } as unknown) as IngestManagerAppContext); + + const [ + { value: mockStartEncryptedSOClient }, + ] = mockStartEncryptedSOPlugin.getClient.mock.results; + + mockStartEncryptedSOClient.getDecryptedAsInternalUser.mockReturnValue( + Promise.resolve({ + id: 'action1', + references: [], + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + attributes: { + type: 'CONFIG_CHANGE', + agent_id: 'id', + sent_at: '2020-03-14T19:45:02.620Z', + timestamp: '2019-01-04T14:32:03.36764-05:00', + created_at: '2020-03-14T19:45:02.620Z', + data: JSON.stringify({ + config: { + id: 'config1', + revision: 4, + settings: { + monitoring: { + enabled: true, + use_output: 'default', + logs: true, + metrics: true, + }, + }, + outputs: { + default: { + type: 'elasticsearch', + hosts: ['http://localhost:9200'], + }, + }, + inputs: [ + { + id: 'f2293360-b57c-11ea-8bd3-7bd51e425399', + name: 'system-1', + type: 'logs', + use_output: 'default', + package: { + name: 'system', + version: '0.3.0', + }, + dataset: { + namespace: 'default', + }, + streams: [ + { + id: 'logs-system.syslog', + dataset: { + name: 'system.syslog', + }, + paths: ['/var/log/messages*', '/var/log/syslog*'], + exclude_files: ['.gz$'], + multiline: { + pattern: '^\\s', + match: 'after', + }, + processors: [ + { + add_locale: null, + }, + { + add_fields: { + target: '', + fields: { + 'ecs.version': '1.5.0', + }, + }, + }, + ], + }, + ], + }, + ], + }, + }), + }, + }) + ); + + mockSavedObjectsClient.bulkGet.mockReturnValue( + Promise.resolve({ + saved_objects: [ + { + id: 'action1', + references: [], + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + attributes: { + type: 'CONFIG_CHANGE', + agent_id: 'id', + sent_at: '2020-03-14T19:45:02.620Z', + timestamp: '2019-01-04T14:32:03.36764-05:00', + created_at: '2020-03-14T19:45:02.620Z', + }, + }, + ], + } as SavedObjectsBulkResponse) + ); + + await acknowledgeAgentActions( + mockSavedObjectsClient, + ({ + id: 'id', + type: AGENT_TYPE_PERMANENT, + config_id: 'config1', + config_revision: 100, + } as unknown) as Agent, + [ + { + type: 'ACTION_RESULT', + subtype: 'CONFIG', + timestamp: '2019-01-04T14:32:03.36764-05:00', + action_id: 'action1', + agent_id: 'id', + } as AgentEvent, + ] + ); + expect(mockSavedObjectsClient.bulkUpdate).toBeCalled(); + expect(mockSavedObjectsClient.bulkUpdate.mock.calls[0][0]).toHaveLength(1); + }); + it('should fail for actions that cannot be found on agent actions list', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); mockSavedObjectsClient.bulkGet.mockReturnValue( diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts b/x-pack/plugins/ingest_manager/server/services/agents/acks.ts index a1b48a879bb890..e391e81ebd0a62 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/acks.ts @@ -18,6 +18,7 @@ import { AgentEventSOAttributes, AgentSOAttributes, AgentActionSOAttributes, + FullAgentConfig, } from '../../types'; import { AGENT_EVENT_SAVED_OBJECT_TYPE, @@ -62,18 +63,18 @@ export async function acknowledgeAgentActions( if (actions.length === 0) { return []; } - const configRevision = getLatestConfigRevison(agent, actions); + const config = getLatestConfigIfUpdated(agent, actions); await soClient.bulkUpdate([ - buildUpdateAgentConfigRevision(agent.id, configRevision), + ...(config ? [buildUpdateAgentConfig(agent.id, config)] : []), ...buildUpdateAgentActionSentAt(actionIds), ]); return actions; } -function getLatestConfigRevison(agent: Agent, actions: AgentAction[]) { - return actions.reduce((acc, action) => { +function getLatestConfigIfUpdated(agent: Agent, actions: AgentAction[]) { + return actions.reduce((acc, action) => { if (action.type !== 'CONFIG_CHANGE') { return acc; } @@ -83,16 +84,26 @@ function getLatestConfigRevison(agent: Agent, actions: AgentAction[]) { return acc; } - return data?.config?.revision > acc ? data?.config?.revision : acc; - }, agent.config_revision || 0); + const currentRevision = (acc && acc.revision) || agent.config_revision || 0; + + return data?.config?.revision > currentRevision ? data?.config : acc; + }, null); } -function buildUpdateAgentConfigRevision(agentId: string, configRevision: number) { +function buildUpdateAgentConfig(agentId: string, config: FullAgentConfig) { + const packages = config.inputs.reduce((acc, input) => { + if (input.package && input.package.name && acc.indexOf(input.package.name) < 0) { + return [input.package.name, ...acc]; + } + return acc; + }, []); + return { type: AGENT_SAVED_OBJECT_TYPE, id: agentId, attributes: { - config_revision: configRevision, + config_revision: config.revision, + packages, }, }; } diff --git a/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts b/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts index 11beba1cd7e43e..2ab5cc8139f69f 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts @@ -23,6 +23,7 @@ export function savedObjectToAgent(so: SavedObject): Agent { user_provided_metadata: so.attributes.user_provided_metadata, access_api_key: undefined, status: undefined, + packages: so.attributes.packages ?? [], }; }