diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index fda07095de95d..9bc935703df9a 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -533,6 +533,10 @@ export type PackageInfo = | Installable> | Installable>; +export interface PackageMetadata { + has_policies: true; +} + export type IntegrationCardReleaseLabel = 'beta' | 'preview' | 'ga' | 'rc'; export type PackageVerificationStatus = 'verified' | 'unverified' | 'unknown'; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/epm.ts b/x-pack/plugins/fleet/common/types/rest_spec/epm.ts index 58f42b08e0612..b897d84152a3e 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/epm.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/epm.ts @@ -19,6 +19,7 @@ import type { SimpleSOAssetType, AssetSOObject, InstallResultStatus, + PackageMetadata, } from '../models/epm'; export interface GetCategoriesRequest { @@ -97,6 +98,7 @@ export interface GetInfoRequest { export interface GetInfoResponse { item: PackageInfo; + metadata?: PackageMetadata; // deprecated in 8.0 response?: PackageInfo; } diff --git a/x-pack/plugins/fleet/cypress/e2e/install_assets.cy.ts b/x-pack/plugins/fleet/cypress/e2e/install_assets.cy.ts index e22d601d06c33..285e85b35b01b 100644 --- a/x-pack/plugins/fleet/cypress/e2e/install_assets.cy.ts +++ b/x-pack/plugins/fleet/cypress/e2e/install_assets.cy.ts @@ -46,6 +46,7 @@ describe('Install unverified package assets', () => { if (res.body?.item?.status) { res.body.item.status = 'not_installed'; } + res.body.metadata = { has_policies: false }; }); }); }); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index c688da76819d4..f826b4f5c308e 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -210,6 +210,7 @@ export function Detail() { pkgVersion, { prerelease: prereleaseIntegrationsEnabled, + withMetadata: true, }, { enabled: !authz.fleet.readSettings || !isSettingsInitialLoading, // Load only after settings are loaded @@ -785,7 +786,11 @@ export function Detail() { /> - + diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx index 769f979e83bf3..33c866c6084a0 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx @@ -29,7 +29,7 @@ import { } from '../../../../../../../components/transform_install_as_current_user_callout'; import type { FleetStartServices } from '../../../../../../../plugin'; -import type { PackageInfo } from '../../../../../types'; +import type { PackageInfo, PackageMetadata } from '../../../../../types'; import { InstallStatus } from '../../../../../types'; import { useGetPackagePoliciesQuery, @@ -117,240 +117,367 @@ const LatestVersionLink = ({ name, version }: { name: string; version: string }) interface Props { packageInfo: PackageInfo; + packageMetadata?: PackageMetadata; startServices: Pick; } -export const SettingsPage: React.FC = memo(({ packageInfo, startServices }: Props) => { - const { name, title, latestVersion, version, keepPoliciesUpToDate } = packageInfo; - const [isUpgradingPackagePolicies, setIsUpgradingPackagePolicies] = useState(false); - const [isChangelogModalOpen, setIsChangelogModalOpen] = useState(false); - - const toggleChangelogModal = useCallback(() => { - setIsChangelogModalOpen(!isChangelogModalOpen); - }, [isChangelogModalOpen]); - const getPackageInstallStatus = useGetPackageInstallStatus(); - - const { data: packagePoliciesData } = useGetPackagePoliciesQuery({ - perPage: SO_SEARCH_LIMIT, - page: 1, - kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${name}`, - }); - - const packagePolicyIds = useMemo( - () => packagePoliciesData?.items.map(({ id }) => id), - [packagePoliciesData] - ); +export const SettingsPage: React.FC = memo( + ({ packageInfo, packageMetadata, startServices }: Props) => { + const { name, title, latestVersion, version, keepPoliciesUpToDate } = packageInfo; + const [isUpgradingPackagePolicies, setIsUpgradingPackagePolicies] = useState(false); + const [isChangelogModalOpen, setIsChangelogModalOpen] = useState(false); + + const toggleChangelogModal = useCallback(() => { + setIsChangelogModalOpen(!isChangelogModalOpen); + }, [isChangelogModalOpen]); + const getPackageInstallStatus = useGetPackageInstallStatus(); + + const { data: packagePoliciesData } = useGetPackagePoliciesQuery({ + perPage: SO_SEARCH_LIMIT, + page: 1, + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${name}`, + }); + + const packagePolicyIds = useMemo( + () => packagePoliciesData?.items.map(({ id }) => id), + [packagePoliciesData] + ); - const agentPolicyIds = useMemo( - () => packagePoliciesData?.items.flatMap((packagePolicy) => packagePolicy.policy_ids) ?? [], - [packagePoliciesData] - ); + const agentPolicyIds = useMemo( + () => packagePoliciesData?.items.flatMap((packagePolicy) => packagePolicy.policy_ids) ?? [], + [packagePoliciesData] + ); - const { data: dryRunData } = useUpgradePackagePolicyDryRunQuery( - packagePolicyIds ?? [], - latestVersion, - { - enabled: packagePolicyIds && packagePolicyIds.length > 0, - } - ); + const { data: dryRunData } = useUpgradePackagePolicyDryRunQuery( + packagePolicyIds ?? [], + latestVersion, + { + enabled: packagePolicyIds && packagePolicyIds.length > 0, + } + ); - const updatePackageMutation = useUpdatePackageMutation(); + const updatePackageMutation = useUpdatePackageMutation(); - const { notifications } = useStartServices(); + const { notifications } = useStartServices(); - const shouldShowKeepPoliciesUpToDateSwitch = useMemo(() => { - return KEEP_POLICIES_UP_TO_DATE_PACKAGES.some((pkg) => pkg.name === name); - }, [name]); + const shouldShowKeepPoliciesUpToDateSwitch = useMemo(() => { + return KEEP_POLICIES_UP_TO_DATE_PACKAGES.some((pkg) => pkg.name === name); + }, [name]); - const isShowKeepPoliciesUpToDateSwitchDisabled = useMemo(() => { - return AUTO_UPGRADE_POLICIES_PACKAGES.some((pkg) => pkg.name === name); - }, [name]); + const isShowKeepPoliciesUpToDateSwitchDisabled = useMemo(() => { + return AUTO_UPGRADE_POLICIES_PACKAGES.some((pkg) => pkg.name === name); + }, [name]); - const [keepPoliciesUpToDateSwitchValue, setKeepPoliciesUpToDateSwitchValue] = useState( - keepPoliciesUpToDate ?? false - ); + const [keepPoliciesUpToDateSwitchValue, setKeepPoliciesUpToDateSwitchValue] = useState( + keepPoliciesUpToDate ?? false + ); - const handleKeepPoliciesUpToDateSwitchChange = useCallback(() => { - setKeepPoliciesUpToDateSwitchValue((prev) => !prev); + const handleKeepPoliciesUpToDateSwitchChange = useCallback(() => { + setKeepPoliciesUpToDateSwitchValue((prev) => !prev); - updatePackageMutation.mutate( - { - pkgName: packageInfo.name, - pkgVersion: packageInfo.version, - body: { - keepPoliciesUpToDate: !keepPoliciesUpToDateSwitchValue, - }, - }, - { - onSuccess: () => { - notifications.toasts.addSuccess({ - title: i18n.translate('xpack.fleet.integrations.integrationSaved', { - defaultMessage: 'Integration settings saved', - }), - text: !keepPoliciesUpToDateSwitchValue - ? i18n.translate('xpack.fleet.integrations.keepPoliciesUpToDateEnabledSuccess', { - defaultMessage: - 'Fleet will automatically keep integration policies up to date for {title}', - values: { title }, - }) - : i18n.translate('xpack.fleet.integrations.keepPoliciesUpToDateDisabledSuccess', { - defaultMessage: - 'Fleet will not automatically keep integration policies up to date for {title}', - values: { title }, - }), - }); - }, - onError: (error) => { - notifications.toasts.addError(error, { - title: i18n.translate('xpack.fleet.integrations.integrationSavedError', { - defaultMessage: 'Error saving integration settings', - }), - toastMessage: i18n.translate('xpack.fleet.integrations.keepPoliciesUpToDateError', { - defaultMessage: 'Error saving integration settings for {title}', - values: { title }, - }), - }); + updatePackageMutation.mutate( + { + pkgName: packageInfo.name, + pkgVersion: packageInfo.version, + body: { + keepPoliciesUpToDate: !keepPoliciesUpToDateSwitchValue, + }, }, - } + { + onSuccess: () => { + notifications.toasts.addSuccess({ + title: i18n.translate('xpack.fleet.integrations.integrationSaved', { + defaultMessage: 'Integration settings saved', + }), + text: !keepPoliciesUpToDateSwitchValue + ? i18n.translate('xpack.fleet.integrations.keepPoliciesUpToDateEnabledSuccess', { + defaultMessage: + 'Fleet will automatically keep integration policies up to date for {title}', + values: { title }, + }) + : i18n.translate('xpack.fleet.integrations.keepPoliciesUpToDateDisabledSuccess', { + defaultMessage: + 'Fleet will not automatically keep integration policies up to date for {title}', + values: { title }, + }), + }); + }, + onError: (error) => { + notifications.toasts.addError(error, { + title: i18n.translate('xpack.fleet.integrations.integrationSavedError', { + defaultMessage: 'Error saving integration settings', + }), + toastMessage: i18n.translate('xpack.fleet.integrations.keepPoliciesUpToDateError', { + defaultMessage: 'Error saving integration settings for {title}', + values: { title }, + }), + }); + }, + } + ); + }, [ + keepPoliciesUpToDateSwitchValue, + notifications.toasts, + packageInfo.name, + packageInfo.version, + title, + updatePackageMutation, + ]); + + const { status: installationStatus, version: installedVersion } = getPackageInstallStatus(name); + + const updateAvailable = + installedVersion && semverLt(installedVersion, latestVersion) ? true : false; + + const isViewingOldPackage = version < latestVersion; + // hide install/remove options if the user has version of the package is installed + // and this package is out of date or if they do have a version installed but it's not this one + const hideInstallOptions = + (installationStatus === InstallStatus.notInstalled && isViewingOldPackage) || + (installationStatus === InstallStatus.installed && installedVersion !== version); + + const isUpdating = installationStatus === InstallStatus.installing && installedVersion; + + const { numOfAssets, numTransformAssets } = useMemo( + () => ({ + numTransformAssets: getNumTransformAssets(packageInfo.assets), + numOfAssets: Object.entries(packageInfo.assets).reduce( + (acc, [serviceName, serviceNameValue]) => + acc + + Object.entries(serviceNameValue).reduce( + (acc2, [assetName, assetNameValue]) => acc2 + assetNameValue.length, + 0 + ), + 0 + ), + }), + [packageInfo.assets] ); - }, [ - keepPoliciesUpToDateSwitchValue, - notifications.toasts, - packageInfo.name, - packageInfo.version, - title, - updatePackageMutation, - ]); - - const { status: installationStatus, version: installedVersion } = getPackageInstallStatus(name); - const packageHasUsages = !!packagePoliciesData?.total; - - const updateAvailable = - installedVersion && semverLt(installedVersion, latestVersion) ? true : false; - - const isViewingOldPackage = version < latestVersion; - // hide install/remove options if the user has version of the package is installed - // and this package is out of date or if they do have a version installed but it's not this one - const hideInstallOptions = - (installationStatus === InstallStatus.notInstalled && isViewingOldPackage) || - (installationStatus === InstallStatus.installed && installedVersion !== version); - - const isUpdating = installationStatus === InstallStatus.installing && installedVersion; - - const { numOfAssets, numTransformAssets } = useMemo( - () => ({ - numTransformAssets: getNumTransformAssets(packageInfo.assets), - numOfAssets: Object.entries(packageInfo.assets).reduce( - (acc, [serviceName, serviceNameValue]) => - acc + - Object.entries(serviceNameValue).reduce( - (acc2, [assetName, assetNameValue]) => acc2 + assetNameValue.length, - 0 - ), - 0 - ), - }), - [packageInfo.assets] - ); - return ( - <> - - - - - -

- -

-
- - {installedVersion !== null && ( -
- -

- -

-
- - - - - - + + + + + +

+ +

+
+ + {installedVersion !== null && ( +
+ +

+ +

+
+ +
+ + + + + + + + + + + + + + +
+ + {installedVersion} + +
+ + {latestVersion} + +
+ {shouldShowKeepPoliciesUpToDateSwitch && ( + <> + + + + )} + + {(updateAvailable || isUpgradingPackagePolicies) && ( + <> + + +

+ - - - - {installedVersion} - - - - - +

+ + )} +
+ )} + {!hideInstallOptions && !isUpdating && ( +
+ + {installationStatus === InstallStatus.notInstalled || + installationStatus === InstallStatus.installing ? ( +
+ +

+ +

+
+ + + {numTransformAssets > 0 ? ( + <> + + + + ) : null} +

- - - - {latestVersion} - - - - - - {shouldShowKeepPoliciesUpToDateSwitch && ( - <> - - - - )} - - {(updateAvailable || isUpgradingPackagePolicies) && ( - <> - - -

- -

- - )} -
- )} - {!hideInstallOptions && !isUpdating && ( -
- - {installationStatus === InstallStatus.notInstalled || - installationStatus === InstallStatus.installing ? ( +

+ + +

+ +

+
+
+
+ ) : ( + <> + + + +

+ +

+
+
+ + + + +
+ +
+
+ {packageMetadata?.has_policies && ( + + + , + }} + /> + + + )} +
+ + + + +

+ +

+
+
+ + + + +
+ +
+
+
+ + )} +
+ )} + {hideInstallOptions && isViewingOldPackage && !isUpdating && ( +
+

@@ -364,160 +491,37 @@ export const SettingsPage: React.FC = memo(({ packageInfo, startServices

- - {numTransformAssets > 0 ? ( - <> - - - - ) : null}

- -

- - -

- -

-
-
-
- ) : ( - <> - - - -

- -

-
-
- + - - -
- -
-
- {packageHasUsages && ( - - - , - }} - /> - - - )} -
- - - - -

- -

-
-
- - + ), + }} /> - - -
- -
-
-
- - )} -
- )} - {hideInstallOptions && isViewingOldPackage && !isUpdating && ( -
- -
- -

- -

-
- -

- - , - }} - /> - -

+ +

+
- - )} -
-
-
- - {isChangelogModalOpen && ( - - )} - - - ); -}); + )} + + + + + {isChangelogModalOpen && ( + + )} + + + ); + } +); diff --git a/x-pack/plugins/fleet/public/hooks/use_request/epm.ts b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts index bd4bec9be6a1a..1ea668fd8955c 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/epm.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts @@ -123,6 +123,7 @@ export const useGetPackageInfoByKeyQuery = ( ignoreUnverified?: boolean; prerelease?: boolean; full?: boolean; + withMetadata?: boolean; }, // Additional options for the useQuery hook queryOptions: { diff --git a/x-pack/plugins/fleet/public/types/index.ts b/x-pack/plugins/fleet/public/types/index.ts index 20d94e6d44fa0..a340b7311fdbe 100644 --- a/x-pack/plugins/fleet/public/types/index.ts +++ b/x-pack/plugins/fleet/public/types/index.ts @@ -101,6 +101,7 @@ export type { CategorySummaryItem, CategorySummaryList, PackageInfo, + PackageMetadata, RegistryVarsEntry, RegistryInput, RegistryStream, diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index 9ce8c9afa4e6a..560af83619a48 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -11,6 +11,8 @@ import type { HttpResponseOptions } from '@kbn/core/server'; import { pick } from 'lodash'; +import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../common'; + import { HTTPAuthorizationHeader } from '../../../common/http_authorization_header'; import { generateTransformSecondaryAuthHeaders } from '../../services/api_keys/transform_api_keys'; import { handleTransformReauthorizeAndStart } from '../../services/epm/elasticsearch/transform/reauthorize'; @@ -71,7 +73,7 @@ import { FleetError, FleetTooManyRequestsError, } from '../../errors'; -import { appContextService, checkAllowedPackages } from '../../services'; +import { appContextService, checkAllowedPackages, packagePolicyService } from '../../services'; import { getPackageUsageStats } from '../../services/epm/packages/get'; import { updatePackage } from '../../services/epm/packages/update'; import { getGpgKeyIdOrUndefined } from '../../services/epm/packages/package_verification'; @@ -229,8 +231,23 @@ export const getInfoHandler: FleetRequestHandler< }); const flattenedRes = soToInstallationInfo(res) as PackageInfo; + let metadata: any; + if (request.query.withMetadata) { + const allSpaceSoClient = appContextService.getInternalUserSOClientWithoutSpaceExtension(); + const { total } = await packagePolicyService.list(allSpaceSoClient, { + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${pkgName}`, + page: 1, + perPage: 0, + spaceId: '*', + }); + metadata = { + has_policies: total > 0, + }; + } + const body: GetInfoResponse = { item: flattenedRes, + metadata, }; return response.ok({ body }); } catch (error) { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/remove.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/remove.test.ts index badb1bc7c1f40..2886774a32d12 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/remove.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/remove.test.ts @@ -20,6 +20,7 @@ jest.mock('../..', () => { error: jest.fn(), warn: jest.fn(), }), + getInternalUserSOClientWithoutSpaceExtension: jest.fn(), }, packagePolicyService: { list: jest.fn().mockImplementation((soClient, params) => { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts index e66c25e407ff5..08f83aa828632 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts @@ -65,11 +65,15 @@ export async function removeInstallation(options: { const installation = await getInstallation({ savedObjectsClient, pkgName }); if (!installation) throw new PackageRemovalError(`${pkgName} is not installed`); - const { total, items } = await packagePolicyService.list(savedObjectsClient, { - kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${pkgName}`, - page: 1, - perPage: SO_SEARCH_LIMIT, - }); + const { total, items } = await packagePolicyService.list( + appContextService.getInternalUserSOClientWithoutSpaceExtension(), + { + kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${pkgName}`, + page: 1, + perPage: SO_SEARCH_LIMIT, + spaceId: '*', + } + ); if (!options.force) { await populatePackagePolicyAssignedAgentsCount(esClient, items); diff --git a/x-pack/plugins/fleet/server/types/rest_spec/epm.ts b/x-pack/plugins/fleet/server/types/rest_spec/epm.ts index 4b83c7f7c7c58..358d2c8d6345d 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/epm.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/epm.ts @@ -86,6 +86,7 @@ export const GetInfoRequestSchema = { ignoreUnverified: schema.maybe(schema.boolean()), prerelease: schema.maybe(schema.boolean()), full: schema.maybe(schema.boolean()), + withMetadata: schema.boolean({ defaultValue: false }), }), }; @@ -103,6 +104,7 @@ export const GetInfoRequestSchemaDeprecated = { ignoreUnverified: schema.maybe(schema.boolean()), prerelease: schema.maybe(schema.boolean()), full: schema.maybe(schema.boolean()), + withMetadata: schema.boolean({ defaultValue: false }), }), }; 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 633d34cfa2d15..b9e01ba70b11e 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 @@ -10,6 +10,7 @@ import type { Agent } from 'supertest'; import { CreateAgentPolicyRequest, CreateAgentPolicyResponse, + CreatePackagePolicyResponse, GetAgentPoliciesResponse, GetAgentsResponse, GetOneAgentPolicyResponse, @@ -29,6 +30,7 @@ import { GetUninstallTokenResponse, GetUninstallTokensMetadataResponse, } from '@kbn/fleet-plugin/common/types/rest_spec/uninstall_token'; +import { SimplifiedPackagePolicy } from '@kbn/fleet-plugin/common/services/simplified_package_policy_helper'; export class SpaceTestApiClient { constructor(private readonly supertest: Agent) {} @@ -63,6 +65,18 @@ export class SpaceTestApiClient { return res; } + async createPackagePolicy( + spaceId?: string, + data: Partial = {} + ): Promise { + const { body: res } = await this.supertest + .post(`${this.getBaseUrl(spaceId)}/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send(data) + .expect(200); + + return res; + } async createFleetServerPolicy(spaceId?: string): Promise { const { body: res } = await this.supertest .post(`${this.getBaseUrl(spaceId)}/api/fleet/agent_policies`) @@ -224,6 +238,18 @@ export class SpaceTestApiClient { return res; } + async uninstallPackage( + { pkgName, pkgVersion, force }: { pkgName: string; pkgVersion: string; force?: boolean }, + spaceId?: string + ) { + const { body: res } = await this.supertest + .delete(`${this.getBaseUrl(spaceId)}/api/fleet/epm/packages/${pkgName}/${pkgVersion}`) + .set('kbn-xsrf', 'xxxx') + .send({ force }) + .expect(200); + + return res; + } async deletePackageKibanaAssets( { pkgName, pkgVersion }: { pkgName: string; pkgVersion: string }, spaceId?: string diff --git a/x-pack/test/fleet_api_integration/apis/space_awareness/package_install.ts b/x-pack/test/fleet_api_integration/apis/space_awareness/package_install.ts index ed464bd8d9f31..4052e9a8de488 100644 --- a/x-pack/test/fleet_api_integration/apis/space_awareness/package_install.ts +++ b/x-pack/test/fleet_api_integration/apis/space_awareness/package_install.ts @@ -21,6 +21,31 @@ export default function (providerContext: FtrProviderContext) { describe('package install', async function () { skipIfNoDockerRegistry(providerContext); const apiClient = new SpaceTestApiClient(supertest); + const createFleetAgent = async (agentPolicyId: string, spaceId?: string) => { + const agentResponse = await esClient.index({ + index: '.fleet-agents', + refresh: true, + body: { + access_api_key_id: 'api-key-3', + active: true, + policy_id: agentPolicyId, + policy_revision_idx: 1, + last_checkin_status: 'online', + type: 'PERMANENT', + local_metadata: { + host: { hostname: 'host123' }, + elastic: { agent: { version: '8.15.0' } }, + }, + user_provided_metadata: {}, + enrolled_at: new Date().toISOString(), + last_checkin: new Date().toISOString(), + tags: ['tag1'], + namespaces: spaceId ? [spaceId] : undefined, + }, + }); + + return agentResponse._id; + }; before(async () => { await kibanaServer.savedObjects.cleanStandardList(); @@ -229,5 +254,61 @@ export default function (providerContext: FtrProviderContext) { }); }); }); + + describe('uninstall', () => { + beforeEach(async () => { + await apiClient.installPackage({ + pkgName: 'nginx', + pkgVersion: '1.20.0', + force: true, // To avoid package verification + }); + const agentPolicyRes = await apiClient.createAgentPolicy(); + + await apiClient.createPackagePolicy(undefined, { + policy_ids: [agentPolicyRes.item.id], + name: `test-nginx-${Date.now()}`, + description: 'test', + package: { + name: 'nginx', + version: '1.20.0', + }, + inputs: {}, + }); + + await createFleetAgent(agentPolicyRes.item.id); + }); + + it('should not allow to delete a package with active agents in the same space', async () => { + let err: Error | undefined; + try { + await apiClient.uninstallPackage({ + pkgName: 'nginx', + pkgVersion: '1.20.0', + force: true, // To avoid package verification + }); + } catch (_err) { + err = _err; + } + expect(err).to.be.an(Error); + expect(err?.message).to.match(/400 "Bad Request"/); + }); + it('should not allow to delete a package with active agents in a different space', async () => { + let err: Error | undefined; + try { + await apiClient.uninstallPackage( + { + pkgName: 'nginx', + pkgVersion: '1.20.0', + force: true, // To avoid package verification + }, + TEST_SPACE_1 + ); + } catch (_err) { + err = _err; + } + expect(err).to.be.an(Error); + expect(err?.message).to.match(/400 "Bad Request"/); + }); + }); }); }