mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[8.x] [Fleet] delete unenrolled agents task (#196072)
Backport https://github.com/elastic/kibana/pull/195544 to 8.x --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
6378ff3ac9
commit
759501133e
31 changed files with 898 additions and 9 deletions
|
@ -13395,6 +13395,13 @@ paths:
|
|||
properties:
|
||||
additional_yaml_config:
|
||||
type: string
|
||||
delete_unenrolled_agents:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
is_preconfigured:
|
||||
type: boolean
|
||||
fleet_server_hosts:
|
||||
description: Protocol and path must be the same for each URL
|
||||
items:
|
||||
|
@ -21844,6 +21851,13 @@ components:
|
|||
title: Settings
|
||||
type: object
|
||||
properties:
|
||||
delete_unenrolled_agents:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
is_preconfigured:
|
||||
type: boolean
|
||||
fleet_server_hosts:
|
||||
deprecated: true
|
||||
items:
|
||||
|
|
|
@ -13395,6 +13395,13 @@ paths:
|
|||
properties:
|
||||
additional_yaml_config:
|
||||
type: string
|
||||
delete_unenrolled_agents:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
is_preconfigured:
|
||||
type: boolean
|
||||
fleet_server_hosts:
|
||||
description: Protocol and path must be the same for each URL
|
||||
items:
|
||||
|
@ -21844,6 +21851,13 @@ components:
|
|||
title: Settings
|
||||
type: object
|
||||
properties:
|
||||
delete_unenrolled_agents:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
is_preconfigured:
|
||||
type: boolean
|
||||
fleet_server_hosts:
|
||||
deprecated: true
|
||||
items:
|
||||
|
|
|
@ -16823,6 +16823,13 @@ paths:
|
|||
properties:
|
||||
additional_yaml_config:
|
||||
type: string
|
||||
delete_unenrolled_agents:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
is_preconfigured:
|
||||
type: boolean
|
||||
fleet_server_hosts:
|
||||
description: Protocol and path must be the same for each URL
|
||||
items:
|
||||
|
@ -29623,6 +29630,13 @@ components:
|
|||
title: Settings
|
||||
type: object
|
||||
properties:
|
||||
delete_unenrolled_agents:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
is_preconfigured:
|
||||
type: boolean
|
||||
fleet_server_hosts:
|
||||
deprecated: true
|
||||
items:
|
||||
|
|
|
@ -16823,6 +16823,13 @@ paths:
|
|||
properties:
|
||||
additional_yaml_config:
|
||||
type: string
|
||||
delete_unenrolled_agents:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
is_preconfigured:
|
||||
type: boolean
|
||||
fleet_server_hosts:
|
||||
description: Protocol and path must be the same for each URL
|
||||
items:
|
||||
|
@ -29623,6 +29630,13 @@ components:
|
|||
title: Settings
|
||||
type: object
|
||||
properties:
|
||||
delete_unenrolled_agents:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
is_preconfigured:
|
||||
type: boolean
|
||||
fleet_server_hosts:
|
||||
deprecated: true
|
||||
items:
|
||||
|
|
|
@ -233,9 +233,7 @@
|
|||
"payload.connector.type",
|
||||
"type"
|
||||
],
|
||||
"cloud-security-posture-settings": [
|
||||
"rules"
|
||||
],
|
||||
"cloud-security-posture-settings": [],
|
||||
"config": [
|
||||
"buildNum"
|
||||
],
|
||||
|
@ -718,6 +716,9 @@
|
|||
"vars"
|
||||
],
|
||||
"ingest_manager_settings": [
|
||||
"delete_unenrolled_agents",
|
||||
"delete_unenrolled_agents.enabled",
|
||||
"delete_unenrolled_agents.is_preconfigured",
|
||||
"fleet_server_hosts",
|
||||
"has_seen_add_data_notice",
|
||||
"output_secret_storage_requirements_met",
|
||||
|
|
|
@ -2373,6 +2373,18 @@
|
|||
},
|
||||
"ingest_manager_settings": {
|
||||
"properties": {
|
||||
"delete_unenrolled_agents": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"index": false,
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_preconfigured": {
|
||||
"index": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fleet_server_hosts": {
|
||||
"type": "keyword"
|
||||
},
|
||||
|
|
|
@ -54,5 +54,7 @@ export async function checkIncompatibleMappings({
|
|||
throw createFailError(
|
||||
`Only mappings changes that are compatible with current mappings are allowed. Consider reaching out to the Kibana core team if you are stuck.`
|
||||
);
|
||||
} finally {
|
||||
await esClient.indices.delete({ index: TEST_INDEX_NAME }).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
|||
"ingest-download-sources": "279a68147e62e4d8858c09ad1cf03bd5551ce58d",
|
||||
"ingest-outputs": "daafff49255ab700e07491376fe89f04fc998b91",
|
||||
"ingest-package-policies": "53a94064674835fdb35e5186233bcd7052eabd22",
|
||||
"ingest_manager_settings": "e794576a05d19dd5306a1e23cbb82c09bffabd65",
|
||||
"ingest_manager_settings": "111a616eb72627c002029c19feb9e6c439a10505",
|
||||
"inventory-view": "b8683c8e352a286b4aca1ab21003115a4800af83",
|
||||
"kql-telemetry": "93c1d16c1a0dfca9c8842062cf5ef8f62ae401ad",
|
||||
"legacy-url-alias": "9b8cca3fbb2da46fd12823d3cd38fdf1c9f24bc8",
|
||||
|
|
|
@ -222,6 +222,17 @@
|
|||
},
|
||||
"additional_yaml_config": {
|
||||
"type": "string"
|
||||
},
|
||||
"delete_unenrolled_agents": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_preconfigured": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6187,6 +6198,17 @@
|
|||
},
|
||||
"prerelease_integrations_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"delete_unenrolled_agents": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_preconfigured": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
|
@ -128,6 +128,13 @@ paths:
|
|||
type: boolean
|
||||
additional_yaml_config:
|
||||
type: string
|
||||
delete_unenrolled_agents:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
is_preconfigured:
|
||||
type: boolean
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
|
@ -3866,6 +3873,13 @@ components:
|
|||
type: string
|
||||
prerelease_integrations_enabled:
|
||||
type: boolean
|
||||
delete_unenrolled_agents:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
is_preconfigured:
|
||||
type: boolean
|
||||
required:
|
||||
- fleet_server_hosts
|
||||
- id
|
||||
|
|
|
@ -12,6 +12,14 @@ properties:
|
|||
type: string
|
||||
prerelease_integrations_enabled:
|
||||
type: boolean
|
||||
delete_unenrolled_agents:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
is_preconfigured:
|
||||
type: boolean
|
||||
|
||||
required:
|
||||
- fleet_server_hosts
|
||||
- id
|
||||
|
|
|
@ -31,6 +31,13 @@ put:
|
|||
type: boolean
|
||||
additional_yaml_config:
|
||||
type: string
|
||||
delete_unenrolled_agents:
|
||||
type: object
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
is_preconfigured:
|
||||
type: boolean
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
export interface BaseSettings {
|
||||
has_seen_add_data_notice?: boolean;
|
||||
fleet_server_hosts?: string[];
|
||||
prerelease_integrations_enabled: boolean;
|
||||
prerelease_integrations_enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface Settings extends BaseSettings {
|
||||
|
@ -19,4 +19,8 @@ export interface Settings extends BaseSettings {
|
|||
output_secret_storage_requirements_met?: boolean;
|
||||
use_space_awareness_migration_status?: 'pending' | 'success' | 'error';
|
||||
use_space_awareness_migration_started_at?: string | null;
|
||||
delete_unenrolled_agents?: {
|
||||
enabled: boolean;
|
||||
is_preconfigured: boolean;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* 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 React, { useCallback, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
EuiTitle,
|
||||
EuiLink,
|
||||
EuiSpacer,
|
||||
EuiDescribedFormGroup,
|
||||
EuiSwitch,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import {
|
||||
useAuthz,
|
||||
useGetSettings,
|
||||
usePutSettingsMutation,
|
||||
useStartServices,
|
||||
} from '../../../../hooks';
|
||||
|
||||
export const AdvancedSection: React.FunctionComponent<{}> = ({}) => {
|
||||
const authz = useAuthz();
|
||||
const { docLinks, notifications } = useStartServices();
|
||||
const deleteUnenrolledAgents =
|
||||
useGetSettings().data?.item?.delete_unenrolled_agents?.enabled ?? false;
|
||||
const isPreconfigured =
|
||||
useGetSettings().data?.item?.delete_unenrolled_agents?.is_preconfigured ?? false;
|
||||
const [deleteUnenrolledAgentsChecked, setDeleteUnenrolledAgentsChecked] =
|
||||
React.useState<boolean>(deleteUnenrolledAgents);
|
||||
const { mutateAsync: mutateSettingsAsync } = usePutSettingsMutation();
|
||||
|
||||
useEffect(() => {
|
||||
if (deleteUnenrolledAgents) {
|
||||
setDeleteUnenrolledAgentsChecked(deleteUnenrolledAgents);
|
||||
}
|
||||
}, [deleteUnenrolledAgents]);
|
||||
|
||||
const updateSettings = useCallback(
|
||||
async (deleteFlag: boolean) => {
|
||||
try {
|
||||
setDeleteUnenrolledAgentsChecked(deleteFlag);
|
||||
const res = await mutateSettingsAsync({
|
||||
delete_unenrolled_agents: {
|
||||
enabled: deleteFlag,
|
||||
is_preconfigured: false,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.error) {
|
||||
throw res.error;
|
||||
}
|
||||
} catch (error) {
|
||||
setDeleteUnenrolledAgentsChecked(!deleteFlag);
|
||||
notifications.toasts.addError(error, {
|
||||
title: i18n.translate('xpack.fleet.errorUpdatingSettings', {
|
||||
defaultMessage: 'Error updating settings',
|
||||
}),
|
||||
});
|
||||
}
|
||||
},
|
||||
[mutateSettingsAsync, notifications.toasts]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiTitle size="s">
|
||||
<h4 data-test-subj="advancedHeader">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.advancedSectionTitle"
|
||||
defaultMessage="Advanced Settings"
|
||||
/>
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiForm component="form">
|
||||
<EuiDescribedFormGroup
|
||||
title={
|
||||
<h3>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.deleteUnenrolledAgentsLabel"
|
||||
defaultMessage="Delete unenrolled agents"
|
||||
/>
|
||||
</h3>
|
||||
}
|
||||
description={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.advancedSection.switchLabel"
|
||||
defaultMessage="Switching on this setting will enable auto deletion of unenrolled agents. For more information see our {docLink}."
|
||||
values={{
|
||||
docLink: (
|
||||
<EuiLink target="_blank" external href={docLinks.links.fleet.settings}>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.advancedSection.link"
|
||||
defaultMessage="docs"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
>
|
||||
<EuiFormRow label="">
|
||||
<EuiToolTip
|
||||
content={
|
||||
isPreconfigured
|
||||
? i18n.translate('xpack.fleet.settings.advancedSection.preconfiguredTitle', {
|
||||
defaultMessage: 'This setting is preconfigured and cannot be updated.',
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<EuiSwitch
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.settings.deleteUnenrolledAgentsLabel"
|
||||
defaultMessage="Delete unenrolled agents"
|
||||
/>
|
||||
}
|
||||
checked={deleteUnenrolledAgentsChecked}
|
||||
onChange={(e) => updateSettings(e.target.checked)}
|
||||
disabled={!authz.fleet.allSettings || isPreconfigured}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFormRow>
|
||||
</EuiDescribedFormGroup>
|
||||
</EuiForm>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -14,6 +14,7 @@ import { FleetServerHostsSection } from './fleet_server_hosts_section';
|
|||
import { OutputSection } from './output_section';
|
||||
import { AgentBinarySection } from './agent_binary_section';
|
||||
import { FleetProxiesSection } from './fleet_proxies_section';
|
||||
import { AdvancedSection } from './advanced_section';
|
||||
|
||||
export interface SettingsPageProps {
|
||||
outputs: Output[];
|
||||
|
@ -52,6 +53,8 @@ export const SettingsPage: React.FunctionComponent<SettingsPageProps> = ({
|
|||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<FleetProxiesSection proxies={proxies} deleteFleetProxy={deleteFleetProxy} />
|
||||
<EuiSpacer size="m" />
|
||||
<AdvancedSection />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -130,6 +130,7 @@ export const config: PluginConfigDescriptor = {
|
|||
schema: schema.object(
|
||||
{
|
||||
isAirGapped: schema.maybe(schema.boolean({ defaultValue: false })),
|
||||
enableDeleteUnenrolledAgents: schema.maybe(schema.boolean({ defaultValue: false })),
|
||||
registryUrl: schema.maybe(schema.uri({ scheme: ['http', 'https'] })),
|
||||
registryProxyUrl: schema.maybe(schema.uri({ scheme: ['http', 'https'] })),
|
||||
agents: schema.object({
|
||||
|
|
|
@ -100,6 +100,7 @@ export class OutputUnauthorizedError extends FleetError {}
|
|||
export class OutputInvalidError extends FleetError {}
|
||||
export class OutputLicenceError extends FleetError {}
|
||||
export class DownloadSourceError extends FleetError {}
|
||||
export class DeleteUnenrolledAgentsPreconfiguredError extends FleetError {}
|
||||
|
||||
// Not found errors
|
||||
export class AgentNotFoundError extends FleetNotFoundError {}
|
||||
|
|
|
@ -139,6 +139,7 @@ export const createAppContextStartContractMock = (
|
|||
}
|
||||
: {}),
|
||||
unenrollInactiveAgentsTask: {} as any,
|
||||
deleteUnenrolledAgentsTask: {} as any,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -142,6 +142,7 @@ import { fetchAgentMetrics } from './services/metrics/fetch_agent_metrics';
|
|||
import { registerFieldsMetadataExtractors } from './services/register_fields_metadata_extractors';
|
||||
import { registerUpgradeManagedPackagePoliciesTask } from './services/setup/managed_package_policies';
|
||||
import { registerDeployAgentPoliciesTask } from './services/agent_policies/deploy_agent_policies_task';
|
||||
import { DeleteUnenrolledAgentsTask } from './tasks/delete_unenrolled_agents_task';
|
||||
|
||||
export interface FleetSetupDeps {
|
||||
security: SecurityPluginSetup;
|
||||
|
@ -192,6 +193,7 @@ export interface FleetAppContext {
|
|||
auditLogger?: AuditLogger;
|
||||
uninstallTokenService: UninstallTokenServiceInterface;
|
||||
unenrollInactiveAgentsTask: UnenrollInactiveAgentsTask;
|
||||
deleteUnenrolledAgentsTask: DeleteUnenrolledAgentsTask;
|
||||
taskManagerStart?: TaskManagerStartContract;
|
||||
}
|
||||
|
||||
|
@ -284,6 +286,7 @@ export class FleetPlugin
|
|||
private checkDeletedFilesTask?: CheckDeletedFilesTask;
|
||||
private fleetMetricsTask?: FleetMetricsTask;
|
||||
private unenrollInactiveAgentsTask?: UnenrollInactiveAgentsTask;
|
||||
private deleteUnenrolledAgentsTask?: DeleteUnenrolledAgentsTask;
|
||||
|
||||
private agentService?: AgentService;
|
||||
private packageService?: PackageService;
|
||||
|
@ -628,6 +631,11 @@ export class FleetPlugin
|
|||
taskManager: deps.taskManager,
|
||||
logFactory: this.initializerContext.logger,
|
||||
});
|
||||
this.deleteUnenrolledAgentsTask = new DeleteUnenrolledAgentsTask({
|
||||
core,
|
||||
taskManager: deps.taskManager,
|
||||
logFactory: this.initializerContext.logger,
|
||||
});
|
||||
|
||||
// Register fields metadata extractors
|
||||
registerFieldsMetadataExtractors({ core, fieldsMetadata: deps.fieldsMetadata });
|
||||
|
@ -674,6 +682,7 @@ export class FleetPlugin
|
|||
messageSigningService,
|
||||
uninstallTokenService,
|
||||
unenrollInactiveAgentsTask: this.unenrollInactiveAgentsTask!,
|
||||
deleteUnenrolledAgentsTask: this.deleteUnenrolledAgentsTask!,
|
||||
taskManagerStart: plugins.taskManager,
|
||||
});
|
||||
licenseService.start(plugins.licensing.license$);
|
||||
|
@ -682,6 +691,7 @@ export class FleetPlugin
|
|||
this.fleetUsageSender?.start(plugins.taskManager).catch(() => {});
|
||||
this.checkDeletedFilesTask?.start({ taskManager: plugins.taskManager }).catch(() => {});
|
||||
this.unenrollInactiveAgentsTask?.start({ taskManager: plugins.taskManager }).catch(() => {});
|
||||
this.deleteUnenrolledAgentsTask?.start({ taskManager: plugins.taskManager }).catch(() => {});
|
||||
startFleetUsageLogger(plugins.taskManager).catch(() => {});
|
||||
this.fleetMetricsTask
|
||||
?.start(plugins.taskManager, core.elasticsearch.client.asInternalUser)
|
||||
|
|
|
@ -161,6 +161,12 @@ export const getSavedObjectTypes = (
|
|||
output_secret_storage_requirements_met: { type: 'boolean' },
|
||||
use_space_awareness_migration_status: { type: 'keyword', index: false },
|
||||
use_space_awareness_migration_started_at: { type: 'date', index: false },
|
||||
delete_unenrolled_agents: {
|
||||
properties: {
|
||||
enabled: { type: 'boolean', index: false },
|
||||
is_preconfigured: { type: 'boolean', index: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
migrations: {
|
||||
|
@ -181,6 +187,21 @@ export const getSavedObjectTypes = (
|
|||
},
|
||||
],
|
||||
},
|
||||
3: {
|
||||
changes: [
|
||||
{
|
||||
type: 'mappings_addition',
|
||||
addedMappings: {
|
||||
delete_unenrolled_agents: {
|
||||
properties: {
|
||||
enabled: { type: 'boolean', index: false },
|
||||
is_preconfigured: { type: 'boolean', index: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
[LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE]: {
|
||||
|
|
|
@ -13,7 +13,7 @@ import { getSettings } from '../../settings';
|
|||
export async function getPrereleaseFromSettings(
|
||||
savedObjectsClient: SavedObjectsClientContract
|
||||
): Promise<boolean> {
|
||||
let prerelease: boolean = false;
|
||||
let prerelease: boolean | undefined = false;
|
||||
try {
|
||||
({ prerelease_integrations_enabled: prerelease } = await getSettings(savedObjectsClient));
|
||||
} catch (err) {
|
||||
|
@ -21,5 +21,5 @@ export async function getPrereleaseFromSettings(
|
|||
.getLogger()
|
||||
.warn('Error while trying to load prerelease flag from settings, defaulting to false', err);
|
||||
}
|
||||
return prerelease;
|
||||
return prerelease ?? false;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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 { settingsService } from '..';
|
||||
|
||||
import { ensureDeleteUnenrolledAgentsSetting } from './delete_unenrolled_agent_setting';
|
||||
|
||||
jest.mock('..', () => ({
|
||||
settingsService: {
|
||||
getSettingsOrUndefined: jest.fn(),
|
||||
saveSettings: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('delete_unenrolled_agent_setting', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should update settings with delete_unenrolled_agents enabled', async () => {
|
||||
await ensureDeleteUnenrolledAgentsSetting({} as any, true);
|
||||
|
||||
expect(settingsService.saveSettings).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
{ delete_unenrolled_agents: { enabled: true, is_preconfigured: true } },
|
||||
{ fromSetup: true }
|
||||
);
|
||||
});
|
||||
|
||||
it('should update settings with delete_unenrolled_agents disabled', async () => {
|
||||
await ensureDeleteUnenrolledAgentsSetting({} as any, false);
|
||||
|
||||
expect(settingsService.saveSettings).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
{ delete_unenrolled_agents: { enabled: false, is_preconfigured: true } },
|
||||
{ fromSetup: true }
|
||||
);
|
||||
});
|
||||
|
||||
it('should update settings when previously preconfigured', async () => {
|
||||
(settingsService.getSettingsOrUndefined as jest.Mock).mockResolvedValue({
|
||||
delete_unenrolled_agents: {
|
||||
enabled: false,
|
||||
is_preconfigured: true,
|
||||
},
|
||||
});
|
||||
await ensureDeleteUnenrolledAgentsSetting({} as any);
|
||||
|
||||
expect(settingsService.saveSettings).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
{ delete_unenrolled_agents: { enabled: false, is_preconfigured: false } },
|
||||
{ fromSetup: true }
|
||||
);
|
||||
});
|
||||
|
||||
it('should not update settings when previously not preconfigured', async () => {
|
||||
(settingsService.getSettingsOrUndefined as jest.Mock).mockResolvedValue({
|
||||
delete_unenrolled_agents: {
|
||||
enabled: false,
|
||||
is_preconfigured: false,
|
||||
},
|
||||
});
|
||||
await ensureDeleteUnenrolledAgentsSetting({} as any);
|
||||
|
||||
expect(settingsService.saveSettings).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
||||
|
||||
import { settingsService } from '..';
|
||||
import type { FleetConfigType } from '../../config';
|
||||
|
||||
export function getPreconfiguredDeleteUnenrolledAgentsSettingFromConfig(
|
||||
config?: FleetConfigType
|
||||
): boolean | undefined {
|
||||
return config.enableDeleteUnenrolledAgents;
|
||||
}
|
||||
|
||||
export async function ensureDeleteUnenrolledAgentsSetting(
|
||||
soClient: SavedObjectsClientContract,
|
||||
enableDeleteUnenrolledAgents?: boolean
|
||||
) {
|
||||
if (enableDeleteUnenrolledAgents === undefined) {
|
||||
const settings = await settingsService.getSettingsOrUndefined(soClient);
|
||||
if (!settings?.delete_unenrolled_agents?.is_preconfigured) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await settingsService.saveSettings(
|
||||
soClient,
|
||||
{
|
||||
delete_unenrolled_agents: {
|
||||
enabled: !!enableDeleteUnenrolledAgents,
|
||||
is_preconfigured: enableDeleteUnenrolledAgents !== undefined,
|
||||
},
|
||||
},
|
||||
{ fromSetup: true }
|
||||
);
|
||||
}
|
|
@ -14,6 +14,8 @@ import { GLOBAL_SETTINGS_ID, GLOBAL_SETTINGS_SAVED_OBJECT_TYPE } from '../../com
|
|||
|
||||
import type { Settings } from '../types';
|
||||
|
||||
import { DeleteUnenrolledAgentsPreconfiguredError } from '../errors';
|
||||
|
||||
import { appContextService } from './app_context';
|
||||
import { getSettings, saveSettings, settingsSetup } from './settings';
|
||||
import { auditLoggingService } from './audit_logging';
|
||||
|
@ -225,4 +227,119 @@ describe('saveSettings', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow updating preconfigured setting if called from setup', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
|
||||
const newData: Partial<Omit<Settings, 'id'>> = {
|
||||
delete_unenrolled_agents: {
|
||||
enabled: true,
|
||||
is_preconfigured: true,
|
||||
},
|
||||
};
|
||||
|
||||
soClient.find.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: GLOBAL_SETTINGS_ID,
|
||||
attributes: {
|
||||
delete_unenrolled_agents: {
|
||||
enabled: false,
|
||||
is_preconfigured: true,
|
||||
},
|
||||
},
|
||||
references: [],
|
||||
type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
|
||||
score: 0,
|
||||
},
|
||||
],
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
total: 1,
|
||||
});
|
||||
mockListFleetServerHosts.mockResolvedValueOnce({
|
||||
items: [
|
||||
{
|
||||
id: 'fleet-server-host',
|
||||
name: 'Fleet Server Host',
|
||||
is_default: true,
|
||||
is_preconfigured: false,
|
||||
host_urls: ['http://localhost:8220'],
|
||||
},
|
||||
],
|
||||
page: 1,
|
||||
perPage: 10,
|
||||
total: 1,
|
||||
});
|
||||
|
||||
soClient.update.mockResolvedValueOnce({
|
||||
id: GLOBAL_SETTINGS_ID,
|
||||
attributes: {},
|
||||
references: [],
|
||||
type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
|
||||
});
|
||||
|
||||
await saveSettings(soClient, newData, { fromSetup: true });
|
||||
|
||||
expect(soClient.update).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not allow updating preconfigured setting if not called from setup', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
|
||||
const newData: Partial<Omit<Settings, 'id'>> = {
|
||||
delete_unenrolled_agents: {
|
||||
enabled: true,
|
||||
is_preconfigured: true,
|
||||
},
|
||||
};
|
||||
|
||||
soClient.find.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: GLOBAL_SETTINGS_ID,
|
||||
attributes: {
|
||||
delete_unenrolled_agents: {
|
||||
enabled: false,
|
||||
is_preconfigured: true,
|
||||
},
|
||||
},
|
||||
references: [],
|
||||
type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
|
||||
score: 0,
|
||||
},
|
||||
],
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
total: 1,
|
||||
});
|
||||
mockListFleetServerHosts.mockResolvedValueOnce({
|
||||
items: [
|
||||
{
|
||||
id: 'fleet-server-host',
|
||||
name: 'Fleet Server Host',
|
||||
is_default: true,
|
||||
is_preconfigured: false,
|
||||
host_urls: ['http://localhost:8220'],
|
||||
},
|
||||
],
|
||||
page: 1,
|
||||
perPage: 10,
|
||||
total: 1,
|
||||
});
|
||||
|
||||
soClient.update.mockResolvedValueOnce({
|
||||
id: GLOBAL_SETTINGS_ID,
|
||||
attributes: {},
|
||||
references: [],
|
||||
type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
|
||||
});
|
||||
|
||||
try {
|
||||
await saveSettings(soClient, newData);
|
||||
fail('Expected to throw');
|
||||
} catch (e) {
|
||||
expect(e).toBeInstanceOf(DeleteUnenrolledAgentsPreconfiguredError);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,6 +13,8 @@ import { GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, GLOBAL_SETTINGS_ID } from '../../com
|
|||
import type { Settings, BaseSettings } from '../../common/types';
|
||||
import type { SettingsSOAttributes } from '../types';
|
||||
|
||||
import { DeleteUnenrolledAgentsPreconfiguredError } from '../errors';
|
||||
|
||||
import { appContextService } from './app_context';
|
||||
import { listFleetServerHosts } from './fleet_server_host';
|
||||
import { auditLoggingService } from './audit_logging';
|
||||
|
@ -39,6 +41,7 @@ export async function getSettings(soClient: SavedObjectsClientContract): Promise
|
|||
...settingsSo.attributes,
|
||||
fleet_server_hosts: fleetServerHosts.items.flatMap((item) => item.host_urls),
|
||||
preconfigured_fields: getConfigFleetServerHosts() ? ['fleet_server_hosts'] : [],
|
||||
delete_unenrolled_agents: settingsSo.attributes.delete_unenrolled_agents,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -72,7 +75,10 @@ export async function settingsSetup(soClient: SavedObjectsClientContract) {
|
|||
export async function saveSettings(
|
||||
soClient: SavedObjectsClientContract,
|
||||
newData: Partial<Omit<Settings, 'id'>>,
|
||||
options?: SavedObjectsUpdateOptions<SettingsSOAttributes> & { createWithOverwrite?: boolean }
|
||||
options?: SavedObjectsUpdateOptions<SettingsSOAttributes> & {
|
||||
createWithOverwrite?: boolean;
|
||||
fromSetup?: boolean;
|
||||
}
|
||||
): Promise<Partial<Settings> & Pick<Settings, 'id'>> {
|
||||
const data = { ...newData };
|
||||
if (data.fleet_server_hosts) {
|
||||
|
@ -83,6 +89,16 @@ export async function saveSettings(
|
|||
try {
|
||||
const settings = await getSettings(soClient);
|
||||
|
||||
if (
|
||||
!options?.fromSetup &&
|
||||
settings.delete_unenrolled_agents?.is_preconfigured &&
|
||||
data.delete_unenrolled_agents
|
||||
) {
|
||||
throw new DeleteUnenrolledAgentsPreconfiguredError(
|
||||
`Setting delete_unenrolled_agents is preconfigured as 'enableDeleteUnenrolledAgents' and cannot be updated outside of kibana config file.`
|
||||
);
|
||||
}
|
||||
|
||||
auditLoggingService.writeCustomSoAuditLog({
|
||||
action: 'update',
|
||||
id: settings.id,
|
||||
|
|
|
@ -53,6 +53,10 @@ import { cleanUpOldFileIndices } from './setup/clean_old_fleet_indices';
|
|||
import type { UninstallTokenInvalidError } from './security/uninstall_token_service';
|
||||
import { ensureAgentPoliciesFleetServerKeysAndPolicies } from './setup/fleet_server_policies_enrollment_keys';
|
||||
import { ensureSpaceSettings } from './preconfiguration/space_settings';
|
||||
import {
|
||||
ensureDeleteUnenrolledAgentsSetting,
|
||||
getPreconfiguredDeleteUnenrolledAgentsSettingFromConfig,
|
||||
} from './preconfiguration/delete_unenrolled_agent_setting';
|
||||
|
||||
export interface SetupStatus {
|
||||
isInitialized: boolean;
|
||||
|
@ -195,6 +199,12 @@ async function createSetupSideEffects(
|
|||
logger.debug('Setting up Space settings');
|
||||
await ensureSpaceSettings(appContextService.getConfig()?.spaceSettings ?? []);
|
||||
|
||||
logger.debug('Setting up delete unenrolled agents setting');
|
||||
await ensureDeleteUnenrolledAgentsSetting(
|
||||
soClient,
|
||||
getPreconfiguredDeleteUnenrolledAgentsSettingFromConfig(appContextService.getConfig())
|
||||
);
|
||||
|
||||
logger.debug('Setting up Fleet outputs');
|
||||
await Promise.all([
|
||||
ensurePreconfiguredOutputs(
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* 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 type { ElasticsearchClientMock } from '@kbn/core/server/mocks';
|
||||
import { coreMock } from '@kbn/core/server/mocks';
|
||||
import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks';
|
||||
import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server';
|
||||
import { TaskStatus } from '@kbn/task-manager-plugin/server';
|
||||
import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task';
|
||||
import type { CoreSetup } from '@kbn/core/server';
|
||||
import { loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
|
||||
import { settingsService } from '../services';
|
||||
import { createAppContextStartContractMock } from '../mocks';
|
||||
|
||||
import { appContextService } from '../services';
|
||||
|
||||
import { DeleteUnenrolledAgentsTask, TYPE, VERSION } from './delete_unenrolled_agents_task';
|
||||
|
||||
jest.mock('../services');
|
||||
|
||||
const MOCK_TASK_INSTANCE = {
|
||||
id: `${TYPE}:${VERSION}`,
|
||||
runAt: new Date(),
|
||||
attempts: 0,
|
||||
ownerId: '',
|
||||
status: TaskStatus.Running,
|
||||
startedAt: new Date(),
|
||||
scheduledAt: new Date(),
|
||||
retryAt: new Date(),
|
||||
params: {},
|
||||
state: {},
|
||||
taskType: TYPE,
|
||||
};
|
||||
|
||||
describe('DeleteUnenrolledAgentsTask', () => {
|
||||
const { createSetup: coreSetupMock } = coreMock;
|
||||
const { createSetup: tmSetupMock, createStart: tmStartMock } = taskManagerMock;
|
||||
|
||||
let mockContract: ReturnType<typeof createAppContextStartContractMock>;
|
||||
let mockTask: DeleteUnenrolledAgentsTask;
|
||||
let mockCore: CoreSetup;
|
||||
let mockTaskManagerSetup: jest.Mocked<TaskManagerSetupContract>;
|
||||
const mockSettingsService = settingsService as jest.Mocked<typeof settingsService>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockContract = createAppContextStartContractMock();
|
||||
appContextService.start(mockContract);
|
||||
mockCore = coreSetupMock();
|
||||
mockTaskManagerSetup = tmSetupMock();
|
||||
mockTask = new DeleteUnenrolledAgentsTask({
|
||||
core: mockCore,
|
||||
taskManager: mockTaskManagerSetup,
|
||||
logFactory: loggingSystemMock.create(),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Task lifecycle', () => {
|
||||
it('Should create task', () => {
|
||||
expect(mockTask).toBeInstanceOf(DeleteUnenrolledAgentsTask);
|
||||
});
|
||||
|
||||
it('Should register task', () => {
|
||||
expect(mockTaskManagerSetup.registerTaskDefinitions).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Should schedule task', async () => {
|
||||
const mockTaskManagerStart = tmStartMock();
|
||||
await mockTask.start({ taskManager: mockTaskManagerStart });
|
||||
expect(mockTaskManagerStart.ensureScheduled).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Task logic', () => {
|
||||
let esClient: ElasticsearchClientMock;
|
||||
const runTask = async (taskInstance = MOCK_TASK_INSTANCE) => {
|
||||
const mockTaskManagerStart = tmStartMock();
|
||||
await mockTask.start({ taskManager: mockTaskManagerStart });
|
||||
const createTaskRunner =
|
||||
mockTaskManagerSetup.registerTaskDefinitions.mock.calls[0][0][TYPE].createTaskRunner;
|
||||
const taskRunner = createTaskRunner({ taskInstance });
|
||||
return taskRunner.run();
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const [{ elasticsearch }] = await mockCore.getStartServices();
|
||||
esClient = elasticsearch.client.asInternalUser as ElasticsearchClientMock;
|
||||
esClient.deleteByQuery.mockResolvedValue({ deleted: 10 });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('Should delete unenrolled agents', async () => {
|
||||
mockSettingsService.getSettingsOrUndefined.mockResolvedValue({
|
||||
delete_unenrolled_agents: {
|
||||
enabled: true,
|
||||
is_preconfigured: false,
|
||||
},
|
||||
id: '1',
|
||||
});
|
||||
|
||||
await runTask();
|
||||
|
||||
expect(esClient.deleteByQuery).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Should not run if task is outdated', async () => {
|
||||
const result = await runTask({ ...MOCK_TASK_INSTANCE, id: 'old-id' });
|
||||
|
||||
expect(esClient.deleteByQuery).not.toHaveBeenCalled();
|
||||
expect(result).toEqual(getDeleteTaskRunResult());
|
||||
});
|
||||
|
||||
it('Should exit if delete unenrolled agents flag is false', async () => {
|
||||
mockSettingsService.getSettingsOrUndefined.mockResolvedValue({
|
||||
delete_unenrolled_agents: {
|
||||
enabled: false,
|
||||
is_preconfigured: false,
|
||||
},
|
||||
id: '1',
|
||||
});
|
||||
|
||||
await runTask();
|
||||
|
||||
expect(esClient.deleteByQuery).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* 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 { SavedObjectsClient } from '@kbn/core/server';
|
||||
import type {
|
||||
CoreSetup,
|
||||
ElasticsearchClient,
|
||||
Logger,
|
||||
SavedObjectsClientContract,
|
||||
} from '@kbn/core/server';
|
||||
import type {
|
||||
ConcreteTaskInstance,
|
||||
TaskManagerSetupContract,
|
||||
TaskManagerStartContract,
|
||||
} from '@kbn/task-manager-plugin/server';
|
||||
import { getDeleteTaskRunResult } from '@kbn/task-manager-plugin/server/task';
|
||||
import type { LoggerFactory } from '@kbn/core/server';
|
||||
import { errors } from '@elastic/elasticsearch';
|
||||
|
||||
import { AGENTS_INDEX } from '../../common/constants';
|
||||
|
||||
import { settingsService } from '../services';
|
||||
|
||||
export const TYPE = 'fleet:delete-unenrolled-agents-task';
|
||||
export const VERSION = '1.0.0';
|
||||
const TITLE = 'Fleet Delete Unenrolled Agents Task';
|
||||
const SCOPE = ['fleet'];
|
||||
const INTERVAL = '1h';
|
||||
const TIMEOUT = '1m';
|
||||
|
||||
interface DeleteUnenrolledAgentsTaskSetupContract {
|
||||
core: CoreSetup;
|
||||
taskManager: TaskManagerSetupContract;
|
||||
logFactory: LoggerFactory;
|
||||
}
|
||||
|
||||
interface DeleteUnenrolledAgentsTaskStartContract {
|
||||
taskManager: TaskManagerStartContract;
|
||||
}
|
||||
|
||||
export class DeleteUnenrolledAgentsTask {
|
||||
private logger: Logger;
|
||||
private wasStarted: boolean = false;
|
||||
private abortController = new AbortController();
|
||||
|
||||
constructor(setupContract: DeleteUnenrolledAgentsTaskSetupContract) {
|
||||
const { core, taskManager, logFactory } = setupContract;
|
||||
this.logger = logFactory.get(this.taskId);
|
||||
|
||||
taskManager.registerTaskDefinitions({
|
||||
[TYPE]: {
|
||||
title: TITLE,
|
||||
timeout: TIMEOUT,
|
||||
createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => {
|
||||
return {
|
||||
run: async () => {
|
||||
return this.runTask(taskInstance, core);
|
||||
},
|
||||
cancel: async () => {
|
||||
this.abortController.abort('Task timed out');
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public start = async ({ taskManager }: DeleteUnenrolledAgentsTaskStartContract) => {
|
||||
if (!taskManager) {
|
||||
this.logger.error('[DeleteUnenrolledAgentsTask] Missing required service during start');
|
||||
return;
|
||||
}
|
||||
|
||||
this.wasStarted = true;
|
||||
this.logger.info(`[DeleteUnenrolledAgentsTask] Started with interval of [${INTERVAL}]`);
|
||||
|
||||
try {
|
||||
await taskManager.ensureScheduled({
|
||||
id: this.taskId,
|
||||
taskType: TYPE,
|
||||
scope: SCOPE,
|
||||
schedule: {
|
||||
interval: INTERVAL,
|
||||
},
|
||||
state: {},
|
||||
params: { version: VERSION },
|
||||
});
|
||||
} catch (e) {
|
||||
this.logger.error(`Error scheduling task DeleteUnenrolledAgentsTask, error: ${e.message}`, e);
|
||||
}
|
||||
};
|
||||
|
||||
private get taskId(): string {
|
||||
return `${TYPE}:${VERSION}`;
|
||||
}
|
||||
|
||||
private endRun(msg: string = '') {
|
||||
this.logger.info(`[DeleteUnenrolledAgentsTask] runTask ended${msg ? ': ' + msg : ''}`);
|
||||
}
|
||||
|
||||
public async deleteUnenrolledAgents(esClient: ElasticsearchClient) {
|
||||
this.logger.debug(`[DeleteUnenrolledAgentsTask] Fetching unenrolled agents`);
|
||||
|
||||
const response = await esClient.deleteByQuery(
|
||||
{
|
||||
index: AGENTS_INDEX,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
term: {
|
||||
active: false,
|
||||
},
|
||||
},
|
||||
{ exists: { field: 'unenrolled_at' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ signal: this.abortController.signal }
|
||||
);
|
||||
|
||||
this.logger.debug(
|
||||
`[DeleteUnenrolledAgentsTask] Executed deletion of ${response.deleted} unenrolled agents`
|
||||
);
|
||||
}
|
||||
|
||||
public async isDeleteUnenrolledAgentsEnabled(
|
||||
soClient: SavedObjectsClientContract
|
||||
): Promise<boolean> {
|
||||
const settings = await settingsService.getSettingsOrUndefined(soClient);
|
||||
return settings?.delete_unenrolled_agents?.enabled ?? false;
|
||||
}
|
||||
|
||||
public runTask = async (taskInstance: ConcreteTaskInstance, core: CoreSetup) => {
|
||||
if (!this.wasStarted) {
|
||||
this.logger.debug('[DeleteUnenrolledAgentsTask] runTask Aborted. Task not started yet');
|
||||
return;
|
||||
}
|
||||
// Check that this task is current
|
||||
if (taskInstance.id !== this.taskId) {
|
||||
this.logger.debug(
|
||||
`[DeleteUnenrolledAgentsTask] Outdated task version: Got [${taskInstance.id}] from task instance. Current version is [${this.taskId}]`
|
||||
);
|
||||
return getDeleteTaskRunResult();
|
||||
}
|
||||
|
||||
this.logger.info(`[runTask()] started`);
|
||||
|
||||
const [coreStart] = await core.getStartServices();
|
||||
const esClient = coreStart.elasticsearch.client.asInternalUser;
|
||||
const soClient = new SavedObjectsClient(coreStart.savedObjects.createInternalRepository());
|
||||
|
||||
try {
|
||||
if (!(await this.isDeleteUnenrolledAgentsEnabled(soClient))) {
|
||||
this.logger.debug(
|
||||
'[DeleteUnenrolledAgentsTask] Delete unenrolled agents flag is disabled, returning.'
|
||||
);
|
||||
this.endRun('Delete unenrolled agents is disabled');
|
||||
return;
|
||||
}
|
||||
await this.deleteUnenrolledAgents(esClient);
|
||||
|
||||
this.endRun('success');
|
||||
} catch (err) {
|
||||
if (err instanceof errors.RequestAbortedError) {
|
||||
this.logger.warn(`[DeleteUnenrolledAgentsTask] request aborted due to timeout: ${err}`);
|
||||
this.endRun();
|
||||
return;
|
||||
}
|
||||
this.logger.error(`[DeleteUnenrolledAgentsTask] error: ${err}`);
|
||||
this.endRun('error');
|
||||
}
|
||||
};
|
||||
}
|
|
@ -36,6 +36,12 @@ export const PutSettingsRequestSchema = {
|
|||
),
|
||||
kibana_ca_sha256: schema.maybe(schema.string()),
|
||||
prerelease_integrations_enabled: schema.maybe(schema.boolean()),
|
||||
delete_unenrolled_agents: schema.maybe(
|
||||
schema.object({
|
||||
enabled: schema.boolean(),
|
||||
is_preconfigured: schema.boolean(),
|
||||
})
|
||||
),
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
|
@ -234,11 +234,15 @@ export type OutputSOAttributes =
|
|||
| OutputSoKafkaAttributes;
|
||||
|
||||
export interface SettingsSOAttributes {
|
||||
prerelease_integrations_enabled: boolean;
|
||||
prerelease_integrations_enabled?: boolean;
|
||||
has_seen_add_data_notice?: boolean;
|
||||
fleet_server_hosts?: string[];
|
||||
secret_storage_requirements_met?: boolean;
|
||||
output_secret_storage_requirements_met?: boolean;
|
||||
delete_unenrolled_agents?: {
|
||||
enabled: boolean;
|
||||
is_preconfigured: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SpaceSettingsSOAttributes {
|
||||
|
|
|
@ -139,6 +139,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
'endpoint:metadata-check-transforms-task',
|
||||
'endpoint:user-artifact-packager',
|
||||
'fleet:check-deleted-files-task',
|
||||
'fleet:delete-unenrolled-agents-task',
|
||||
'fleet:deploy_agent_policies',
|
||||
'fleet:reassign_action:retry',
|
||||
'fleet:request_diagnostics:retry',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue