diff --git a/x-pack/plugins/fleet/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts index 2add5750f1855..cefe1d035cba2 100644 --- a/x-pack/plugins/fleet/common/types/models/agent.ts +++ b/x-pack/plugins/fleet/common/types/models/agent.ts @@ -61,6 +61,7 @@ export interface NewAgentAction { ack_data?: any; sent_at?: string; agents: string[]; + namespaces?: string[]; created_at?: string; id?: string; expiration?: string; @@ -408,6 +409,8 @@ export interface FleetServerAgentAction { */ agents?: string[]; + namespaces?: string[]; + /** * Date when the agent should execute that agent. This field could be altered by Fleet server for progressive rollout of the action. */ diff --git a/x-pack/plugins/fleet/server/routes/agent/handlers.ts b/x-pack/plugins/fleet/server/routes/agent/handlers.ts index 52a0540c384fa..ff5b6a3c8523b 100644 --- a/x-pack/plugins/fleet/server/routes/agent/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/agent/handlers.ts @@ -8,7 +8,6 @@ import { uniq } from 'lodash'; import { type RequestHandler, SavedObjectsErrorHelpers } from '@kbn/core/server'; import type { TypeOf } from '@kbn/config-schema'; -import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; import type { GetAgentsResponse, @@ -45,19 +44,10 @@ import { defaultFleetErrorHandler, FleetNotFoundError } from '../../errors'; import * as AgentService from '../../services/agents'; import { fetchAndAssignAgentMetrics } from '../../services/agents/agent_metrics'; import { getAgentStatusForAgentPolicy } from '../../services/agents'; -import { appContextService } from '../../services'; +import { isAgentInNamespace } from '../../services/agents/namespace'; -export function verifyNamespace(agent: Agent, currentNamespace?: string) { - if (!appContextService.getExperimentalFeatures().useSpaceAwareness) { - return; - } - const isInNamespace = - (currentNamespace && agent.namespaces?.includes(currentNamespace)) || - (!currentNamespace && - (!agent.namespaces || - agent.namespaces.length === 0 || - agent.namespaces?.includes(DEFAULT_NAMESPACE_STRING))); - if (!isInNamespace) { +function verifyNamespace(agent: Agent, namespace?: string) { + if (!isAgentInNamespace(agent, namespace)) { throw new FleetNotFoundError(`${agent.id} not found in namespace`); } } @@ -71,7 +61,6 @@ export const getAgentHandler: FleetRequestHandler< const esClientCurrentUser = coreContext.elasticsearch.client.asCurrentUser; let agent = await fleetContext.agentClient.asCurrentUser.getAgent(request.params.agentId); - verifyNamespace(agent, coreContext.savedObjects.client.getCurrentNamespace()); if (request.query.withMetrics) { @@ -94,12 +83,15 @@ export const getAgentHandler: FleetRequestHandler< } }; -export const deleteAgentHandler: RequestHandler< +export const deleteAgentHandler: FleetRequestHandler< TypeOf > = async (context, request, response) => { + const [coreContext, fleetContext] = await Promise.all([context.core, context.fleet]); + const esClient = coreContext.elasticsearch.client.asInternalUser; + try { - const coreContext = await context.core; - const esClient = coreContext.elasticsearch.client.asInternalUser; + const agent = await fleetContext.agentClient.asCurrentUser.getAgent(request.params.agentId); + verifyNamespace(agent, coreContext.savedObjects.client.getCurrentNamespace()); await AgentService.deleteAgent(esClient, request.params.agentId); @@ -120,12 +112,12 @@ export const deleteAgentHandler: RequestHandler< } }; -export const updateAgentHandler: RequestHandler< +export const updateAgentHandler: FleetRequestHandler< TypeOf, undefined, TypeOf > = async (context, request, response) => { - const coreContext = await context.core; + const [coreContext, fleetContext] = await Promise.all([context.core, context.fleet]); const esClient = coreContext.elasticsearch.client.asInternalUser; const soClient = coreContext.savedObjects.client; @@ -138,6 +130,9 @@ export const updateAgentHandler: RequestHandler< } try { + const agent = await fleetContext.agentClient.asCurrentUser.getAgent(request.params.agentId); + verifyNamespace(agent, coreContext.savedObjects.client.getCurrentNamespace()); + await AgentService.updateAgent(esClient, request.params.agentId, partialAgent); const body = { item: await AgentService.getAgentById(esClient, soClient, request.params.agentId), diff --git a/x-pack/plugins/fleet/server/services/agents/actions.ts b/x-pack/plugins/fleet/server/services/agents/actions.ts index bda3090f25d0e..f344aa24e59dd 100644 --- a/x-pack/plugins/fleet/server/services/agents/actions.ts +++ b/x-pack/plugins/fleet/server/services/agents/actions.ts @@ -52,6 +52,7 @@ export async function createAgentAction( ? undefined : newAgentAction.expiration ?? new Date(now + ONE_MONTH_IN_MS).toISOString(), agents: newAgentAction.agents, + namespaces: newAgentAction.namespaces, action_id: actionId, data: newAgentAction.data, type: newAgentAction.type, @@ -182,6 +183,7 @@ export async function bulkCreateAgentActionResults( results: Array<{ actionId: string; agentId: string; + namespaces?: string[]; error?: string; }> ): Promise { @@ -194,6 +196,7 @@ export async function bulkCreateAgentActionResults( '@timestamp': new Date().toISOString(), action_id: result.actionId, agent_id: result.agentId, + namespaces: result.namespaces, error: result.error, }; diff --git a/x-pack/plugins/fleet/server/services/agents/namespace.test.ts b/x-pack/plugins/fleet/server/services/agents/namespace.test.ts new file mode 100644 index 0000000000000..b5cda85cc45e4 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agents/namespace.test.ts @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { appContextService } from '../app_context'; + +import type { Agent } from '../../types'; + +import { agentsKueryNamespaceFilter, isAgentInNamespace } from './namespace'; + +jest.mock('../app_context'); + +const mockedAppContextService = appContextService as jest.Mocked; + +describe('isAgentInNamespace', () => { + describe('with the useSpaceAwareness feature flag disabled', () => { + beforeEach(() => { + mockedAppContextService.getExperimentalFeatures.mockReturnValue({ + useSpaceAwareness: false, + } as any); + }); + + it('returns true even if the agent is in a different space', () => { + const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent; + expect(isAgentInNamespace(agent, 'space2')).toEqual(true); + }); + }); + + describe('with the useSpaceAwareness feature flag enabled', () => { + beforeEach(() => { + mockedAppContextService.getExperimentalFeatures.mockReturnValue({ + useSpaceAwareness: true, + } as any); + }); + + describe('when the namespace is defined', () => { + it('returns true if the agent namespaces include the namespace', () => { + const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent; + expect(isAgentInNamespace(agent, 'space1')).toEqual(true); + }); + + it('returns false if the agent namespaces do not include the namespace', () => { + const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent; + expect(isAgentInNamespace(agent, 'space2')).toEqual(false); + }); + + it('returns false if the agent has zero length namespaces', () => { + const agent = { id: '123', namespaces: [] as string[] } as Agent; + expect(isAgentInNamespace(agent, 'space1')).toEqual(false); + }); + + it('returns false if the agent does not have namespaces', () => { + const agent = { id: '123' } as Agent; + expect(isAgentInNamespace(agent, 'space1')).toEqual(false); + }); + }); + + describe('when the namespace is undefined', () => { + it('returns true if the agent does not have namespaces', () => { + const agent = { id: '123' } as Agent; + expect(isAgentInNamespace(agent)).toEqual(true); + }); + + it('returns true if the agent has zero length namespaces', () => { + const agent = { id: '123', namespaces: [] as string[] } as Agent; + expect(isAgentInNamespace(agent)).toEqual(true); + }); + + it('returns true if the agent namespaces include the default one', () => { + const agent = { id: '123', namespaces: ['default'] } as Agent; + expect(isAgentInNamespace(agent)).toEqual(true); + }); + + it('returns false if the agent namespaces include the default one', () => { + const agent = { id: '123', namespaces: ['space1'] } as Agent; + expect(isAgentInNamespace(agent)).toEqual(false); + }); + }); + }); +}); + +describe('agentsKueryNamespaceFilter', () => { + describe('with the useSpaceAwareness feature flag disabled', () => { + beforeEach(() => { + mockedAppContextService.getExperimentalFeatures.mockReturnValue({ + useSpaceAwareness: false, + } as any); + }); + + it('returns undefined if the useSpaceAwareness feature flag disabled', () => { + expect(agentsKueryNamespaceFilter('space1')).toBeUndefined(); + }); + }); + + describe('with the useSpaceAwareness feature flag enabled', () => { + beforeEach(() => { + mockedAppContextService.getExperimentalFeatures.mockReturnValue({ + useSpaceAwareness: true, + } as any); + }); + + it('returns undefined if the namespace is undefined', () => { + expect(agentsKueryNamespaceFilter()).toBeUndefined(); + }); + + it('returns a kuery for the default space', () => { + expect(agentsKueryNamespaceFilter('default')).toEqual( + 'namespaces:(default) or not namespaces:*' + ); + }); + + it('returns a kuery for custom spaces', () => { + expect(agentsKueryNamespaceFilter('space1')).toEqual('namespaces:(space1)'); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/agents/namespace.ts b/x-pack/plugins/fleet/server/services/agents/namespace.ts new file mode 100644 index 0000000000000..28cecdf22e30b --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agents/namespace.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server'; + +import { appContextService } from '../app_context'; + +import type { Agent } from '../../types'; + +export function isAgentInNamespace(agent: Agent, namespace?: string) { + const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; + if (!useSpaceAwareness) { + return true; + } + + return ( + (namespace && agent.namespaces?.includes(namespace)) || + (!namespace && + (!agent.namespaces || + agent.namespaces.length === 0 || + agent.namespaces?.includes(DEFAULT_NAMESPACE_STRING))) + ); +} + +export function agentsKueryNamespaceFilter(namespace?: string) { + const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness; + if (!useSpaceAwareness || !namespace) { + return; + } + return namespace === DEFAULT_NAMESPACE_STRING + ? `namespaces:(${DEFAULT_NAMESPACE_STRING}) or not namespaces:*` + : `namespaces:(${namespace})`; +} 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 b118b8ff03ac8..35163288e97dc 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 @@ -8,6 +8,8 @@ import type { SavedObjectsClientContract } from '@kbn/core/server'; import type { ElasticsearchClientMock } from '@kbn/core/server/mocks'; import { elasticsearchServiceMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { appContextService } from '../app_context'; + import type { Agent } from '../../types'; import { createClientMock } from './action.mock'; @@ -374,4 +376,126 @@ describe('update_agent_tags', () => { }) ); }); + + it('should update tags for agents in the space', async () => { + soClient.getCurrentNamespace.mockReturnValue('default'); + esClient.search.mockResolvedValue({ + hits: { + hits: [ + { + _id: 'agent1', + _source: { + tags: ['one', 'two', 'three'], + namespaces: ['default'], + }, + fields: { + status: 'online', + }, + }, + ], + }, + } as any); + + await updateAgentTags(soClient, esClient, { agentIds: ['agent1'] }, ['one'], ['two']); + + expect(esClient.updateByQuery).toHaveBeenCalledWith( + expect.objectContaining({ + conflicts: 'proceed', + index: '.fleet-agents', + query: { + terms: { _id: ['agent1'] }, + }, + script: expect.objectContaining({ + lang: 'painless', + params: expect.objectContaining({ + tagsToAdd: ['one'], + tagsToRemove: ['two'], + updatedAt: expect.anything(), + }), + source: expect.anything(), + }), + }) + ); + }); + + describe('with the useSpaceAwareness feature flag enabled', () => { + beforeEach(() => { + jest.mocked(appContextService.getExperimentalFeatures).mockReturnValue({ + useSpaceAwareness: true, + } 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'); + + await updateAgentTags( + soClient, + esClient, + { kuery: 'status:healthy OR status:offline' }, + [], + ['remove'] + ); + + expect(UpdateAgentTagsActionRunner).toHaveBeenCalledWith( + expect.anything(), + expect.anything(), + expect.objectContaining({ + batchSize: 10000, + kuery: + '(namespaces:(default) or not namespaces:*) AND (status:healthy OR status:offline) AND (tags:remove)', + tagsToAdd: [], + tagsToRemove: ['remove'], + }), + expect.anything() + ); + }); + + it('should add namespace filter to kuery in a custom space', async () => { + soClient.getCurrentNamespace.mockReturnValue('myspace'); + + await updateAgentTags( + soClient, + esClient, + { kuery: 'status:healthy OR status:offline' }, + [], + ['remove'] + ); + + expect(UpdateAgentTagsActionRunner).toHaveBeenCalledWith( + expect.anything(), + expect.anything(), + expect.objectContaining({ + batchSize: 10000, + kuery: '(namespaces:(myspace)) AND (status:healthy OR status:offline) AND (tags:remove)', + tagsToAdd: [], + tagsToRemove: ['remove'], + }), + expect.anything() + ); + }); + }); }); 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 5e335bfd41996..e4a7ca8f0148d 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 @@ -15,6 +15,7 @@ import { SO_SEARCH_LIMIT } from '../../constants'; import { getAgentsById, getAgentsByKuery, openPointInTime } from './crud'; import type { GetAgentsOptions } from '.'; import { UpdateAgentTagsActionRunner, updateTagsBatch } from './update_agent_tags_action_runner'; +import { agentsKueryNamespaceFilter, isAgentInNamespace } from './namespace'; export async function updateAgentTags( soClient: SavedObjectsClientContract, @@ -25,6 +26,7 @@ export async function updateAgentTags( ): Promise<{ actionId: string }> { const outgoingErrors: Record = {}; const givenAgents: Agent[] = []; + const currentNameSpace = soClient.getCurrentNamespace(); if ('agentIds' in options) { const maybeAgents = await getAgentsById(esClient, soClient, options.agentIds); @@ -33,6 +35,10 @@ 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); } @@ -40,7 +46,8 @@ export async function updateAgentTags( } else if ('kuery' in options) { const batchSize = options.batchSize ?? SO_SEARCH_LIMIT; - const filters = []; + const namespaceFilter = agentsKueryNamespaceFilter(currentNameSpace); + const filters = namespaceFilter ? [namespaceFilter] : []; if (options.kuery !== '') { filters.push(options.kuery); } 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 0a8232d6e5715..44fef8d4a6c2b 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 @@ -149,6 +149,9 @@ export async function updateTagsBatch( const versionConflictCount = res.version_conflicts ?? 0; const versionConflictIds = isLastRetry ? getUuidArray(versionConflictCount) : []; + const currentNameSpace = soClient.getCurrentNamespace(); + const namespaces = currentNameSpace ? [currentNameSpace] : []; + // 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 // this ensures that the action status count will be accurate @@ -157,6 +160,7 @@ export async function updateTagsBatch( agents: updatedIds .concat(failures.map((failure) => failure.id)) .concat(isLastRetry ? versionConflictIds : []), + namespaces, created_at: new Date().toISOString(), type: 'UPDATE_TAGS', total: options.total ?? res.total, @@ -176,6 +180,7 @@ export async function updateTagsBatch( updatedIds.map((id) => ({ agentId: id, actionId, + namespaces, })) ); appContextService.getLogger().debug(`action updated result wrote on ${updatedCount} agents`); @@ -188,6 +193,7 @@ export async function updateTagsBatch( failures.map((failure) => ({ agentId: failure.id, actionId, + namespace: currentNameSpace, error: failure.cause.reason, })) ); @@ -202,6 +208,7 @@ export async function updateTagsBatch( versionConflictIds.map((id) => ({ agentId: id, actionId, + namespace: currentNameSpace, error: 'version conflict on last retry', })) ); 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 73c2675d7b2d5..0a4a1035083e6 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 @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { CreateAgentPolicyResponse } from '@kbn/fleet-plugin/common'; +import { CreateAgentPolicyResponse, GetAgentsResponse } from '@kbn/fleet-plugin/common'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; import { SpaceTestApiClient } from './api_helper'; @@ -132,5 +132,121 @@ export default function (providerContext: FtrProviderContext) { expect(err?.message).to.match(/404 "Not Found"/); }); }); + + describe('PUT /agents/{id}', () => { + it('should allow to update 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 () => { + let err: Error | undefined; + try { + await apiClient.updateAgent(testSpaceAgent1, { tags: ['foo'] }); + } catch (_err) { + err = _err; + } + + expect(err).to.be.an(Error); + expect(err?.message).to.match(/404 "Not Found"/); + }); + }); + + describe('DELETE /agents/{id}', () => { + it('should allow to delete an agent in the same space', async () => { + const testSpaceAgent3 = await createFleetAgent(spaceTest1Policy2.item.id, TEST_SPACE_1); + await apiClient.deleteAgent(testSpaceAgent3, TEST_SPACE_1); + }); + + it('should not allow to delete an agent from a different space from the default space', async () => { + let err: Error | undefined; + try { + await apiClient.deleteAgent(testSpaceAgent1); + } catch (_err) { + err = _err; + } + + expect(err).to.be.an(Error); + expect(err?.message).to.match(/404 "Not Found"/); + }); + }); + + describe('POST /agents/bulkUpdateAgentTags', () => { + function getAgentTags(agents: GetAgentsResponse) { + return agents.items?.reduce((acc, item) => { + acc[item.id] = item.tags; + return acc; + }, {} as any); + } + + it('should only update tags of agents in the same space when passing a list of agent ids', async () => { + let agents = await apiClient.getAgents(TEST_SPACE_1); + let agentTags = getAgentTags(agents); + expect(agentTags[testSpaceAgent1]).to.eql(['tag1']); + expect(agentTags[testSpaceAgent2]).to.eql(['tag1']); + // Add tag + await apiClient.bulkUpdateAgentTags( + { + agents: [defaultSpaceAgent1, testSpaceAgent1], + tagsToAdd: ['space1'], + }, + TEST_SPACE_1 + ); + agents = await apiClient.getAgents(TEST_SPACE_1); + agentTags = getAgentTags(agents); + expect(agentTags[testSpaceAgent1]).to.eql(['tag1', 'space1']); + expect(agentTags[testSpaceAgent2]).to.eql(['tag1']); + // Reset tags + await apiClient.bulkUpdateAgentTags( + { + agents: [testSpaceAgent1], + tagsToRemove: ['space1'], + }, + TEST_SPACE_1 + ); + agents = await apiClient.getAgents(TEST_SPACE_1); + agentTags = getAgentTags(agents); + expect(agentTags[testSpaceAgent1]).to.eql(['tag1']); + }); + + it('should only update tags of agents in the same space when passing a kuery', async () => { + let agentsInDefaultSpace = await apiClient.getAgents(); + let agentInDefaultSpaceTags = getAgentTags(agentsInDefaultSpace); + let agentsInTestSpace = await apiClient.getAgents(TEST_SPACE_1); + let agentInTestSpaceTags = getAgentTags(agentsInTestSpace); + expect(agentInDefaultSpaceTags[defaultSpaceAgent1]).to.eql(['tag1']); + expect(agentInDefaultSpaceTags[defaultSpaceAgent2]).to.eql(['tag1']); + expect(agentInTestSpaceTags[testSpaceAgent1]).to.eql(['tag1']); + expect(agentInTestSpaceTags[testSpaceAgent2]).to.eql(['tag1']); + // Add tag + await apiClient.bulkUpdateAgentTags( + { + agents: '', + tagsToAdd: ['space1'], + }, + TEST_SPACE_1 + ); + agentsInDefaultSpace = await apiClient.getAgents(); + agentInDefaultSpaceTags = getAgentTags(agentsInDefaultSpace); + agentsInTestSpace = await apiClient.getAgents(TEST_SPACE_1); + agentInTestSpaceTags = getAgentTags(agentsInTestSpace); + expect(agentInDefaultSpaceTags[defaultSpaceAgent1]).to.eql(['tag1']); + expect(agentInDefaultSpaceTags[defaultSpaceAgent2]).to.eql(['tag1']); + expect(agentInTestSpaceTags[testSpaceAgent1]).to.eql(['tag1', 'space1']); + expect(agentInTestSpaceTags[testSpaceAgent2]).to.eql(['tag1', 'space1']); + // Reset tags + await apiClient.bulkUpdateAgentTags( + { + agents: '', + tagsToRemove: ['space1'], + }, + TEST_SPACE_1 + ); + agentsInTestSpace = await apiClient.getAgents(TEST_SPACE_1); + agentInTestSpaceTags = getAgentTags(agentsInTestSpace); + expect(agentInTestSpaceTags[testSpaceAgent1]).to.eql(['tag1']); + expect(agentInTestSpaceTags[testSpaceAgent2]).to.eql(['tag1']); + }); + }); }); } 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 b9e01ba70b11e..963c1af66d1df 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 @@ -187,6 +187,32 @@ export class SpaceTestApiClient { return res; } + async updateAgent(agentId: string, data: any, spaceId?: string) { + const { body: res } = await this.supertest + .put(`${this.getBaseUrl(spaceId)}/api/fleet/agents/${agentId}`) + .set('kbn-xsrf', 'xxxx') + .send(data) + .expect(200); + + return res; + } + async deleteAgent(agentId: string, spaceId?: string) { + const { body: res } = await this.supertest + .delete(`${this.getBaseUrl(spaceId)}/api/fleet/agents/${agentId}`) + .set('kbn-xsrf', 'xxxx') + .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`) + .set('kbn-xsrf', 'xxxx') + .send(data) + .expect(200); + + return res; + } // Enrollment Settings async getEnrollmentSettings(spaceId?: string): Promise { const { body: res } = await this.supertest