From 0a705889b04d7bc39837e75c248274fd4922ee5d Mon Sep 17 00:00:00 2001 From: jillguyonnet Date: Wed, 7 Aug 2024 16:56:11 +0200 Subject: [PATCH 01/12] [Fleet] Make upgrade agent APIs space aware --- .../plugins/fleet/common/openapi/bundled.json | 10 +- .../plugins/fleet/common/openapi/bundled.yaml | 7 +- .../fleet/common/openapi/entrypoint.yaml | 4 +- ...=> agents@actions@{action_id}@cancel.yaml} | 5 - .../server/routes/agent/actions_handlers.ts | 10 +- .../server/services/agents/actions.test.ts | 17 +- .../fleet/server/services/agents/actions.ts | 43 +++-- .../fleet/server/services/agents/upgrade.ts | 19 +- .../services/agents/upgrade_action_runner.ts | 5 + .../apis/space_awareness/actions.ts | 49 ++++- .../apis/space_awareness/agents.ts | 169 ++++++++++++++++-- .../apis/space_awareness/api_helper.ts | 25 ++- .../apis/space_awareness/helpers.ts | 33 +++- 13 files changed, 325 insertions(+), 71 deletions(-) rename x-pack/plugins/fleet/common/openapi/paths/{agents@{agent_id}@actions@{action_id}@cancel.yaml => agents@actions@{action_id}@cancel.yaml} (87%) diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index cf3ee35fca6df..7c87ba051204b 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -2471,16 +2471,8 @@ } } }, - "/agents/{agentId}/actions/{actionId}/cancel": { + "/agents/actions/{actionId}/cancel": { "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "agentId", - "in": "path", - "required": true - }, { "schema": { "type": "string" diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index ad592b9ea1847..b29bccc70ea25 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -1564,13 +1564,8 @@ paths: properties: action: $ref: '#/components/schemas/agent_action' - /agents/{agentId}/actions/{actionId}/cancel: + /agents/actions/{actionId}/cancel: parameters: - - schema: - type: string - name: agentId - in: path - required: true - schema: type: string name: actionId diff --git a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml index 04203cc5d2e6b..fa361c180b26e 100644 --- a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml +++ b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml @@ -75,8 +75,8 @@ paths: $ref: 'paths/agents@{agent_id}.yaml' '/agents/{agentId}/actions': $ref: 'paths/agents@{agent_id}@actions.yaml' - '/agents/{agentId}/actions/{actionId}/cancel': - $ref: 'paths/agents@{agent_id}@actions@{action_id}@cancel.yaml' + '/agents/actions/{actionId}/cancel': + $ref: 'paths/agents@actions@{action_id}@cancel.yaml' '/agents/files/{fileId}/{fileName}': $ref: 'paths/agents@files@{file_id}@{file_name}.yaml' '/agents/files/{fileId}': diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions@{action_id}@cancel.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@actions@{action_id}@cancel.yaml similarity index 87% rename from x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions@{action_id}@cancel.yaml rename to x-pack/plugins/fleet/common/openapi/paths/agents@actions@{action_id}@cancel.yaml index f91acd133355d..8e326bf28809f 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@actions@{action_id}@cancel.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@actions@{action_id}@cancel.yaml @@ -1,9 +1,4 @@ parameters: - - schema: - type: string - name: agentId - in: path - required: true - schema: type: string name: actionId diff --git a/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts index 9b5bbdccb3540..b11dcb719e2d2 100644 --- a/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts @@ -59,9 +59,15 @@ export const postCancelActionHandlerBuilder = function ( ): RequestHandler, undefined, undefined> { return async (context, request, response) => { try { - const esClient = (await context.core).elasticsearch.client.asInternalUser; + const core = await context.core; + const esClient = core.elasticsearch.client.asInternalUser; + const soClient = core.savedObjects.client; - const action = await actionsService.cancelAgentAction(esClient, request.params.actionId); + const action = await actionsService.cancelAgentAction( + esClient, + soClient, + request.params.actionId + ); const body: PostNewAgentActionResponse = { item: action, diff --git a/x-pack/plugins/fleet/server/services/agents/actions.test.ts b/x-pack/plugins/fleet/server/services/agents/actions.test.ts index 4a2fc9e743b2a..b8cb2ce8c8d6a 100644 --- a/x-pack/plugins/fleet/server/services/agents/actions.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/actions.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; +import { elasticsearchServiceMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; import type { NewAgentAction, AgentActionType } from '../../../common/types'; @@ -307,16 +307,17 @@ describe('Agent actions', () => { }); describe('cancelAgentAction', () => { - it('throw if the target action is not found', async () => { + it('should throw if the target action is not found', async () => { const esClient = elasticsearchServiceMock.createInternalClient(); esClient.search.mockResolvedValue({ hits: { hits: [], }, } as any); - await expect(() => cancelAgentAction(esClient, 'i-do-not-exists')).rejects.toThrowError( - /Action not found/ - ); + const soClient = savedObjectsClientMock.create(); + await expect(() => + cancelAgentAction(esClient, soClient, 'i-do-not-exists') + ).rejects.toThrowError(/Action not found/); }); it('should create one CANCEL action for each UPGRADE action found', async () => { @@ -343,7 +344,8 @@ describe('Agent actions', () => { ], }, } as any); - await cancelAgentAction(esClient, 'action1'); + const soClient = savedObjectsClientMock.create(); + await cancelAgentAction(esClient, soClient, 'action1'); expect(esClient.create).toBeCalledTimes(2); expect(esClient.create).toBeCalledWith( @@ -382,7 +384,8 @@ describe('Agent actions', () => { ], }, } as any); - await cancelAgentAction(esClient, 'action1'); + const soClient = savedObjectsClientMock.create(); + await cancelAgentAction(esClient, soClient, 'action1'); expect(mockedBulkUpdateAgents).toBeCalled(); expect(mockedBulkUpdateAgents).toBeCalledWith( diff --git a/x-pack/plugins/fleet/server/services/agents/actions.ts b/x-pack/plugins/fleet/server/services/agents/actions.ts index f344aa24e59dd..bd62b350513d2 100644 --- a/x-pack/plugins/fleet/server/services/agents/actions.ts +++ b/x-pack/plugins/fleet/server/services/agents/actions.ts @@ -30,6 +30,9 @@ import { auditLoggingService } from '../audit_logging'; import { getAgentIdsForAgentPolicies } from '../agent_policies/agent_policies_to_agent_ids'; +import { getCurrentNamespace } from '../spaces/get_current_namespace'; +import { addNamespaceFilteringToQuery } from '../spaces/query_namespaces_filtering'; + import { bulkUpdateAgents } from './crud'; const ONE_MONTH_IN_MS = 2592000000; @@ -305,21 +308,28 @@ export async function getUnenrollAgentActions( return result; } -export async function cancelAgentAction(esClient: ElasticsearchClient, actionId: string) { +export async function cancelAgentAction( + esClient: ElasticsearchClient, + soClient: SavedObjectsClientContract, + actionId: string +) { + const currentNameSpace = getCurrentNamespace(soClient); + const getUpgradeActions = async () => { - const res = await esClient.search({ - index: AGENT_ACTIONS_INDEX, - query: { - bool: { - filter: [ - { - term: { - action_id: actionId, - }, + const query = { + bool: { + filter: [ + { + term: { + action_id: actionId, }, - ], - }, + }, + ], }, + }; + const res = await esClient.search({ + index: AGENT_ACTIONS_INDEX, + query: addNamespaceFilteringToQuery(query, currentNameSpace), size: SO_SEARCH_LIMIT, }); @@ -348,9 +358,12 @@ export async function cancelAgentAction(esClient: ElasticsearchClient, actionId: const cancelledActions: Array<{ agents: string[] }> = []; const createAction = async (action: FleetServerAgentAction) => { + const namespaces = currentNameSpace ? { namespaces: [currentNameSpace] } : {}; + await createAgentAction(esClient, { id: cancelActionId, type: 'CANCEL', + ...namespaces, agents: action.agents!, data: { target_id: action.action_id, @@ -505,7 +518,11 @@ export interface ActionsService { agentId: string ) => Promise; - cancelAgentAction: (esClient: ElasticsearchClient, actionId: string) => Promise; + cancelAgentAction: ( + esClient: ElasticsearchClient, + soClient: SavedObjectsClientContract, + actionId: string + ) => Promise; createAgentAction: ( esClient: ElasticsearchClient, diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.ts index a164b9ff86399..586f19c8af3fd 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade.ts @@ -10,6 +10,9 @@ import type { Agent } from '../../types'; import { AgentReassignmentError, HostedAgentPolicyRestrictionRelatedError } from '../../errors'; import { SO_SEARCH_LIMIT } from '../../constants'; +import { agentsKueryNamespaceFilter, isAgentInNamespace } from '../spaces/agent_namespaces'; +import { getCurrentNamespace } from '../spaces/get_current_namespace'; + import { createAgentAction } from './actions'; import type { GetAgentsOptions } from './crud'; import { openPointInTime } from './crud'; @@ -43,12 +46,16 @@ export async function sendUpgradeAgentAction({ ); } + const currentNameSpace = getCurrentNamespace(soClient); + const namespaces = currentNameSpace ? { namespaces: [currentNameSpace] } : {}; + await createAgentAction(esClient, { agents: [agentId], created_at: now, data, ack_data: data, type: 'UPGRADE', + ...namespaces, }); await updateAgent(esClient, agentId, { upgraded_at: null, @@ -72,6 +79,8 @@ export async function sendUpgradeAgentsActions( // Full set of agents const outgoingErrors: Record = {}; let givenAgents: Agent[] = []; + const currentNameSpace = getCurrentNamespace(soClient); + if ('agents' in options) { givenAgents = options.agents; } else if ('agentIds' in options) { @@ -81,18 +90,26 @@ export async function sendUpgradeAgentsActions( outgoingErrors[maybeAgent.id] = new AgentReassignmentError( `Cannot find agent ${maybeAgent.id}` ); + } else if (!isAgentInNamespace(maybeAgent, currentNameSpace)) { + outgoingErrors[maybeAgent.id] = new AgentReassignmentError( + `Agent ${maybeAgent.id} is not in the current space` + ); } else { givenAgents.push(maybeAgent); } } } else if ('kuery' in options) { const batchSize = options.batchSize ?? SO_SEARCH_LIMIT; + const namespaceFilter = agentsKueryNamespaceFilter(currentNameSpace); + const kuery = namespaceFilter ? `${namespaceFilter} AND ${options.kuery}` : options.kuery; + const res = await getAgentsByKuery(esClient, soClient, { - kuery: options.kuery, + kuery, showInactive: options.showInactive ?? false, page: 1, perPage: batchSize, }); + if (res.total <= batchSize) { givenAgents = res.agents; } else { diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts index d123489fe9ead..537ea71d7c469 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts @@ -22,6 +22,8 @@ import { HostedAgentPolicyRestrictionRelatedError, FleetError } from '../../erro import { appContextService } from '../app_context'; +import { getCurrentNamespace } from '../spaces/get_current_namespace'; + import { ActionRunner } from './action_runner'; import type { GetAgentsOptions } from './crud'; @@ -167,6 +169,8 @@ export async function upgradeBatch( const actionId = options.actionId ?? uuidv4(); const total = options.total ?? givenAgents.length; + const currentNameSpace = getCurrentNamespace(soClient); + const namespaces = currentNameSpace ? { namespaces: [currentNameSpace] } : {}; await createAgentAction(esClient, { id: actionId, @@ -177,6 +181,7 @@ export async function upgradeBatch( total, agents: agentsToUpdate.map((agent) => agent.id), ...rollingUpgradeOptions, + ...namespaces, }); await createErrorActionResults( diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/actions.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/actions.ts index efd73ddb54b0f..48008802d2e21 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/actions.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/actions.ts @@ -19,8 +19,7 @@ export default function (providerContext: FtrProviderContext) { const esClient = getService('es'); const kibanaServer = getService('kibanaServer'); - // Failing: See https://github.com/elastic/kibana/issues/189805 - describe.skip('actions', async function () { + describe('actions', async function () { skipIfNoDockerRegistry(providerContext); const apiClient = new SpaceTestApiClient(supertest); @@ -251,5 +250,51 @@ export default function (providerContext: FtrProviderContext) { expect(actionStatusInCustomSpace.items.length).to.eql(1); }); }); + + describe('post /agents/actions/{actionId}/cancel', () => { + it('should return 200 and a CANCEL action if the action is in the same space', async () => { + // Create UPDATE_TAGS action for agents in custom space + await apiClient.bulkUpdateAgentTags( + { + agents: [testSpaceAgent1, testSpaceAgent2], + tagsToAdd: ['tag1'], + }, + TEST_SPACE_1 + ); + + const actionStatusInCustomSpace = await apiClient.getActionStatus(TEST_SPACE_1); + expect(actionStatusInCustomSpace.items.length).to.eql(1); + + const res = await apiClient.cancelAction( + actionStatusInCustomSpace.items[0].actionId, + TEST_SPACE_1 + ); + expect(res.item.type).to.eql('CANCEL'); + }); + + it('should return 404 if the action is in a different space', async () => { + // Create UPDATE_TAGS action for agents in custom space + await apiClient.bulkUpdateAgentTags( + { + agents: [testSpaceAgent1, testSpaceAgent2], + tagsToAdd: ['tag1'], + }, + TEST_SPACE_1 + ); + + const actionStatusInCustomSpace = await apiClient.getActionStatus(TEST_SPACE_1); + expect(actionStatusInCustomSpace.items.length).to.eql(1); + + let err: Error | undefined; + try { + await apiClient.cancelAction(actionStatusInCustomSpace.items[0].actionId); + } catch (_err) { + err = _err; + } + + expect(err).to.be.an(Error); + expect(err?.message).to.match(/404 "Not Found"/); + }); + }); }); } diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts index 047d32a854511..5ff9b20012bf3 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts @@ -10,7 +10,12 @@ import { CreateAgentPolicyResponse, GetAgentsResponse } from '@kbn/fleet-plugin/ import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; import { SpaceTestApiClient } from './api_helper'; -import { cleanFleetIndices, createFleetAgent } from './helpers'; +import { + cleanFleetAgents, + cleanFleetIndices, + createFleetAgent, + makeAgentsUpgradeable, +} from './helpers'; import { setupTestSpaces, TEST_SPACE_1 } from './space_helpers'; export default function (providerContext: FtrProviderContext) { @@ -49,16 +54,7 @@ export default function (providerContext: FtrProviderContext) { let testSpaceAgent1: string; let testSpaceAgent2: string; - before(async () => { - const [_defaultSpacePolicy1, _spaceTest1Policy1, _spaceTest1Policy2] = await Promise.all([ - apiClient.createAgentPolicy(), - apiClient.createAgentPolicy(TEST_SPACE_1), - apiClient.createAgentPolicy(TEST_SPACE_1), - ]); - defaultSpacePolicy1 = _defaultSpacePolicy1; - spaceTest1Policy1 = _spaceTest1Policy1; - spaceTest1Policy2 = _spaceTest1Policy2; - + async function createAgents() { const [_defaultSpaceAgent1, _defaultSpaceAgent2, _testSpaceAgent1, _testSpaceAgent2] = await Promise.all([ createFleetAgent(esClient, defaultSpacePolicy1.item.id, 'default'), @@ -70,9 +66,22 @@ export default function (providerContext: FtrProviderContext) { defaultSpaceAgent2 = _defaultSpaceAgent2; testSpaceAgent1 = _testSpaceAgent1; testSpaceAgent2 = _testSpaceAgent2; + } + + before(async () => { + const [_defaultSpacePolicy1, _spaceTest1Policy1, _spaceTest1Policy2] = await Promise.all([ + apiClient.createAgentPolicy(), + apiClient.createAgentPolicy(TEST_SPACE_1), + apiClient.createAgentPolicy(TEST_SPACE_1), + ]); + defaultSpacePolicy1 = _defaultSpacePolicy1; + spaceTest1Policy1 = _spaceTest1Policy1; + spaceTest1Policy2 = _spaceTest1Policy2; + + await createAgents(); }); - describe('GET /agents', () => { + describe('GET /agent', () => { it('should return agents in a specific space', async () => { const agents = await apiClient.getAgents(TEST_SPACE_1); expect(agents.total).to.eql(2); @@ -90,7 +99,7 @@ export default function (providerContext: FtrProviderContext) { }); }); - describe('GET /agents/{id}', () => { + describe('GET /agents/{agentId}', () => { it('should allow to retrieve agent in the same space', async () => { await apiClient.getAgent(testSpaceAgent1, TEST_SPACE_1); }); @@ -108,13 +117,13 @@ export default function (providerContext: FtrProviderContext) { }); }); - describe('PUT /agents/{id}', () => { - it('should allow to update an agent in the same space', async () => { + describe('PUT /agents/{agentId}', () => { + it('should allow updating an agent in the same space', async () => { await apiClient.updateAgent(testSpaceAgent1, { tags: ['foo'] }, TEST_SPACE_1); await apiClient.updateAgent(testSpaceAgent1, { tags: ['tag1'] }, TEST_SPACE_1); }); - it('should not allow to update an agent from a different space from the default space', async () => { + it('should not allow updating an agent from a different space', async () => { let err: Error | undefined; try { await apiClient.updateAgent(testSpaceAgent1, { tags: ['foo'] }); @@ -137,7 +146,7 @@ export default function (providerContext: FtrProviderContext) { await apiClient.deleteAgent(testSpaceAgent3, TEST_SPACE_1); }); - it('should not allow to delete an agent from a different space from the default space', async () => { + it('should not allow deleting an agent from a different space', async () => { let err: Error | undefined; try { await apiClient.deleteAgent(testSpaceAgent1); @@ -227,5 +236,131 @@ export default function (providerContext: FtrProviderContext) { expect(agentInTestSpaceTags[testSpaceAgent2]).to.eql(['tag1']); }); }); + + describe('POST /agents/{agentId}/upgrade', () => { + beforeEach(async () => { + await cleanFleetAgents(esClient); + await createAgents(); + }); + + it('should allow upgrading an agent in the same space', async () => { + await makeAgentsUpgradeable(esClient, [testSpaceAgent1], '8.14.0'); + await apiClient.upgradeAgent(testSpaceAgent1, { version: '8.15.0' }, TEST_SPACE_1); + }); + + it('should forbid upgrading an agent from a different space', async () => { + await makeAgentsUpgradeable(esClient, [testSpaceAgent1], '8.14.0'); + const res = await supertest + .post(`/api/fleet/agents/${testSpaceAgent1}/upgrade`) + .set('kbn-xsrf', 'xxxx') + .send({ version: '8.15.0' }) + .expect(404); + expect(res.body.message).to.eql(`${testSpaceAgent1} not found in namespace`); + }); + }); + + describe('POST /agents/bulk_upgrade', () => { + beforeEach(async () => { + await cleanFleetAgents(esClient); + await createAgents(); + }); + + function getAgentStatus(agents: GetAgentsResponse) { + return agents.items?.reduce((acc, item) => { + acc[item.id] = item.status; + return acc; + }, {} as any); + } + + it('should only upgrade agents in the same space when passing a list of agent ids', async () => { + makeAgentsUpgradeable( + esClient, + [defaultSpaceAgent1, defaultSpaceAgent2, testSpaceAgent1, testSpaceAgent2], + '8.14.0' + ); + + let agents = await apiClient.getAgents(); + let agentStatus = getAgentStatus(agents); + expect(agentStatus).to.eql({ + [defaultSpaceAgent1]: 'online', + [defaultSpaceAgent2]: 'online', + }); + + agents = await apiClient.getAgents(TEST_SPACE_1); + agentStatus = getAgentStatus(agents); + expect(agentStatus).to.eql({ + [testSpaceAgent1]: 'online', + [testSpaceAgent2]: 'online', + }); + + await apiClient.bulkUpgradeAgents( + { + agents: [defaultSpaceAgent1, testSpaceAgent1], + version: '8.15.0', + skipRateLimitCheck: true, + }, + TEST_SPACE_1 + ); + + agents = await apiClient.getAgents(); + agentStatus = getAgentStatus(agents); + expect(agentStatus).to.eql({ + [defaultSpaceAgent1]: 'online', + [defaultSpaceAgent2]: 'online', + }); + + agents = await apiClient.getAgents(TEST_SPACE_1); + agentStatus = getAgentStatus(agents); + expect(agentStatus).to.eql({ + [testSpaceAgent1]: 'updating', + [testSpaceAgent2]: 'online', + }); + }); + + it('should only upgrade agents in the same space when passing a kuery', async () => { + makeAgentsUpgradeable( + esClient, + [defaultSpaceAgent1, defaultSpaceAgent2, testSpaceAgent1, testSpaceAgent2], + '8.14.0' + ); + + let agents = await apiClient.getAgents(); + let agentStatus = getAgentStatus(agents); + expect(agentStatus).to.eql({ + [defaultSpaceAgent1]: 'online', + [defaultSpaceAgent2]: 'online', + }); + + agents = await apiClient.getAgents(TEST_SPACE_1); + agentStatus = getAgentStatus(agents); + expect(agentStatus).to.eql({ + [testSpaceAgent1]: 'online', + [testSpaceAgent2]: 'online', + }); + + await apiClient.bulkUpgradeAgents( + { + agents: 'status:online', + version: '8.15.0', + skipRateLimitCheck: true, + }, + TEST_SPACE_1 + ); + + agents = await apiClient.getAgents(); + agentStatus = getAgentStatus(agents); + expect(agentStatus).to.eql({ + [defaultSpaceAgent1]: 'online', + [defaultSpaceAgent2]: 'online', + }); + + agents = await apiClient.getAgents(TEST_SPACE_1); + agentStatus = getAgentStatus(agents); + expect(agentStatus).to.eql({ + [testSpaceAgent1]: 'updating', + [testSpaceAgent2]: 'updating', + }); + }); + }); }); } diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts index 11fd693d9340b..c24d4db6987cf 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts @@ -206,6 +206,22 @@ export class SpaceTestApiClient { return res; } + async upgradeAgent(agentId: string, data: any, spaceId?: string) { + this.supertest + .post(`${this.getBaseUrl(spaceId)}/api/fleet/agents/${agentId}/upgrade`) + .set('kbn-xsrf', 'xxxx') + .send(data) + .expect(200); + } + async bulkUpgradeAgents(data: any, spaceId?: string) { + const { body: res } = await this.supertest + .post(`${this.getBaseUrl(spaceId)}/api/fleet/agents/bulk_upgrade`) + .set('kbn-xsrf', 'xxxx') + .send(data) + .expect(200); + + return res; + } async bulkUpdateAgentTags(data: any, spaceId?: string) { const { body: res } = await this.supertest .post(`${this.getBaseUrl(spaceId)}/api/fleet/agents/bulk_update_agent_tags`) @@ -312,7 +328,6 @@ export class SpaceTestApiClient { return res; } - async postNewAgentAction(agentId: string, spaceId?: string): Promise { const { body: res } = await this.supertest .post(`${this.getBaseUrl(spaceId)}/api/fleet/agents/${agentId}/actions`) @@ -322,4 +337,12 @@ export class SpaceTestApiClient { return res; } + async cancelAction(actionId: string, spaceId?: string): Promise { + const { body: res } = await this.supertest + .post(`${this.getBaseUrl(spaceId)}/api/fleet/agents/actions/${actionId}/cancel`) + .set('kbn-xsrf', 'xxxx') + .expect(200); + + return res; + } } diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/helpers.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/helpers.ts index eacee0b41b6ac..d9f2e111344da 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/helpers.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/helpers.ts @@ -14,6 +14,7 @@ import { AGENT_POLICY_INDEX, } from '@kbn/fleet-plugin/common'; import { ENROLLMENT_API_KEYS_INDEX } from '@kbn/fleet-plugin/common/constants'; +import { asyncForEach } from '@kbn/std'; const ES_INDEX_OPTIONS = { headers: { 'X-elastic-product-origin': 'fleet' } }; @@ -34,6 +35,15 @@ export async function cleanFleetIndices(esClient: Client) { ]); } +export async function cleanFleetAgents(esClient: Client) { + await esClient.deleteByQuery({ + index: AGENTS_INDEX, + q: '*', + ignore_unavailable: true, + refresh: true, + }); +} + export async function cleanFleetActionIndices(esClient: Client) { try { await Promise.all([ @@ -62,11 +72,7 @@ export async function cleanFleetActionIndices(esClient: Client) { } } -export const createFleetAgent = async ( - esClient: Client, - agentPolicyId: string, - spaceId?: string -) => { +export async function createFleetAgent(esClient: Client, agentPolicyId: string, spaceId?: string) { const agentResponse = await esClient.index({ index: '.fleet-agents', refresh: true, @@ -90,4 +96,19 @@ export const createFleetAgent = async ( }); return agentResponse._id; -}; +} + +export async function makeAgentsUpgradeable(esClient: Client, agentIds: string[], version: string) { + await asyncForEach(agentIds, async (agentId) => { + await esClient.update({ + id: agentId, + refresh: 'wait_for', + index: AGENTS_INDEX, + body: { + doc: { + local_metadata: { elastic: { agent: { upgradeable: true, version } } }, + }, + }, + }); + }); +} From d2cff5d7c3f5d7b741bf459d8e70013c0aecf704 Mon Sep 17 00:00:00 2001 From: jillguyonnet Date: Thu, 8 Aug 2024 15:10:21 +0200 Subject: [PATCH 02/12] Refactor and add reassign endpoints --- .../fleet/server/routes/agent/handlers.ts | 2 +- .../fleet/server/routes/agent/index.ts | 4 +- .../fleet/server/services/agents/crud.ts | 5 +- .../fleet/server/services/agents/reassign.ts | 80 ++++++++++--------- .../services/agents/update_agent_tags.ts | 9 +-- .../fleet/server/services/agents/upgrade.ts | 8 +- 6 files changed, 53 insertions(+), 55 deletions(-) diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index 8ff3f82b7e6c6..b30e3b0f603e5 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -266,7 +266,7 @@ export const putAgentsReassignHandlerDeprecated: RequestHandler< } }; -export const postAgentsReassignHandler: RequestHandler< +export const postAgentReassignHandler: RequestHandler< TypeOf, undefined, TypeOf diff --git a/x-pack/plugins/fleet/server/routes/agent/index.ts b/x-pack/plugins/fleet/server/routes/agent/index.ts index 6c55835a1ed94..7d64bf365f74b 100644 --- a/x-pack/plugins/fleet/server/routes/agent/index.ts +++ b/x-pack/plugins/fleet/server/routes/agent/index.ts @@ -59,7 +59,7 @@ import { getAgentUploadsHandler, getAgentUploadFileHandler, deleteAgentUploadFileHandler, - postAgentsReassignHandler, + postAgentReassignHandler, postRetrieveAgentsByActionsHandler, } from './handlers'; import { @@ -271,7 +271,7 @@ export const registerAPIRoutes = (router: FleetAuthzRouter, config: FleetConfigT version: API_VERSIONS.public.v1, validate: { request: PostAgentReassignRequestSchema }, }, - postAgentsReassignHandler + postAgentReassignHandler ); router.versioned diff --git a/x-pack/plugins/fleet/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts index 64c20cbbc4d6b..8b157a3e393b0 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.ts @@ -30,6 +30,8 @@ import { auditLoggingService } from '../audit_logging'; import { isAgentInNamespace } from '../spaces/agent_namespaces'; import { getCurrentNamespace } from '../spaces/get_current_namespace'; +import { addNamespaceFilteringToQuery } from '../spaces/query_namespaces_filtering'; + import { searchHitToAgent, agentSOAttributesToFleetServerAgentDoc } from './helpers'; import { buildAgentStatusRuntimeField } from './build_status_runtime_field'; import { getLatestAvailableAgentVersion } from './versions'; @@ -431,6 +433,7 @@ async function _filterAgents( }> { const { page = 1, perPage = 20, sortField = 'enrolled_at', sortOrder = 'desc' } = options; const runtimeFields = await buildAgentStatusRuntimeField(soClient); + const currentNameSpace = getCurrentNamespace(soClient); let res; try { @@ -442,7 +445,7 @@ async function _filterAgents( runtime_mappings: runtimeFields, fields: Object.keys(runtimeFields), sort: [{ [sortField]: { order: sortOrder } }], - query: { bool: { filter: query } }, + query: addNamespaceFilteringToQuery({ bool: { filter: [query] } }, currentNameSpace), index: AGENTS_INDEX, ignore_unavailable: true, }); diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.ts b/x-pack/plugins/fleet/server/services/agents/reassign.ts index 0a5c6f9b51ee0..173913cf87d42 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.ts @@ -6,6 +6,8 @@ */ import type { SavedObjectsClientContract, ElasticsearchClient } from '@kbn/core/server'; +import { SavedObjectNotFound } from '@kbn/kibana-utils-plugin/common'; + import type { Agent } from '../../types'; import { agentPolicyService } from '../agent_policy'; import { @@ -16,52 +18,54 @@ import { import { SO_SEARCH_LIMIT } from '../../constants'; +import { agentsKueryNamespaceFilter } from '../spaces/agent_namespaces'; +import { getCurrentNamespace } from '../spaces/get_current_namespace'; + import { getAgentsById, getAgentPolicyForAgent, updateAgent, getAgentsByKuery, openPointInTime, + getAgentById, } from './crud'; import type { GetAgentsOptions } from '.'; import { createAgentAction } from './actions'; import { ReassignActionRunner, reassignBatch } from './reassign_action_runner'; -export async function reassignAgent( +async function verifyNewAgentPolicy( soClient: SavedObjectsClientContract, - esClient: ElasticsearchClient, - agentId: string, newAgentPolicyId: string ) { - const newAgentPolicy = await agentPolicyService.get(soClient, newAgentPolicyId); + let newAgentPolicy; + try { + newAgentPolicy = await agentPolicyService.get(soClient, newAgentPolicyId); + } catch (err) { + if (err instanceof SavedObjectNotFound) { + throw new AgentPolicyNotFoundError(`Agent policy not found: ${newAgentPolicyId}`); + } + } if (!newAgentPolicy) { throw new AgentPolicyNotFoundError(`Agent policy not found: ${newAgentPolicyId}`); } - - await reassignAgentIsAllowed(soClient, esClient, agentId, newAgentPolicyId); - - await updateAgent(esClient, agentId, { - policy_id: newAgentPolicyId, - policy_revision: null, - }); - - await createAgentAction(esClient, { - agents: [agentId], - created_at: new Date().toISOString(), - type: 'POLICY_REASSIGN', - data: { - policy_id: newAgentPolicyId, - }, - }); + if (newAgentPolicy?.is_managed) { + throw new HostedAgentPolicyRestrictionRelatedError( + `Cannot reassign agents to hosted agent policy ${newAgentPolicy.id}` + ); + } } -export async function reassignAgentIsAllowed( +export async function reassignAgent( soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, agentId: string, newAgentPolicyId: string ) { + await verifyNewAgentPolicy(soClient, newAgentPolicyId); + + await getAgentById(esClient, soClient, agentId); // throw 404 if agent not in namespace + const agentPolicy = await getAgentPolicyForAgent(soClient, esClient, agentId); if (agentPolicy?.is_managed) { throw new HostedAgentPolicyRestrictionRelatedError( @@ -69,14 +73,19 @@ export async function reassignAgentIsAllowed( ); } - const newAgentPolicy = await agentPolicyService.get(soClient, newAgentPolicyId); - if (newAgentPolicy?.is_managed) { - throw new HostedAgentPolicyRestrictionRelatedError( - `Cannot reassign an agent to hosted agent policy ${newAgentPolicy.id}` - ); - } + await updateAgent(esClient, agentId, { + policy_id: newAgentPolicyId, + policy_revision: null, + }); - return true; + await createAgentAction(esClient, { + agents: [agentId], + created_at: new Date().toISOString(), + type: 'POLICY_REASSIGN', + data: { + policy_id: newAgentPolicyId, + }, + }); } export async function reassignAgents( @@ -88,15 +97,7 @@ export async function reassignAgents( }, newAgentPolicyId: string ): Promise<{ actionId: string }> { - const newAgentPolicy = await agentPolicyService.get(soClient, newAgentPolicyId); - if (!newAgentPolicy) { - throw new AgentPolicyNotFoundError(`Agent policy not found: ${newAgentPolicyId}`); - } - if (newAgentPolicy.is_managed) { - throw new HostedAgentPolicyRestrictionRelatedError( - `Cannot reassign an agent to hosted agent policy ${newAgentPolicy.id}` - ); - } + await verifyNewAgentPolicy(soClient, newAgentPolicyId); const outgoingErrors: Record = {}; let givenAgents: Agent[] = []; @@ -115,8 +116,11 @@ export async function reassignAgents( } } else if ('kuery' in options) { const batchSize = options.batchSize ?? SO_SEARCH_LIMIT; + const currentNameSpace = getCurrentNamespace(soClient); + const namespaceFilter = agentsKueryNamespaceFilter(currentNameSpace); + const kuery = namespaceFilter ? `${namespaceFilter} AND ${options.kuery}` : options.kuery; const res = await getAgentsByKuery(esClient, soClient, { - kuery: options.kuery, + kuery, showInactive: options.showInactive ?? false, page: 1, perPage: batchSize, diff --git a/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts b/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts index f3443458249b7..ed4108c953bb6 100644 --- a/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts +++ b/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts @@ -12,7 +12,7 @@ import { AgentReassignmentError } from '../../errors'; import { SO_SEARCH_LIMIT } from '../../constants'; -import { agentsKueryNamespaceFilter, isAgentInNamespace } from '../spaces/agent_namespaces'; +import { agentsKueryNamespaceFilter } from '../spaces/agent_namespaces'; import { getCurrentNamespace } from '../spaces/get_current_namespace'; @@ -29,7 +29,6 @@ export async function updateAgentTags( ): Promise<{ actionId: string }> { const outgoingErrors: Record = {}; const givenAgents: Agent[] = []; - const currentNameSpace = getCurrentNamespace(soClient); if ('agentIds' in options) { const maybeAgents = await getAgentsById(esClient, soClient, options.agentIds); @@ -38,17 +37,13 @@ export async function updateAgentTags( outgoingErrors[maybeAgent.id] = new AgentReassignmentError( `Cannot find agent ${maybeAgent.id}` ); - } else if (!isAgentInNamespace(maybeAgent, currentNameSpace)) { - outgoingErrors[maybeAgent.id] = new AgentReassignmentError( - `Agent ${maybeAgent.id} is not in the current space` - ); } else { givenAgents.push(maybeAgent); } } } else if ('kuery' in options) { const batchSize = options.batchSize ?? SO_SEARCH_LIMIT; - + const currentNameSpace = getCurrentNamespace(soClient); const namespaceFilter = agentsKueryNamespaceFilter(currentNameSpace); const filters = namespaceFilter ? [namespaceFilter] : []; if (options.kuery !== '') { diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.ts index 586f19c8af3fd..1108e48f0d32f 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade.ts @@ -10,7 +10,7 @@ import type { Agent } from '../../types'; import { AgentReassignmentError, HostedAgentPolicyRestrictionRelatedError } from '../../errors'; import { SO_SEARCH_LIMIT } from '../../constants'; -import { agentsKueryNamespaceFilter, isAgentInNamespace } from '../spaces/agent_namespaces'; +import { agentsKueryNamespaceFilter } from '../spaces/agent_namespaces'; import { getCurrentNamespace } from '../spaces/get_current_namespace'; import { createAgentAction } from './actions'; @@ -79,7 +79,6 @@ export async function sendUpgradeAgentsActions( // Full set of agents const outgoingErrors: Record = {}; let givenAgents: Agent[] = []; - const currentNameSpace = getCurrentNamespace(soClient); if ('agents' in options) { givenAgents = options.agents; @@ -90,16 +89,13 @@ export async function sendUpgradeAgentsActions( outgoingErrors[maybeAgent.id] = new AgentReassignmentError( `Cannot find agent ${maybeAgent.id}` ); - } else if (!isAgentInNamespace(maybeAgent, currentNameSpace)) { - outgoingErrors[maybeAgent.id] = new AgentReassignmentError( - `Agent ${maybeAgent.id} is not in the current space` - ); } else { givenAgents.push(maybeAgent); } } } else if ('kuery' in options) { const batchSize = options.batchSize ?? SO_SEARCH_LIMIT; + const currentNameSpace = getCurrentNamespace(soClient); const namespaceFilter = agentsKueryNamespaceFilter(currentNameSpace); const kuery = namespaceFilter ? `${namespaceFilter} AND ${options.kuery}` : options.kuery; From 0c26e880bacbf7cb00dad6e1fb5d36ce374e3536 Mon Sep 17 00:00:00 2001 From: jillguyonnet Date: Thu, 8 Aug 2024 16:59:28 +0200 Subject: [PATCH 03/12] Fix space id in batch functions --- .../fleet/server/services/agents/reassign.ts | 11 +- .../services/agents/reassign_action_runner.ts | 7 +- .../services/agents/update_agent_tags.ts | 17 ++- .../agents/update_agent_tags_action_runner.ts | 12 +- .../fleet/server/services/agents/upgrade.ts | 11 +- .../services/agents/upgrade_action_runner.ts | 8 +- .../apis/space_awareness/agents.ts | 143 +++++++++++++++++- .../apis/space_awareness/api_helper.ts | 22 ++- 8 files changed, 198 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.ts b/x-pack/plugins/fleet/server/services/agents/reassign.ts index 173913cf87d42..44d696bd44342 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.ts @@ -99,6 +99,7 @@ export async function reassignAgents( ): Promise<{ actionId: string }> { await verifyNewAgentPolicy(soClient, newAgentPolicyId); + const currentNameSpace = getCurrentNamespace(soClient); const outgoingErrors: Record = {}; let givenAgents: Agent[] = []; if ('agents' in options) { @@ -116,7 +117,6 @@ export async function reassignAgents( } } else if ('kuery' in options) { const batchSize = options.batchSize ?? SO_SEARCH_LIMIT; - const currentNameSpace = getCurrentNamespace(soClient); const namespaceFilter = agentsKueryNamespaceFilter(currentNameSpace); const kuery = namespaceFilter ? `${namespaceFilter} AND ${options.kuery}` : options.kuery; const res = await getAgentsByKuery(esClient, soClient, { @@ -143,5 +143,12 @@ export async function reassignAgents( } } - return await reassignBatch(soClient, esClient, { newAgentPolicyId }, givenAgents, outgoingErrors); + return await reassignBatch( + soClient, + esClient, + { newAgentPolicyId }, + givenAgents, + outgoingErrors, + currentNameSpace + ); } diff --git a/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts index b03146ab6b387..f4df61c4f4972 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts @@ -43,7 +43,8 @@ export async function reassignBatch( total?: number; }, givenAgents: Agent[], - outgoingErrors: Record + outgoingErrors: Record, + spaceId?: string ): Promise<{ actionId: string }> { const errors: Record = { ...outgoingErrors }; @@ -86,8 +87,9 @@ export async function reassignBatch( const actionId = options.actionId ?? uuidv4(); const total = options.total ?? givenAgents.length; - const now = new Date().toISOString(); + const namespaces = spaceId ? { namespaces: [spaceId] } : {}; + await createAgentAction(esClient, { id: actionId, agents: agentsToUpdate.map((agent) => agent.id), @@ -97,6 +99,7 @@ export async function reassignBatch( data: { policy_id: options.newAgentPolicyId, }, + ...namespaces, }); await createErrorActionResults( diff --git a/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts b/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts index ed4108c953bb6..855fc8f83684e 100644 --- a/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts +++ b/x-pack/plugins/fleet/server/services/agents/update_agent_tags.ts @@ -27,6 +27,7 @@ export async function updateAgentTags( tagsToAdd: string[], tagsToRemove: string[] ): Promise<{ actionId: string }> { + const currentNameSpace = getCurrentNamespace(soClient); const outgoingErrors: Record = {}; const givenAgents: Agent[] = []; @@ -43,7 +44,6 @@ export async function updateAgentTags( } } else if ('kuery' in options) { const batchSize = options.batchSize ?? SO_SEARCH_LIMIT; - const currentNameSpace = getCurrentNamespace(soClient); const namespaceFilter = agentsKueryNamespaceFilter(currentNameSpace); const filters = namespaceFilter ? [namespaceFilter] : []; if (options.kuery !== '') { @@ -81,8 +81,15 @@ export async function updateAgentTags( ).runActionAsyncWithRetry(); } - return await updateTagsBatch(soClient, esClient, givenAgents, outgoingErrors, { - tagsToAdd, - tagsToRemove, - }); + return await updateTagsBatch( + soClient, + esClient, + givenAgents, + outgoingErrors, + { + tagsToAdd, + tagsToRemove, + }, + currentNameSpace + ); } diff --git a/x-pack/plugins/fleet/server/services/agents/update_agent_tags_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/update_agent_tags_action_runner.ts index 8b68e1b6e9fd8..bb3b5f71cb222 100644 --- a/x-pack/plugins/fleet/server/services/agents/update_agent_tags_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/update_agent_tags_action_runner.ts @@ -17,8 +17,6 @@ import { appContextService } from '../app_context'; import { FleetError } from '../../errors'; -import { getCurrentNamespace } from '../spaces/get_current_namespace'; - import { ActionRunner } from './action_runner'; import { BulkActionTaskType } from './bulk_action_types'; @@ -63,7 +61,8 @@ export async function updateTagsBatch( total?: number; kuery?: string; retryCount?: number; - } + }, + spaceId?: string ): Promise<{ actionId: string; updated?: number; took?: number }> { const errors: Record = { ...outgoingErrors }; const hostedAgentError = `Cannot modify tags on a hosted agent`; @@ -151,8 +150,7 @@ export async function updateTagsBatch( const versionConflictCount = res.version_conflicts ?? 0; const versionConflictIds = isLastRetry ? getUuidArray(versionConflictCount) : []; - const currentNameSpace = getCurrentNamespace(soClient); - const namespaces = currentNameSpace ? { namespaces: [currentNameSpace] } : {}; + const namespaces = spaceId ? { namespaces: [spaceId] } : {}; // creating an action doc so that update tags shows up in activity // the logic only saves agent count in the action that updated, failed or in case of last retry, conflicted @@ -195,7 +193,7 @@ export async function updateTagsBatch( failures.map((failure) => ({ agentId: failure.id, actionId, - namespace: currentNameSpace, + namespace: spaceId, error: failure.cause.reason, })) ); @@ -210,7 +208,7 @@ export async function updateTagsBatch( versionConflictIds.map((id) => ({ agentId: id, actionId, - namespace: currentNameSpace, + namespace: spaceId, error: 'version conflict on last retry', })) ); diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.ts index 1108e48f0d32f..90ef8cafde40e 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade.ts @@ -76,6 +76,7 @@ export async function sendUpgradeAgentsActions( batchSize?: number; } ): Promise<{ actionId: string }> { + const currentNameSpace = getCurrentNamespace(soClient); // Full set of agents const outgoingErrors: Record = {}; let givenAgents: Agent[] = []; @@ -95,7 +96,6 @@ export async function sendUpgradeAgentsActions( } } else if ('kuery' in options) { const batchSize = options.batchSize ?? SO_SEARCH_LIMIT; - const currentNameSpace = getCurrentNamespace(soClient); const namespaceFilter = agentsKueryNamespaceFilter(currentNameSpace); const kuery = namespaceFilter ? `${namespaceFilter} AND ${options.kuery}` : options.kuery; @@ -122,5 +122,12 @@ export async function sendUpgradeAgentsActions( } } - return await upgradeBatch(soClient, esClient, givenAgents, outgoingErrors, options); + return await upgradeBatch( + soClient, + esClient, + givenAgents, + outgoingErrors, + options, + currentNameSpace + ); } diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts index 537ea71d7c469..70bba0181880f 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts @@ -22,8 +22,6 @@ import { HostedAgentPolicyRestrictionRelatedError, FleetError } from '../../erro import { appContextService } from '../app_context'; -import { getCurrentNamespace } from '../spaces/get_current_namespace'; - import { ActionRunner } from './action_runner'; import type { GetAgentsOptions } from './crud'; @@ -67,7 +65,8 @@ export async function upgradeBatch( upgradeDurationSeconds?: number; startTime?: string; total?: number; - } + }, + spaceId?: string ): Promise<{ actionId: string }> { const errors: Record = { ...outgoingErrors }; @@ -169,8 +168,7 @@ export async function upgradeBatch( const actionId = options.actionId ?? uuidv4(); const total = options.total ?? givenAgents.length; - const currentNameSpace = getCurrentNamespace(soClient); - const namespaces = currentNameSpace ? { namespaces: [currentNameSpace] } : {}; + const namespaces = spaceId ? { namespaces: [spaceId] } : {}; await createAgentAction(esClient, { id: actionId, diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts index 5ff9b20012bf3..aa63d9877fec8 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts @@ -24,7 +24,7 @@ export default function (providerContext: FtrProviderContext) { const esClient = getService('es'); const kibanaServer = getService('kibanaServer'); - describe('agents', async function () { + describe('agents TMPDEBUGME', async function () { skipIfNoDockerRegistry(providerContext); const apiClient = new SpaceTestApiClient(supertest); @@ -46,6 +46,7 @@ export default function (providerContext: FtrProviderContext) { setupTestSpaces(providerContext); let defaultSpacePolicy1: CreateAgentPolicyResponse; + let defaultSpacePolicy2: CreateAgentPolicyResponse; let spaceTest1Policy1: CreateAgentPolicyResponse; let spaceTest1Policy2: CreateAgentPolicyResponse; @@ -69,12 +70,15 @@ export default function (providerContext: FtrProviderContext) { } before(async () => { - const [_defaultSpacePolicy1, _spaceTest1Policy1, _spaceTest1Policy2] = await Promise.all([ - apiClient.createAgentPolicy(), - apiClient.createAgentPolicy(TEST_SPACE_1), - apiClient.createAgentPolicy(TEST_SPACE_1), - ]); + const [_defaultSpacePolicy1, _defaultSpacePolicy2, _spaceTest1Policy1, _spaceTest1Policy2] = + await Promise.all([ + apiClient.createAgentPolicy(), + apiClient.createAgentPolicy(), + apiClient.createAgentPolicy(TEST_SPACE_1), + apiClient.createAgentPolicy(TEST_SPACE_1), + ]); defaultSpacePolicy1 = _defaultSpacePolicy1; + defaultSpacePolicy2 = _defaultSpacePolicy2; spaceTest1Policy1 = _spaceTest1Policy1; spaceTest1Policy2 = _spaceTest1Policy2; @@ -362,5 +366,132 @@ export default function (providerContext: FtrProviderContext) { }); }); }); + + describe('POST /agents/{agentId}/reassign', () => { + it('should allow reassigning an agent in the current space to a policy in the current space', async () => { + let agent = await apiClient.getAgent(defaultSpaceAgent1); + expect(agent.item.policy_id).to.eql(defaultSpacePolicy1.item.id); + await apiClient.reassignAgent(defaultSpaceAgent1, defaultSpacePolicy2.item.id); + agent = await apiClient.getAgent(defaultSpaceAgent1); + expect(agent.item.policy_id).to.eql(defaultSpacePolicy2.item.id); + + agent = await apiClient.getAgent(testSpaceAgent1, TEST_SPACE_1); + expect(agent.item.policy_id).to.eql(spaceTest1Policy1.item.id); + await apiClient.reassignAgent(testSpaceAgent1, spaceTest1Policy2.item.id, TEST_SPACE_1); + agent = await apiClient.getAgent(testSpaceAgent1, TEST_SPACE_1); + expect(agent.item.policy_id).to.eql(spaceTest1Policy2.item.id); + + await apiClient.reassignAgent(defaultSpaceAgent1, defaultSpacePolicy1.item.id); + await apiClient.reassignAgent(testSpaceAgent1, spaceTest1Policy1.item.id, TEST_SPACE_1); + }); + + it('should not allow reassigning an agent in a different space', async () => { + let err: Error | undefined; + try { + await apiClient.reassignAgent(testSpaceAgent1, defaultSpacePolicy2.item.id); + } catch (_err) { + err = _err; + } + + expect(err).to.be.an(Error); + expect(err?.message).to.match(/404 "Not Found"/); + }); + + it('should not allow reassigning an agent in the current space to a policy in a different space', async () => { + let err: Error | undefined; + try { + await apiClient.reassignAgent(defaultSpaceAgent1, spaceTest1Policy2.item.id); + } catch (_err) { + err = _err; + } + + expect(err).to.be.an(Error); + expect(err?.message).to.match(/404 "Not Found"/); + }); + }); + + describe('POST /agents/bulk_reassign', () => { + function getAgentPolicyIds(agents: GetAgentsResponse) { + return agents.items?.reduce((acc, item) => { + acc[item.id] = item.policy_id; + return acc; + }, {} as any); + } + + it('should return 404 if the policy is in another space', async () => { + let err: Error | undefined; + try { + await apiClient.bulkReassignAgents({ + agents: [defaultSpaceAgent1, testSpaceAgent1], + policy_id: spaceTest1Policy2.item.id, + }); + } catch (_err) { + err = _err; + } + + expect(err).to.be.an(Error); + expect(err?.message).to.match(/404 "Not Found"/); + }); + + it('should only reassign agents in the same space when passing a list of agent ids', async () => { + let agent = await apiClient.getAgent(defaultSpaceAgent1); + expect(agent.item.policy_id).to.eql(defaultSpacePolicy1.item.id); + agent = await apiClient.getAgent(testSpaceAgent1, TEST_SPACE_1); + expect(agent.item.policy_id).to.eql(spaceTest1Policy1.item.id); + + await apiClient.bulkReassignAgents( + { + agents: [defaultSpaceAgent1, testSpaceAgent1], + policy_id: spaceTest1Policy2.item.id, + }, + TEST_SPACE_1 + ); + + agent = await apiClient.getAgent(defaultSpaceAgent1); + expect(agent.item.policy_id).to.eql(defaultSpacePolicy1.item.id); + agent = await apiClient.getAgent(testSpaceAgent1, TEST_SPACE_1); + expect(agent.item.policy_id).to.eql(spaceTest1Policy2.item.id); + + await apiClient.reassignAgent(testSpaceAgent1, spaceTest1Policy1.item.id, TEST_SPACE_1); + }); + + it('should only reassign agents in the same space when passing a kuery', async () => { + let agents = await apiClient.getAgents(); + let agentPolicyIds = getAgentPolicyIds(agents); + expect(agentPolicyIds).to.eql({ + [defaultSpaceAgent1]: defaultSpacePolicy1.item.id, + [defaultSpaceAgent2]: defaultSpacePolicy2.item.id, + }); + agents = await apiClient.getAgents(TEST_SPACE_1); + agentPolicyIds = getAgentPolicyIds(agents); + expect(agentPolicyIds).to.eql({ + [testSpaceAgent1]: spaceTest1Policy1.item.id, + [testSpaceAgent2]: spaceTest1Policy2.item.id, + }); + + await apiClient.bulkReassignAgents( + { + agents: 'status:online', + policy_id: spaceTest1Policy2.item.id, + }, + TEST_SPACE_1 + ); + + agents = await apiClient.getAgents(); + agentPolicyIds = getAgentPolicyIds(agents); + expect(agentPolicyIds).to.eql({ + [defaultSpaceAgent1]: defaultSpacePolicy1.item.id, + [defaultSpaceAgent2]: defaultSpacePolicy2.item.id, + }); + agents = await apiClient.getAgents(TEST_SPACE_1); + agentPolicyIds = getAgentPolicyIds(agents); + expect(agentPolicyIds).to.eql({ + [testSpaceAgent1]: spaceTest1Policy2.item.id, + [testSpaceAgent2]: spaceTest1Policy2.item.id, + }); + + await apiClient.reassignAgent(testSpaceAgent1, spaceTest1Policy1.item.id, TEST_SPACE_1); + }); + }); }); } diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts index c24d4db6987cf..1a8d8c9f2e522 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts @@ -206,21 +206,35 @@ export class SpaceTestApiClient { return res; } + async reassignAgent(agentId: string, policyId: string, spaceId?: string) { + await this.supertest + .post(`${this.getBaseUrl(spaceId)}/api/fleet/agents/${agentId}/reassign`) + .set('kbn-xsrf', 'xxx') + .send({ + policy_id: policyId, + }) + .expect(200); + } + async bulkReassignAgents(data: any, spaceId?: string) { + await this.supertest + .post(`${this.getBaseUrl(spaceId)}/api/fleet/agents/bulk_reassign`) + .set('kbn-xsrf', 'xxxx') + .send(data) + .expect(200); + } async upgradeAgent(agentId: string, data: any, spaceId?: string) { - this.supertest + await this.supertest .post(`${this.getBaseUrl(spaceId)}/api/fleet/agents/${agentId}/upgrade`) .set('kbn-xsrf', 'xxxx') .send(data) .expect(200); } async bulkUpgradeAgents(data: any, spaceId?: string) { - const { body: res } = await this.supertest + await this.supertest .post(`${this.getBaseUrl(spaceId)}/api/fleet/agents/bulk_upgrade`) .set('kbn-xsrf', 'xxxx') .send(data) .expect(200); - - return res; } async bulkUpdateAgentTags(data: any, spaceId?: string) { const { body: res } = await this.supertest From 4f7c2e7afea142cd2d3ac89c36739d751b831a44 Mon Sep 17 00:00:00 2001 From: jillguyonnet Date: Thu, 8 Aug 2024 17:02:44 +0200 Subject: [PATCH 04/12] remove tmp comment --- .../test/fleet_api_integration/apis/space_awareness/agents.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts index aa63d9877fec8..d0f59f687f8ff 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts @@ -24,7 +24,7 @@ export default function (providerContext: FtrProviderContext) { const esClient = getService('es'); const kibanaServer = getService('kibanaServer'); - describe('agents TMPDEBUGME', async function () { + describe('agents', async function () { skipIfNoDockerRegistry(providerContext); const apiClient = new SpaceTestApiClient(supertest); From 25636c422a8d7e426b341a5498ddcfdcb65c8e7e Mon Sep 17 00:00:00 2001 From: jillguyonnet Date: Fri, 9 Aug 2024 10:33:17 +0200 Subject: [PATCH 05/12] Fix tests --- .../fleet/server/services/agents/crud.ts | 5 ---- .../services/agents/update_agent_tags.test.ts | 24 ------------------- .../apis/space_awareness/actions.ts | 6 ++--- .../apis/space_awareness/agents.ts | 6 ++--- 4 files changed, 5 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/agents/crud.ts b/x-pack/plugins/fleet/server/services/agents/crud.ts index 8b157a3e393b0..716a64050adbf 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud.ts @@ -27,7 +27,6 @@ import { FleetUnauthorizedError, } from '../../errors'; import { auditLoggingService } from '../audit_logging'; -import { isAgentInNamespace } from '../spaces/agent_namespaces'; import { getCurrentNamespace } from '../spaces/get_current_namespace'; import { addNamespaceFilteringToQuery } from '../spaces/query_namespaces_filtering'; @@ -408,10 +407,6 @@ export async function getAgentById( throw new AgentNotFoundError(`Agent ${agentId} not found`); } - if (!isAgentInNamespace(agentHit, getCurrentNamespace(soClient))) { - throw new AgentNotFoundError(`${agentHit.id} not found in namespace`); - } - return agentHit; } diff --git a/x-pack/plugins/fleet/server/services/agents/update_agent_tags.test.ts b/x-pack/plugins/fleet/server/services/agents/update_agent_tags.test.ts index 35163288e97dc..01dccb6af6105 100644 --- a/x-pack/plugins/fleet/server/services/agents/update_agent_tags.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/update_agent_tags.test.ts @@ -425,30 +425,6 @@ describe('update_agent_tags', () => { } as any); }); - it('should not update tags for agents in another space', async () => { - soClient.getCurrentNamespace.mockReturnValue('default'); - esClient.search.mockResolvedValue({ - hits: { - hits: [ - { - _id: 'agent1', - _source: { - tags: ['one', 'two', 'three'], - namespaces: ['myspace'], - }, - fields: { - status: 'online', - }, - }, - ], - }, - } as any); - - await updateAgentTags(soClient, esClient, { agentIds: ['agent1'] }, ['one'], ['two']); - - expect(esClient.updateByQuery).not.toHaveBeenCalled(); - }); - it('should add namespace filter to kuery in the default space', async () => { soClient.getCurrentNamespace.mockReturnValue('default'); diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/actions.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/actions.ts index 48008802d2e21..cbf19052627cc 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/actions.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/actions.ts @@ -218,16 +218,14 @@ export default function (providerContext: FtrProviderContext) { .set('kbn-xsrf', 'xxxx') .send({ action: { type: 'UNENROLL' } }) .expect(404); - expect(resInDefaultSpace.body.message).to.eql(`${testSpaceAgent1} not found in namespace`); + expect(resInDefaultSpace.body.message).to.eql(`Agent ${testSpaceAgent1} not found`); const resInCustomSpace = await supertest .post(`/s/${TEST_SPACE_1}/api/fleet/agents/${defaultSpaceAgent1}/actions`) .set('kbn-xsrf', 'xxxx') .send({ action: { type: 'UNENROLL' } }) .expect(404); - expect(resInCustomSpace.body.message).to.eql( - `${defaultSpaceAgent1} not found in namespace` - ); + expect(resInCustomSpace.body.message).to.eql(`Agent ${defaultSpaceAgent1} not found`); }); it('should create an action with set namespace in the default space', async () => { diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts index d0f59f687f8ff..eff2f61c8f5f9 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts @@ -59,7 +59,7 @@ export default function (providerContext: FtrProviderContext) { const [_defaultSpaceAgent1, _defaultSpaceAgent2, _testSpaceAgent1, _testSpaceAgent2] = await Promise.all([ createFleetAgent(esClient, defaultSpacePolicy1.item.id, 'default'), - createFleetAgent(esClient, defaultSpacePolicy1.item.id), + createFleetAgent(esClient, defaultSpacePolicy2.item.id), createFleetAgent(esClient, spaceTest1Policy1.item.id, TEST_SPACE_1), createFleetAgent(esClient, spaceTest1Policy2.item.id, TEST_SPACE_1), ]); @@ -259,7 +259,7 @@ export default function (providerContext: FtrProviderContext) { .set('kbn-xsrf', 'xxxx') .send({ version: '8.15.0' }) .expect(404); - expect(res.body.message).to.eql(`${testSpaceAgent1} not found in namespace`); + expect(res.body.message).to.eql(`Agent ${testSpaceAgent1} not found`); }); }); @@ -471,7 +471,7 @@ export default function (providerContext: FtrProviderContext) { await apiClient.bulkReassignAgents( { - agents: 'status:online', + agents: '*', policy_id: spaceTest1Policy2.item.id, }, TEST_SPACE_1 From d7ee85b13c8a8c266c198a0a0d2abb63a8c167e4 Mon Sep 17 00:00:00 2001 From: jillguyonnet Date: Fri, 9 Aug 2024 11:37:23 +0200 Subject: [PATCH 06/12] Add reassign action namespaces --- x-pack/plugins/fleet/server/services/agents/reassign.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.ts b/x-pack/plugins/fleet/server/services/agents/reassign.ts index 44d696bd44342..46f2f92966352 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.ts @@ -78,6 +78,9 @@ export async function reassignAgent( policy_revision: null, }); + const currentNameSpace = getCurrentNamespace(soClient); + const namespaces = currentNameSpace ? { namespaces: [currentNameSpace] } : {}; + await createAgentAction(esClient, { agents: [agentId], created_at: new Date().toISOString(), @@ -85,6 +88,7 @@ export async function reassignAgent( data: { policy_id: newAgentPolicyId, }, + ...namespaces, }); } From cc50db244911b363c07813a032c602d2839ca14b Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 14 Aug 2024 08:20:04 -0400 Subject: [PATCH 07/12] Fix test batch --- .../server/services/agents/action_runner.ts | 16 ++- .../services/agents/bulk_actions_resolver.ts | 2 + .../fleet/server/services/agents/reassign.ts | 7 +- .../services/agents/reassign_action_runner.ts | 9 +- .../fleet/server/services/agents/upgrade.ts | 1 + .../apis/space_awareness/agents.ts | 103 ++++++++++++++++-- .../apis/space_awareness/api_helper.ts | 4 +- 7 files changed, 119 insertions(+), 23 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/agents/action_runner.ts b/x-pack/plugins/fleet/server/services/agents/action_runner.ts index 3abe4787e6132..dc56543000506 100644 --- a/x-pack/plugins/fleet/server/services/agents/action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/action_runner.ts @@ -22,6 +22,7 @@ import { closePointInTime, getAgentsByKuery } from './crud'; import type { BulkActionsResolver } from './bulk_actions_resolver'; import type { RetryParams } from './retry_helper'; import { getRetryParams, MAX_RETRY_COUNT } from './retry_helper'; +import { agentsKueryNamespaceFilter } from '../spaces/agent_namespaces'; export interface ActionParams { kuery: string; @@ -29,6 +30,7 @@ export interface ActionParams { batchSize?: number; total?: number; actionId?: string; + spaceId?: string; // additional parameters specific to an action e.g. reassign to new policy id [key: string]: any; } @@ -184,7 +186,7 @@ export abstract class ActionRunner { } } - return await this.processAgents(agents); + return await this.processAgents(agents).catch(console.log); } async processAgentsInBatches(): Promise<{ actionId: string }> { @@ -195,15 +197,21 @@ export abstract class ActionRunner { appContextService.getLogger().debug('kuery: ' + this.actionParams.kuery); - const getAgents = () => - getAgentsByKuery(this.esClient, this.soClient, { - kuery: this.actionParams.kuery, + const getAgents = () => { + const namespaceFilter = agentsKueryNamespaceFilter(this.actionParams.spaceId); + const kuery = namespaceFilter + ? `${namespaceFilter} AND ${this.actionParams.kuery}` + : this.actionParams.kuery; + + return getAgentsByKuery(this.esClient, this.soClient, { + kuery, showInactive: this.actionParams.showInactive ?? false, page: 1, perPage, pitId, searchAfter: this.retryParams.searchAfter, }); + }; const res = await getAgents(); diff --git a/x-pack/plugins/fleet/server/services/agents/bulk_actions_resolver.ts b/x-pack/plugins/fleet/server/services/agents/bulk_actions_resolver.ts index b68bae611252a..152164f5ce2aa 100644 --- a/x-pack/plugins/fleet/server/services/agents/bulk_actions_resolver.ts +++ b/x-pack/plugins/fleet/server/services/agents/bulk_actions_resolver.ts @@ -49,6 +49,8 @@ export class BulkActionsResolver { [BulkActionTaskType.REQUEST_DIAGNOSTICS_RETRY]: RequestDiagnosticsActionRunner, }; + console.log(taskInstance); + return createRetryTask( taskInstance, getDeps, diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.ts b/x-pack/plugins/fleet/server/services/agents/reassign.ts index 46f2f92966352..2f463f530caa1 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.ts @@ -138,6 +138,7 @@ export async function reassignAgents( soClient, { ...options, + spaceId: currentNameSpace, batchSize, total: res.total, newAgentPolicyId, @@ -148,11 +149,9 @@ export async function reassignAgents( } return await reassignBatch( - soClient, esClient, - { newAgentPolicyId }, + { newAgentPolicyId, spaceId: currentNameSpace }, givenAgents, - outgoingErrors, - currentNameSpace + outgoingErrors ); } diff --git a/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts index f4df61c4f4972..25a1e63f7a167 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts @@ -22,7 +22,7 @@ import { BulkActionTaskType } from './bulk_action_types'; export class ReassignActionRunner extends ActionRunner { protected async processAgents(agents: Agent[]): Promise<{ actionId: string }> { - return await reassignBatch(this.soClient, this.esClient, this.actionParams! as any, agents, {}); + return await reassignBatch(this.esClient, this.actionParams! as any, agents, {}); } protected getTaskType() { @@ -35,17 +35,18 @@ export class ReassignActionRunner extends ActionRunner { } export async function reassignBatch( - soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, options: { newAgentPolicyId: string; actionId?: string; total?: number; + spaceId?: string; }, givenAgents: Agent[], - outgoingErrors: Record, - spaceId?: string + outgoingErrors: Record ): Promise<{ actionId: string }> { + const spaceId = options.spaceId; + const soClient = appContextService.getInternalUserSOClientForSpaceId(spaceId); const errors: Record = { ...outgoingErrors }; const hostedPolicies = await getHostedPolicies(soClient, givenAgents); diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.ts index 90ef8cafde40e..e919b4a7724d0 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade.ts @@ -116,6 +116,7 @@ export async function sendUpgradeAgentsActions( ...options, batchSize, total: res.total, + spaceId: currentNameSpace, }, { pitId: await openPointInTime(esClient) } ).runActionAsyncWithRetry(); diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts index eff2f61c8f5f9..88ea912895113 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts @@ -54,19 +54,27 @@ export default function (providerContext: FtrProviderContext) { let defaultSpaceAgent2: string; let testSpaceAgent1: string; let testSpaceAgent2: string; + let testSpaceAgent3: string; async function createAgents() { - const [_defaultSpaceAgent1, _defaultSpaceAgent2, _testSpaceAgent1, _testSpaceAgent2] = - await Promise.all([ - createFleetAgent(esClient, defaultSpacePolicy1.item.id, 'default'), - createFleetAgent(esClient, defaultSpacePolicy2.item.id), - createFleetAgent(esClient, spaceTest1Policy1.item.id, TEST_SPACE_1), - createFleetAgent(esClient, spaceTest1Policy2.item.id, TEST_SPACE_1), - ]); + const [ + _defaultSpaceAgent1, + _defaultSpaceAgent2, + _testSpaceAgent1, + _testSpaceAgent2, + _testSpaceAgent3, + ] = await Promise.all([ + createFleetAgent(esClient, defaultSpacePolicy1.item.id, 'default'), + createFleetAgent(esClient, defaultSpacePolicy2.item.id), + createFleetAgent(esClient, spaceTest1Policy1.item.id, TEST_SPACE_1), + createFleetAgent(esClient, spaceTest1Policy2.item.id, TEST_SPACE_1), + createFleetAgent(esClient, spaceTest1Policy1.item.id, TEST_SPACE_1), + ]); defaultSpaceAgent1 = _defaultSpaceAgent1; defaultSpaceAgent2 = _defaultSpaceAgent2; testSpaceAgent1 = _testSpaceAgent1; testSpaceAgent2 = _testSpaceAgent2; + testSpaceAgent3 = _testSpaceAgent3; } before(async () => { @@ -88,7 +96,7 @@ export default function (providerContext: FtrProviderContext) { describe('GET /agent', () => { it('should return agents in a specific space', async () => { const agents = await apiClient.getAgents(TEST_SPACE_1); - expect(agents.total).to.eql(2); + expect(agents.total).to.eql(3); const agentIds = agents.items?.map((item) => item.id); expect(agentIds).to.contain(testSpaceAgent1); expect(agentIds).to.contain(testSpaceAgent2); @@ -277,7 +285,7 @@ export default function (providerContext: FtrProviderContext) { } it('should only upgrade agents in the same space when passing a list of agent ids', async () => { - makeAgentsUpgradeable( + await makeAgentsUpgradeable( esClient, [defaultSpaceAgent1, defaultSpaceAgent2, testSpaceAgent1, testSpaceAgent2], '8.14.0' @@ -295,6 +303,7 @@ export default function (providerContext: FtrProviderContext) { expect(agentStatus).to.eql({ [testSpaceAgent1]: 'online', [testSpaceAgent2]: 'online', + [testSpaceAgent3]: 'online', }); await apiClient.bulkUpgradeAgents( @@ -318,11 +327,12 @@ export default function (providerContext: FtrProviderContext) { expect(agentStatus).to.eql({ [testSpaceAgent1]: 'updating', [testSpaceAgent2]: 'online', + [testSpaceAgent3]: 'online', }); }); it('should only upgrade agents in the same space when passing a kuery', async () => { - makeAgentsUpgradeable( + await makeAgentsUpgradeable( esClient, [defaultSpaceAgent1, defaultSpaceAgent2, testSpaceAgent1, testSpaceAgent2], '8.14.0' @@ -340,6 +350,7 @@ export default function (providerContext: FtrProviderContext) { expect(agentStatus).to.eql({ [testSpaceAgent1]: 'online', [testSpaceAgent2]: 'online', + [testSpaceAgent3]: 'online', }); await apiClient.bulkUpgradeAgents( @@ -363,11 +374,16 @@ export default function (providerContext: FtrProviderContext) { expect(agentStatus).to.eql({ [testSpaceAgent1]: 'updating', [testSpaceAgent2]: 'updating', + [testSpaceAgent3]: 'updating', }); }); }); describe('POST /agents/{agentId}/reassign', () => { + beforeEach(async () => { + await cleanFleetAgents(esClient); + await createAgents(); + }); it('should allow reassigning an agent in the current space to a policy in the current space', async () => { let agent = await apiClient.getAgent(defaultSpaceAgent1); expect(agent.item.policy_id).to.eql(defaultSpacePolicy1.item.id); @@ -411,6 +427,10 @@ export default function (providerContext: FtrProviderContext) { }); describe('POST /agents/bulk_reassign', () => { + beforeEach(async () => { + await cleanFleetAgents(esClient); + await createAgents(); + }); function getAgentPolicyIds(agents: GetAgentsResponse) { return agents.items?.reduce((acc, item) => { acc[item.id] = item.policy_id; @@ -467,6 +487,7 @@ export default function (providerContext: FtrProviderContext) { expect(agentPolicyIds).to.eql({ [testSpaceAgent1]: spaceTest1Policy1.item.id, [testSpaceAgent2]: spaceTest1Policy2.item.id, + [testSpaceAgent3]: spaceTest1Policy1.item.id, }); await apiClient.bulkReassignAgents( @@ -488,9 +509,71 @@ export default function (providerContext: FtrProviderContext) { expect(agentPolicyIds).to.eql({ [testSpaceAgent1]: spaceTest1Policy2.item.id, [testSpaceAgent2]: spaceTest1Policy2.item.id, + [testSpaceAgent3]: spaceTest1Policy2.item.id, }); await apiClient.reassignAgent(testSpaceAgent1, spaceTest1Policy1.item.id, TEST_SPACE_1); + await apiClient.reassignAgent(testSpaceAgent2, spaceTest1Policy1.item.id, TEST_SPACE_1); + }); + + it('should reassign agents in the same space by kuery in batches', async () => { + let agents = await apiClient.getAgents(); + let agentPolicyIds = getAgentPolicyIds(agents); + expect(agentPolicyIds).to.eql({ + [defaultSpaceAgent1]: defaultSpacePolicy1.item.id, + [defaultSpaceAgent2]: defaultSpacePolicy2.item.id, + }); + agents = await apiClient.getAgents(TEST_SPACE_1); + agentPolicyIds = getAgentPolicyIds(agents); + expect(agentPolicyIds).to.eql({ + [testSpaceAgent1]: spaceTest1Policy1.item.id, + [testSpaceAgent2]: spaceTest1Policy2.item.id, + [testSpaceAgent3]: spaceTest1Policy1.item.id, + }); + + const res = await apiClient.bulkReassignAgents( + { + agents: `not fleet-agents.policy_id:"${spaceTest1Policy2.item.id}"`, + policy_id: spaceTest1Policy2.item.id, + batchSize: 1, + }, + TEST_SPACE_1 + ); + + const verifyActionResult = async () => { + const { body: result } = await supertest + .get(`/s/${TEST_SPACE_1}/api/fleet/agents`) + .set('kbn-xsrf', 'xxx'); + expect(result.total).to.eql(3); + result.items.forEach((agent: any) => { + expect(agent.policy_id).to.eql(spaceTest1Policy2.item.id); + }); + }; + + await new Promise((resolve, reject) => { + let attempts = 0; + const intervalId = setInterval(async () => { + if (attempts > 20) { + clearInterval(intervalId); + reject(new Error('action timed out')); + } + ++attempts; + const { + body: { items: actionStatuses }, + } = await supertest + .get(`/s/${TEST_SPACE_1}/api/fleet/agents/action_status`) + .set('kbn-xsrf', 'xxx'); + + const action = actionStatuses.find((a: any) => a.actionId === res.actionId); + if (action && action.nbAgentsActioned === action.nbAgentsActionCreated) { + clearInterval(intervalId); + await verifyActionResult(); + resolve({}); + } + }, 1000); + }).catch((e) => { + throw e; + }); }); }); }); diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts index 1a8d8c9f2e522..3ac41c0cc83ff 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/api_helper.ts @@ -216,11 +216,13 @@ export class SpaceTestApiClient { .expect(200); } async bulkReassignAgents(data: any, spaceId?: string) { - await this.supertest + const { body: res } = await this.supertest .post(`${this.getBaseUrl(spaceId)}/api/fleet/agents/bulk_reassign`) .set('kbn-xsrf', 'xxxx') .send(data) .expect(200); + + return res; } async upgradeAgent(agentId: string, data: any, spaceId?: string) { await this.supertest From 74a772a3f65a5d5ca55741d3e9dcdafbc50a931d Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 14 Aug 2024 10:44:19 -0400 Subject: [PATCH 08/12] Fix tests --- x-pack/plugins/fleet/server/mocks/index.ts | 24 +++++++++++++++++-- .../server/services/agents/reassign.test.ts | 21 +++++++++++----- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index 9dfb920251e76..10dd6d214c08b 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -19,6 +19,8 @@ import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; import { securityMock } from '@kbn/security-plugin/server/mocks'; import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; +import { SPACES_EXTENSION_ID } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import type { PackagePolicyClient } from '../services/package_policy_service'; import type { AgentPolicyServiceInterface } from '../services'; @@ -59,7 +61,11 @@ export interface MockedFleetAppContext extends FleetAppContext { export const createAppContextStartContractMock = ( configOverrides: Partial = {}, - isServerless: boolean = false + isServerless: boolean = false, + soClients: Partial<{ + internal?: SavedObjectsClientContract; + withoutSpaceExtensions?: SavedObjectsClientContract; + }> = {} ): MockedFleetAppContext => { const config = { agents: { enabled: true, elasticsearch: {} }, @@ -70,12 +76,26 @@ export const createAppContextStartContractMock = ( const config$ = of(config); + const mockedSavedObject = savedObjectsServiceMock.createStartContract(); + + const internalSoClient = soClients.internal ?? savedObjectsClientMock.create(); + const internalSoClientWithoutSpaceExtension = + soClients.withoutSpaceExtensions ?? savedObjectsClientMock.create(); + + mockedSavedObject.getScopedClient.mockImplementation((request, options) => { + if (options?.excludedExtensions?.includes(SPACES_EXTENSION_ID)) { + return internalSoClientWithoutSpaceExtension; + } + + return internalSoClient; + }); + return { elasticsearch: elasticsearchServiceMock.createStart(), data: dataPluginMock.createStartContract(), encryptedSavedObjectsStart: encryptedSavedObjectsMock.createStart(), encryptedSavedObjectsSetup: encryptedSavedObjectsMock.createSetup({ canEncrypt: true }), - savedObjects: savedObjectsServiceMock.createStartContract(), + savedObjects: mockedSavedObject, securityCoreStart: securityServiceMock.createStart(), securitySetup: securityMock.createSetup(), securityStart: securityMock.createStart(), diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.test.ts b/x-pack/plugins/fleet/server/services/agents/reassign.test.ts index 6261b3295496a..d3278a695b14b 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.test.ts @@ -15,8 +15,16 @@ import { reassignAgent, reassignAgents } from './reassign'; import { createClientMock } from './action.mock'; describe('reassignAgent', () => { + let mocks: ReturnType; + beforeEach(async () => { - appContextService.start(createAppContextStartContractMock()); + mocks = createClientMock(); + + appContextService.start( + createAppContextStartContractMock({}, false, { + internal: mocks.soClient, + }) + ); }); afterEach(() => { @@ -24,7 +32,7 @@ describe('reassignAgent', () => { }); describe('reassignAgent (singular)', () => { it('can reassign from regular agent policy to regular', async () => { - const { soClient, esClient, agentInRegularDoc, regularAgentPolicySO } = createClientMock(); + const { soClient, esClient, agentInRegularDoc, regularAgentPolicySO } = mocks; await reassignAgent(soClient, esClient, agentInRegularDoc._id, regularAgentPolicySO.id); // calls ES update with correct values @@ -38,7 +46,7 @@ describe('reassignAgent', () => { }); it('cannot reassign from regular agent policy to hosted', async () => { - const { soClient, esClient, agentInRegularDoc, hostedAgentPolicySO } = createClientMock(); + const { soClient, esClient, agentInRegularDoc, hostedAgentPolicySO } = mocks; await expect( reassignAgent(soClient, esClient, agentInRegularDoc._id, hostedAgentPolicySO.id) ).rejects.toThrowError(HostedAgentPolicyRestrictionRelatedError); @@ -49,7 +57,7 @@ describe('reassignAgent', () => { it('cannot reassign from hosted agent policy', async () => { const { soClient, esClient, agentInHostedDoc, hostedAgentPolicySO, regularAgentPolicySO } = - createClientMock(); + mocks; await expect( reassignAgent(soClient, esClient, agentInHostedDoc._id, regularAgentPolicySO.id) ).rejects.toThrowError(HostedAgentPolicyRestrictionRelatedError); @@ -73,7 +81,7 @@ describe('reassignAgent', () => { agentInHostedDoc, agentInHostedDoc2, regularAgentPolicySO2, - } = createClientMock(); + } = mocks; esClient.search.mockResponse({ hits: { @@ -111,7 +119,8 @@ describe('reassignAgent', () => { }); it('should report errors from ES agent update call', async () => { - const { soClient, esClient, agentInRegularDoc, regularAgentPolicySO2 } = createClientMock(); + const { soClient, esClient, agentInRegularDoc, regularAgentPolicySO2 } = mocks; + esClient.bulk.mockResponse({ items: [ { From 903eb64cd1092696e47bd65451be3f95393b32f2 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:47:11 +0000 Subject: [PATCH 09/12] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../fleet/server/services/agents/reassign_action_runner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts index 25a1e63f7a167..cd9183b0771bc 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign_action_runner.ts @@ -5,7 +5,7 @@ * 2.0. */ import { v4 as uuidv4 } from 'uuid'; -import type { SavedObjectsClientContract, ElasticsearchClient } from '@kbn/core/server'; +import type { ElasticsearchClient } from '@kbn/core/server'; import type { Agent } from '../../types'; From 5d3fe24e44937c65244c128de31e52e42e4a1d25 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 14 Aug 2024 13:52:51 -0400 Subject: [PATCH 10/12] fix types --- x-pack/plugins/fleet/server/services/agents/action_runner.ts | 4 ++-- x-pack/plugins/fleet/server/services/agents/reassign.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/agents/action_runner.ts b/x-pack/plugins/fleet/server/services/agents/action_runner.ts index dc56543000506..6ef97e347d20f 100644 --- a/x-pack/plugins/fleet/server/services/agents/action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/action_runner.ts @@ -16,13 +16,13 @@ import moment from 'moment'; import type { Agent } from '../../types'; import { appContextService } from '..'; import { SO_SEARCH_LIMIT } from '../../../common/constants'; +import { agentsKueryNamespaceFilter } from '../spaces/agent_namespaces'; import { getAgentActions } from './actions'; import { closePointInTime, getAgentsByKuery } from './crud'; import type { BulkActionsResolver } from './bulk_actions_resolver'; import type { RetryParams } from './retry_helper'; import { getRetryParams, MAX_RETRY_COUNT } from './retry_helper'; -import { agentsKueryNamespaceFilter } from '../spaces/agent_namespaces'; export interface ActionParams { kuery: string; @@ -186,7 +186,7 @@ export abstract class ActionRunner { } } - return await this.processAgents(agents).catch(console.log); + return await this.processAgents(agents); } async processAgentsInBatches(): Promise<{ actionId: string }> { diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.test.ts b/x-pack/plugins/fleet/server/services/agents/reassign.test.ts index d3278a695b14b..f8d721065432c 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.test.ts @@ -15,7 +15,7 @@ import { reassignAgent, reassignAgents } from './reassign'; import { createClientMock } from './action.mock'; describe('reassignAgent', () => { - let mocks: ReturnType; + let mocks: ReturnType; beforeEach(async () => { mocks = createClientMock(); From f4e67383d73da5615d677a58374944cb8aa6d93e Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Sun, 18 Aug 2024 16:40:34 -0400 Subject: [PATCH 11/12] fix after merging main --- .../fleet/server/services/agents/action_runner.ts | 4 ++-- .../plugins/fleet/server/services/agents/actions.ts | 2 +- .../server/services/agents/bulk_actions_resolver.ts | 2 -- .../plugins/fleet/server/services/agents/reassign.ts | 2 +- .../plugins/fleet/server/services/agents/upgrade.ts | 11 ++--------- .../server/services/agents/upgrade_action_runner.ts | 12 +++++++++--- 6 files changed, 15 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/agents/action_runner.ts b/x-pack/plugins/fleet/server/services/agents/action_runner.ts index 6ef97e347d20f..f7eea6f3ac560 100644 --- a/x-pack/plugins/fleet/server/services/agents/action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/action_runner.ts @@ -197,8 +197,8 @@ export abstract class ActionRunner { appContextService.getLogger().debug('kuery: ' + this.actionParams.kuery); - const getAgents = () => { - const namespaceFilter = agentsKueryNamespaceFilter(this.actionParams.spaceId); + const getAgents = async () => { + const namespaceFilter = await agentsKueryNamespaceFilter(this.actionParams.spaceId); const kuery = namespaceFilter ? `${namespaceFilter} AND ${this.actionParams.kuery}` : this.actionParams.kuery; diff --git a/x-pack/plugins/fleet/server/services/agents/actions.ts b/x-pack/plugins/fleet/server/services/agents/actions.ts index bd62b350513d2..11c7174e09312 100644 --- a/x-pack/plugins/fleet/server/services/agents/actions.ts +++ b/x-pack/plugins/fleet/server/services/agents/actions.ts @@ -329,7 +329,7 @@ export async function cancelAgentAction( }; const res = await esClient.search({ index: AGENT_ACTIONS_INDEX, - query: addNamespaceFilteringToQuery(query, currentNameSpace), + query: await addNamespaceFilteringToQuery(query, currentNameSpace), size: SO_SEARCH_LIMIT, }); diff --git a/x-pack/plugins/fleet/server/services/agents/bulk_actions_resolver.ts b/x-pack/plugins/fleet/server/services/agents/bulk_actions_resolver.ts index 152164f5ce2aa..b68bae611252a 100644 --- a/x-pack/plugins/fleet/server/services/agents/bulk_actions_resolver.ts +++ b/x-pack/plugins/fleet/server/services/agents/bulk_actions_resolver.ts @@ -49,8 +49,6 @@ export class BulkActionsResolver { [BulkActionTaskType.REQUEST_DIAGNOSTICS_RETRY]: RequestDiagnosticsActionRunner, }; - console.log(taskInstance); - return createRetryTask( taskInstance, getDeps, diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.ts b/x-pack/plugins/fleet/server/services/agents/reassign.ts index 2f463f530caa1..d5a4d2ab67525 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.ts @@ -121,7 +121,7 @@ export async function reassignAgents( } } else if ('kuery' in options) { const batchSize = options.batchSize ?? SO_SEARCH_LIMIT; - const namespaceFilter = agentsKueryNamespaceFilter(currentNameSpace); + const namespaceFilter = await agentsKueryNamespaceFilter(currentNameSpace); const kuery = namespaceFilter ? `${namespaceFilter} AND ${options.kuery}` : options.kuery; const res = await getAgentsByKuery(esClient, soClient, { kuery, diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.ts index e919b4a7724d0..40d676a68e24d 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade.ts @@ -96,7 +96,7 @@ export async function sendUpgradeAgentsActions( } } else if ('kuery' in options) { const batchSize = options.batchSize ?? SO_SEARCH_LIMIT; - const namespaceFilter = agentsKueryNamespaceFilter(currentNameSpace); + const namespaceFilter = await agentsKueryNamespaceFilter(currentNameSpace); const kuery = namespaceFilter ? `${namespaceFilter} AND ${options.kuery}` : options.kuery; const res = await getAgentsByKuery(esClient, soClient, { @@ -123,12 +123,5 @@ export async function sendUpgradeAgentsActions( } } - return await upgradeBatch( - soClient, - esClient, - givenAgents, - outgoingErrors, - options, - currentNameSpace - ); + return await upgradeBatch(esClient, givenAgents, outgoingErrors, options, currentNameSpace); } diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts index 70bba0181880f..a11b43a5b3ee2 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade_action_runner.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SavedObjectsClientContract, ElasticsearchClient } from '@kbn/core/server'; +import type { ElasticsearchClient } from '@kbn/core/server'; import { v4 as uuidv4 } from 'uuid'; import moment from 'moment'; @@ -34,7 +34,13 @@ import { getLatestAvailableAgentVersion } from './versions'; export class UpgradeActionRunner extends ActionRunner { protected async processAgents(agents: Agent[]): Promise<{ actionId: string }> { - return await upgradeBatch(this.soClient, this.esClient, agents, {}, this.actionParams! as any); + return await upgradeBatch( + this.esClient, + agents, + {}, + this.actionParams! as any, + this.actionParams?.spaceId + ); } protected getTaskType() { @@ -52,7 +58,6 @@ const isActionIdCancelled = async (esClient: ElasticsearchClient, actionId: stri }; export async function upgradeBatch( - soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, givenAgents: Agent[], outgoingErrors: Record, @@ -68,6 +73,7 @@ export async function upgradeBatch( }, spaceId?: string ): Promise<{ actionId: string }> { + const soClient = appContextService.getInternalUserSOClientForSpaceId(spaceId); const errors: Record = { ...outgoingErrors }; const hostedPolicies = await getHostedPolicies(soClient, givenAgents); From a69ea1bffe35ee2182d9936728dbd6eff17d36a7 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Sun, 18 Aug 2024 16:45:19 -0400 Subject: [PATCH 12/12] fix unit tests --- .../server/services/agents/upgrade.test.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade.test.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.test.ts index b1e78862fde0e..7dbfaf86bd272 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade.test.ts @@ -35,11 +35,15 @@ jest.mock('./action_status', () => { }); describe('sendUpgradeAgentsActions (plural)', () => { + let mocks: ReturnType; + beforeEach(async () => { - const { soClient } = createClientMock(); + mocks = createClientMock(); + appContextService.start( createAppContextStartContractMock({}, false, { - withoutSpaceExtensions: soClient, + internal: mocks.soClient, + withoutSpaceExtensions: mocks.soClient, }) ); }); @@ -48,7 +52,7 @@ describe('sendUpgradeAgentsActions (plural)', () => { appContextService.stop(); }); it('can upgrade from an regular agent policy', async () => { - const { soClient, esClient, agentInRegularDoc, agentInRegularDoc2 } = createClientMock(); + const { soClient, esClient, agentInRegularDoc, agentInRegularDoc2 } = mocks; const idsToAction = [agentInRegularDoc._id, agentInRegularDoc2._id]; await sendUpgradeAgentsActions(soClient, esClient, { agentIds: idsToAction, version: '8.5.0' }); @@ -68,8 +72,7 @@ describe('sendUpgradeAgentsActions (plural)', () => { } }); it('cannot upgrade from a hosted agent policy by default', async () => { - const { soClient, esClient, agentInHostedDoc, agentInRegularDoc, agentInRegularDoc2 } = - createClientMock(); + const { soClient, esClient, agentInHostedDoc, agentInRegularDoc, agentInRegularDoc2 } = mocks; const idsToAction = [agentInRegularDoc._id, agentInHostedDoc._id, agentInRegularDoc2._id]; await sendUpgradeAgentsActions(soClient, esClient, { agentIds: idsToAction, version: '8.5.0' }); @@ -104,8 +107,8 @@ describe('sendUpgradeAgentsActions (plural)', () => { }); it('can upgrade from hosted agent policy with force=true', async () => { - const { soClient, esClient, agentInHostedDoc, agentInRegularDoc, agentInRegularDoc2 } = - createClientMock(); + const { soClient, esClient, agentInHostedDoc, agentInRegularDoc, agentInRegularDoc2 } = mocks; + const idsToAction = [agentInRegularDoc._id, agentInHostedDoc._id, agentInRegularDoc2._id]; await sendUpgradeAgentsActions(soClient, esClient, { agentIds: idsToAction, @@ -129,9 +132,9 @@ describe('sendUpgradeAgentsActions (plural)', () => { }); it('skip upgrade if action id is cancelled', async () => { - const { soClient, esClient, agentInRegularDoc } = createClientMock(); + const { esClient, agentInRegularDoc } = mocks; const agents = [{ id: agentInRegularDoc._id } as Agent]; - await upgradeBatch(soClient, esClient, agents, {}, { + await upgradeBatch(esClient, agents, {}, { actionId: 'cancelled-action', } as any); });