mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Fleet] Replace call to registry when deleting kibana assets for custom packages (#224886)
Fixes https://github.com/elastic/kibana/issues/224191 ## Summary Bugfix - Replace call to registry when deleting kibana assets for packages of type "custom" and "bundled". Also replaced the call to `fetchInfo.registry` on another code path to avoid errors in the same situation - - These calls are replaced with `getPackageInfo`, that has some internal functionalities to decide when the packageInfo should be fetched from the cache, ES or the registry. - Added additional logging to the delete assets functions ### Testing - Install a custom integration that has some assets (a dashboard for instance) - Uninstall it and check that the asset is correctly removed and there are no errors: <img width="1453" alt="Screenshot 2025-06-25 at 11 02 39" src="https://github.com/user-attachments/assets/32fb07f3-2628-4e30-be92-16610043b3ae" /> ### Checklist - [ ] [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: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
e31f1a584f
commit
550b9d58ea
4 changed files with 70 additions and 33 deletions
|
@ -279,7 +279,7 @@ export async function installKibanaAssetsAndReferences({
|
|||
const kibanaAssetsArchiveIterator = getKibanaAssetsArchiveIterator(packageInstallContext);
|
||||
|
||||
if (installedPkg) {
|
||||
await deleteKibanaSavedObjectsAssets({ installedPkg, spaceId });
|
||||
await deleteKibanaSavedObjectsAssets({ savedObjectsClient, installedPkg, spaceId });
|
||||
}
|
||||
let installedKibanaAssetsRefs: KibanaAssetReference[] = [];
|
||||
|
||||
|
@ -344,7 +344,7 @@ export async function deleteKibanaAssetsAndReferencesForSpace({
|
|||
'Impossible to delete kibana assets from the space where the package was installed, you must uninstall the package.'
|
||||
);
|
||||
}
|
||||
await deleteKibanaSavedObjectsAssets({ installedPkg, spaceId });
|
||||
await deleteKibanaSavedObjectsAssets({ savedObjectsClient, installedPkg, spaceId });
|
||||
await saveKibanaAssetsRefs(savedObjectsClient, pkgName, null, true);
|
||||
}
|
||||
|
||||
|
|
|
@ -269,7 +269,8 @@ describe('cleanUpKibanaAssetsStep', () => {
|
|||
expect(mockedDeleteKibanaAssets).toBeCalledWith({
|
||||
installedObjects: installedKibana,
|
||||
spaceId: 'default',
|
||||
packageInfo: packageInstallContext.packageInfo,
|
||||
packageSpecConditions: { kibana: { version: 'x.y.z' } },
|
||||
logger: expect.anything(),
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -449,7 +450,8 @@ describe('cleanUpUnusedKibanaAssetsStep', () => {
|
|||
expect(mockedDeleteKibanaAssets).toBeCalledWith({
|
||||
installedObjects: [installedAssets[1]],
|
||||
spaceId: 'default',
|
||||
packageInfo: packageInstallContext.packageInfo,
|
||||
packageSpecConditions: { kibana: { version: 'x.y.z' } },
|
||||
logger: expect.anything(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -82,7 +82,12 @@ export async function cleanUpKibanaAssetsStep(context: InstallContext) {
|
|||
logger.debug('Retry transition - clean up Kibana assets first');
|
||||
|
||||
await withPackageSpan('Retry transition - clean up Kibana assets first', async () => {
|
||||
await deleteKibanaAssets({ installedObjects, spaceId, packageInfo });
|
||||
await deleteKibanaAssets({
|
||||
installedObjects,
|
||||
spaceId,
|
||||
packageSpecConditions: packageInfo?.conditions,
|
||||
logger,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -124,6 +129,11 @@ export async function cleanUpUnusedKibanaAssetsStep(context: InstallContext) {
|
|||
}
|
||||
|
||||
await withPackageSpan('Clean up Kibana assets that are no longer in the package', async () => {
|
||||
await deleteKibanaAssets({ installedObjects: assetsToRemove, spaceId, packageInfo });
|
||||
await deleteKibanaAssets({
|
||||
installedObjects: assetsToRemove,
|
||||
spaceId,
|
||||
packageSpecConditions: packageInfo?.conditions,
|
||||
logger,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server';
|
||||
import { differenceBy } from 'lodash';
|
||||
import type { ElasticsearchClient, SavedObjectsClientContract, Logger } from '@kbn/core/server';
|
||||
import { differenceBy, chunk } from 'lodash';
|
||||
|
||||
import type { SavedObject } from '@kbn/core/server';
|
||||
|
||||
|
@ -17,7 +17,6 @@ import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants';
|
|||
import { SavedObjectsUtils, SavedObjectsErrorHelpers } from '@kbn/core/server';
|
||||
import minVersion from 'semver/ranges/min-version';
|
||||
|
||||
import { chunk } from 'lodash';
|
||||
import pMap from 'p-map';
|
||||
|
||||
import { updateIndexSettings } from '../elasticsearch/index/update_settings';
|
||||
|
@ -36,8 +35,7 @@ import type {
|
|||
EsAssetReference,
|
||||
KibanaAssetReference,
|
||||
Installation,
|
||||
ArchivePackage,
|
||||
RegistryPackage,
|
||||
InstallSource,
|
||||
} from '../../../types';
|
||||
import { deletePipeline } from '../elasticsearch/ingest_pipeline';
|
||||
import { removeUnusedIndexPatterns } from '../kibana/index_pattern/install';
|
||||
|
@ -52,9 +50,10 @@ import { auditLoggingService } from '../../audit_logging';
|
|||
import { FleetError, PackageRemovalError } from '../../../errors';
|
||||
|
||||
import { populatePackagePolicyAssignedAgentsCount } from '../../package_policies/populate_package_policy_assigned_agents_count';
|
||||
import * as Registry from '../registry';
|
||||
|
||||
import { getInstallation, kibanaSavedObjectTypes } from '.';
|
||||
import type { PackageSpecConditions } from '../../../../common';
|
||||
|
||||
import { getInstallation, getPackageInfo, kibanaSavedObjectTypes } from '.';
|
||||
import { updateUninstallFailedAttempts } from './uninstall_errors_helpers';
|
||||
|
||||
const MAX_ASSETS_TO_DELETE = 1000;
|
||||
|
@ -65,6 +64,7 @@ export async function removeInstallation(options: {
|
|||
pkgVersion?: string;
|
||||
esClient: ElasticsearchClient;
|
||||
force?: boolean;
|
||||
installSource?: InstallSource;
|
||||
}): Promise<AssetReference[]> {
|
||||
const { savedObjectsClient, pkgName, pkgVersion, esClient } = options;
|
||||
const installation = await getInstallation({ savedObjectsClient, pkgName });
|
||||
|
@ -103,7 +103,7 @@ export async function removeInstallation(options: {
|
|||
|
||||
// Delete the installed assets. Don't include installation.package_assets. Those are irrelevant to users
|
||||
const installedAssets = [...installation.installed_kibana, ...installation.installed_es];
|
||||
await deleteAssets(installation, esClient);
|
||||
await deleteAssets(savedObjectsClient, installation, esClient);
|
||||
|
||||
// Delete the manager saved object with references to the asset objects
|
||||
// could also update with [] or some other state
|
||||
|
@ -144,22 +144,26 @@ export async function removeInstallation(options: {
|
|||
*/
|
||||
export async function deleteKibanaAssets({
|
||||
installedObjects,
|
||||
packageInfo,
|
||||
packageSpecConditions,
|
||||
logger,
|
||||
spaceId = DEFAULT_SPACE_ID,
|
||||
}: {
|
||||
installedObjects: KibanaAssetReference[];
|
||||
logger: Logger;
|
||||
packageSpecConditions?: PackageSpecConditions;
|
||||
spaceId?: string;
|
||||
packageInfo: RegistryPackage | ArchivePackage;
|
||||
}) {
|
||||
const savedObjectsClient = new SavedObjectsClient(
|
||||
appContextService.getSavedObjects().createInternalRepository()
|
||||
);
|
||||
|
||||
const namespace = SavedObjectsUtils.namespaceStringToId(spaceId);
|
||||
if (namespace) {
|
||||
logger.debug(`Deleting Kibana assets in namespace: ${namespace}`);
|
||||
}
|
||||
|
||||
// TODO this should be the installed package info, not the package that is being installed
|
||||
const minKibana = packageInfo.conditions?.kibana?.version
|
||||
? minVersion(packageInfo.conditions.kibana.version)
|
||||
const minKibana = packageSpecConditions?.kibana?.version
|
||||
? minVersion(packageSpecConditions.kibana.version)
|
||||
: null;
|
||||
|
||||
// Compare Kibana versions to determine if the package could been installed
|
||||
|
@ -167,7 +171,7 @@ export async function deleteKibanaAssets({
|
|||
// and delete the assets directly. Otherwise, we need to resolve the assets
|
||||
// which might create high memory pressure if a package has a lot of assets.
|
||||
if (minKibana && minKibana.major >= 8) {
|
||||
await bulkDeleteSavedObjects(installedObjects, namespace, savedObjectsClient);
|
||||
await bulkDeleteSavedObjects(installedObjects, namespace, savedObjectsClient, logger);
|
||||
} else {
|
||||
const { resolved_objects: resolvedObjects } = await savedObjectsClient.bulkResolve(
|
||||
installedObjects,
|
||||
|
@ -190,23 +194,25 @@ export async function deleteKibanaAssets({
|
|||
// we filter these out before calling delete
|
||||
const assetsToDelete = foundObjects.map(({ saved_object: { id, type } }) => ({ id, type }));
|
||||
|
||||
await bulkDeleteSavedObjects(assetsToDelete, namespace, savedObjectsClient);
|
||||
await bulkDeleteSavedObjects(assetsToDelete, namespace, savedObjectsClient, logger);
|
||||
}
|
||||
}
|
||||
|
||||
async function bulkDeleteSavedObjects(
|
||||
assetsToDelete: Array<{ id: string; type: string }>,
|
||||
namespace: string | undefined,
|
||||
savedObjectsClient: SavedObjectsClientContract
|
||||
savedObjectsClient: SavedObjectsClientContract,
|
||||
logger: Logger
|
||||
) {
|
||||
logger.debug(`Starting bulk deletion of assets and saved objects`);
|
||||
for (const asset of assetsToDelete) {
|
||||
logger.debug(`Delete asset - id: ${asset?.id}, type: ${asset?.type},`);
|
||||
auditLoggingService.writeCustomSoAuditLog({
|
||||
action: 'delete',
|
||||
id: asset.id,
|
||||
savedObjectType: asset.type,
|
||||
});
|
||||
}
|
||||
|
||||
// Delete assets in chunks to avoid high memory pressure. This is mostly
|
||||
// relevant for packages containing many assets, as large payload and response
|
||||
// objects are created in memory during the delete operation. While chunking
|
||||
|
@ -333,6 +339,7 @@ export async function deletePrerequisiteAssets(
|
|||
}
|
||||
|
||||
async function deleteAssets(
|
||||
savedObjectsClient: SavedObjectsClientContract,
|
||||
{
|
||||
installed_es: installedEs,
|
||||
installed_kibana: installedKibana,
|
||||
|
@ -340,6 +347,7 @@ async function deleteAssets(
|
|||
additional_spaces_installed_kibana: installedInAdditionalSpacesKibana = {},
|
||||
name,
|
||||
version,
|
||||
install_source: installSource,
|
||||
}: Installation,
|
||||
esClient: ElasticsearchClient
|
||||
) {
|
||||
|
@ -357,17 +365,29 @@ async function deleteAssets(
|
|||
esClient
|
||||
);
|
||||
|
||||
// delete the other asset types
|
||||
try {
|
||||
const packageInfo = await Registry.fetchInfo(name, version);
|
||||
const packageInfo = await getPackageInfo({
|
||||
savedObjectsClient,
|
||||
pkgName: name,
|
||||
pkgVersion: version,
|
||||
skipArchive: installSource !== 'registry',
|
||||
});
|
||||
|
||||
// delete the other asset types
|
||||
await Promise.all([
|
||||
...deleteESAssets(otherAssets, esClient),
|
||||
deleteKibanaAssets({ installedObjects: installedKibana, spaceId, packageInfo }),
|
||||
deleteKibanaAssets({
|
||||
installedObjects: installedKibana,
|
||||
spaceId,
|
||||
packageSpecConditions: packageInfo?.conditions,
|
||||
logger,
|
||||
}),
|
||||
Object.entries(installedInAdditionalSpacesKibana).map(([additionalSpaceId, kibanaAssets]) =>
|
||||
deleteKibanaAssets({
|
||||
installedObjects: kibanaAssets,
|
||||
spaceId: additionalSpaceId,
|
||||
packageInfo,
|
||||
logger,
|
||||
packageSpecConditions: packageInfo?.conditions,
|
||||
})
|
||||
),
|
||||
]);
|
||||
|
@ -402,9 +422,11 @@ async function deleteComponentTemplate(esClient: ElasticsearchClient, name: stri
|
|||
}
|
||||
|
||||
export async function deleteKibanaSavedObjectsAssets({
|
||||
savedObjectsClient,
|
||||
installedPkg,
|
||||
spaceId,
|
||||
}: {
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
installedPkg: SavedObject<Installation>;
|
||||
spaceId?: string;
|
||||
}) {
|
||||
|
@ -427,15 +449,18 @@ export async function deleteKibanaSavedObjectsAssets({
|
|||
.map(({ id, type }) => ({ id, type } as KibanaAssetReference));
|
||||
|
||||
try {
|
||||
const packageInfo = await Registry.fetchInfo(
|
||||
installedPkg.attributes.name,
|
||||
installedPkg.attributes.version
|
||||
);
|
||||
const packageInfo = await getPackageInfo({
|
||||
savedObjectsClient,
|
||||
pkgName: installedPkg.attributes.name,
|
||||
pkgVersion: installedPkg.attributes.version,
|
||||
skipArchive: installedPkg.attributes.install_source !== 'registry',
|
||||
});
|
||||
|
||||
await deleteKibanaAssets({
|
||||
installedObjects: assetsToDelete,
|
||||
spaceId: spaceIdToDelete,
|
||||
packageInfo,
|
||||
packageSpecConditions: packageInfo?.conditions,
|
||||
logger,
|
||||
});
|
||||
} catch (err) {
|
||||
// in the rollback case, partial installs are likely, so missing assets are not an error
|
||||
|
@ -502,7 +527,7 @@ export async function cleanupAssets(
|
|||
esClient: ElasticsearchClient,
|
||||
soClient: SavedObjectsClientContract
|
||||
) {
|
||||
await deleteAssets(installationToDelete, esClient);
|
||||
await deleteAssets(soClient, installationToDelete, esClient);
|
||||
|
||||
const {
|
||||
installed_es: installedEs,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue