mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Fleet] Prevent deletion of agent policies with inactive agents from UI (#175815)
Fixes https://github.com/elastic/kibana/issues/155925
## Summary
Prevent deleting an agent policy that has inactive agents assigned. The
workaround to fix existing "orphaned" agents is outlined
[here](https://github.com/elastic/kibana/issues/155925#issuecomment-1919250162).
### API
Adding check for inactive agents to the agent policy "delete" endpoint -
It will now fail if the policy has either active or inactive agents:
```
POST /api/fleet/agent_policies/delete
{
agentPolicyId: 1234534,
}
```
### UI
Warning added to the "delete" action in the UI when trying to delete a
policy that has active or inactive agents.
## Testing
- Have a policy with many inactive agents assigned to it
- Try to delete it from the agent policy list or settings
- The deletion is prevented and a warning is raised:

### Checklist
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
---------
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
1fe7833a23
commit
222e894b8a
4 changed files with 91 additions and 16 deletions
|
@ -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<Props> = ({
|
|||
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<Props> = ({
|
|||
) : agentsCount ? (
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
iconType="warning"
|
||||
title={i18n.translate(
|
||||
'xpack.fleet.deleteAgentPolicy.confirmModal.affectedAgentsTitle',
|
||||
{
|
||||
|
|
|
@ -387,6 +387,18 @@ describe('agent policy', () => {
|
|||
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', () => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue