diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx index 84f40ee4b692f..0f7f2cdf45e5f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_delete_provider.tsx @@ -12,15 +12,15 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { useHistory } from 'react-router-dom'; -import { AGENTS_PREFIX } from '../../../constants'; +import { SO_SEARCH_LIMIT } from '../../../../../constants'; + import { useStartServices, useConfig, - sendRequest, useLink, useDeleteAgentPolicyMutation, + sendGetAgents, } from '../../../hooks'; -import { API_VERSIONS } from '../../../../../../common/constants'; interface Props { children: (deleteAgentPolicy: DeleteAgentPolicy) => React.ReactElement; @@ -111,15 +111,13 @@ export const AgentPolicyDeleteProvider: React.FunctionComponent = ({ return; } setIsLoadingAgentsCount(true); - const { data } = await sendRequest<{ total: number }>({ - path: `/api/fleet/agents`, - method: 'get', - query: { - kuery: `${AGENTS_PREFIX}.policy_id : ${agentPolicyToCheck}`, - }, - version: API_VERSIONS.public.v1, + // filtering out the unenrolled agents assigned to this policy + const agents = await sendGetAgents({ + showInactive: true, + kuery: `policy_id:"${agentPolicyToCheck}" and not status: unenrolled`, + perPage: SO_SEARCH_LIMIT, }); - setAgentsCount(data?.total || 0); + setAgentsCount(agents.data?.total ?? 0); setIsLoadingAgentsCount(false); }; @@ -168,6 +166,7 @@ export const AgentPolicyDeleteProvider: React.FunctionComponent = ({ ) : agentsCount ? ( { savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE, }); }); + + it('should throw error if active agents are assigned to the policy', async () => { + (getAgentsByKuery as jest.Mock).mockResolvedValue({ + agents: [], + total: 2, + page: 1, + perPage: 10, + }); + await expect(agentPolicyService.delete(soClient, esClient, 'mocked')).rejects.toThrowError( + 'Cannot delete an agent policy that is assigned to any active or inactive agents' + ); + }); }); describe('bumpRevision', () => { diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 8c521d88819d6..cb64b0e543d2f 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -853,16 +853,18 @@ class AgentPolicyService { if (agentPolicy.is_managed && !options?.force) { throw new HostedAgentPolicyRestrictionRelatedError(`Cannot delete hosted agent policy ${id}`); } - + // Prevent deleting policy when assigned agents are inactive const { total } = await getAgentsByKuery(esClient, soClient, { - showInactive: false, + showInactive: true, perPage: 0, page: 1, - kuery: `${AGENTS_PREFIX}.policy_id:${id}`, + kuery: `${AGENTS_PREFIX}.policy_id:${id} and not status: unenrolled`, }); if (total > 0) { - throw new FleetError('Cannot delete agent policy that is assigned to agent(s)'); + throw new FleetError( + 'Cannot delete an agent policy that is assigned to any active or inactive agents' + ); } const packagePolicies = await packagePolicyService.findAllForAgentPolicy(soClient, id); diff --git a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts index dc7ee034bfbce..e27dff4e9d081 100644 --- a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts +++ b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; import { FLEET_AGENT_POLICIES_SCHEMA_VERSION } from '@kbn/fleet-plugin/server/constants'; -import { skipIfNoDockerRegistry } from '../../helpers'; +import { skipIfNoDockerRegistry, generateAgent } from '../../helpers'; import { setupFleetAndAgents } from '../agents/services'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; @@ -1186,6 +1186,68 @@ export default function (providerContext: FtrProviderContext) { name: 'Regular policy', }); }); + + describe('Errors when trying to delete', () => { + it('should prevent policies having agents from being deleted', async () => { + const { + body: { item: policyWithAgents }, + } = await supertest + .post(`/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'Policy with agents', + namespace: 'default', + }) + .expect(200); + await generateAgent(providerContext, 'healhty', 'agent-healthy-1', policyWithAgents.id); + const { body } = await supertest + .post('/api/fleet/agent_policies/delete') + .set('kbn-xsrf', 'xxx') + .send({ agentPolicyId: policyWithAgents.id }) + .expect(400); + + expect(body.message).to.contain( + 'Cannot delete an agent policy that is assigned to any active or inactive agents' + ); + await supertest + .delete(`/api/fleet/agents/agent-healthy-1`) + .set('kbn-xsrf', 'xx') + .expect(200); + }); + + it('should prevent policies having inactive agents from being deleted', async () => { + const { + body: { item: policyWithInactiveAgents }, + } = await supertest + .post(`/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'Policy with inactive agents', + namespace: 'default', + }) + .expect(200); + await generateAgent( + providerContext, + 'inactive', + 'agent-inactive-1', + policyWithInactiveAgents.id + ); + + const { body } = await supertest + .post('/api/fleet/agent_policies/delete') + .set('kbn-xsrf', 'xxx') + .send({ agentPolicyId: policyWithInactiveAgents.id }) + .expect(400); + + expect(body.message).to.contain( + 'Cannot delete an agent policy that is assigned to any active or inactive agents' + ); + await supertest + .delete(`/api/fleet/agents/agent-inactive-1`) + .set('kbn-xsrf', 'xx') + .expect(200); + }); + }); }); describe('POST /api/fleet/agent_policies/_bulk_get', () => {