From 42d066be171fdf16f743bfb3acf00635f66339ec Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Mon, 29 Mar 2021 14:33:10 +0200 Subject: [PATCH 01/11] Intercept installation errors and add meta info. --- .../fleet/common/types/rest_spec/epm.ts | 7 ++++-- .../fleet/server/routes/epm/handlers.ts | 25 ++++++++++--------- .../server/services/epm/packages/install.ts | 11 +++++--- 3 files changed, 26 insertions(+), 17 deletions(-) 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 3c7a32265d20a..e5c7ace420c73 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/epm.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/epm.ts @@ -12,6 +12,7 @@ import type { RegistrySearchResult, PackageInfo, PackageUsageStats, + InstallType, } from '../models/epm'; export interface GetCategoriesRequest { @@ -83,8 +84,10 @@ export interface IBulkInstallPackageHTTPError { } export interface InstallResult { - assets: AssetReference[]; - status: 'installed' | 'already_installed'; + assets?: AssetReference[]; + status?: 'installed' | 'already_installed'; + error?: Error; + installType: InstallType; } export interface BulkInstallPackageInfo { diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index f0d6e68427361..8518edfc91d48 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -226,20 +226,21 @@ export const installPackageFromRegistryHandler: RequestHandler< const savedObjectsClient = context.core.savedObjects.client; const esClient = context.core.elasticsearch.client.asCurrentUser; const { pkgkey } = request.params; - try { - const res = await installPackage({ - installSource: 'registry', - savedObjectsClient, - pkgkey, - esClient, - force: request.body?.force, - }); + + const res = await installPackage({ + installSource: 'registry', + savedObjectsClient, + pkgkey, + esClient, + force: request.body?.force, + }); + if (!res.error) { const body: InstallPackageResponse = { - response: res.assets, + response: res.assets || [], }; return response.ok({ body }); - } catch (e) { - return await defaultIngestErrorHandler({ error: e, response }); + } else { + return await defaultIngestErrorHandler({ error: res.error, response }); } }; @@ -301,7 +302,7 @@ export const installPackageByUploadHandler: RequestHandler< contentType, }); const body: InstallPackageResponse = { - response: res.assets, + response: res.assets || [], }; return response.ok({ body }); } catch (error) { diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index 4373251a969bc..4ea223318d80f 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -227,6 +227,7 @@ async function installPackageFromRegistry({ ...installedPkg.attributes.installed_kibana, ], status: 'already_installed', + installType, }; } } @@ -258,7 +259,7 @@ async function installPackageFromRegistry({ installType, installSource: 'registry', }).then((assets) => { - return { assets, status: 'installed' }; + return { assets, status: 'installed', installType }; }); } catch (e) { await handleInstallPackageFailure({ @@ -269,7 +270,11 @@ async function installPackageFromRegistry({ installedPkg, esClient, }); - throw e; + // throw e; + return { + error: e, + installType, + }; } } @@ -324,7 +329,7 @@ async function installPackageByUpload({ installType, installSource, }).then((assets) => { - return { assets, status: 'installed' }; + return { assets, status: 'installed', installType }; }); } From 6a815f4579a7231525136e12be1ef8aea2562353 Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Mon, 29 Mar 2021 15:41:59 +0200 Subject: [PATCH 02/11] Adjust mock. --- .../packages/ensure_installed_default_packages.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/epm/packages/ensure_installed_default_packages.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/ensure_installed_default_packages.test.ts index fa2ea9e2209ed..f8c91e55fbbb6 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/ensure_installed_default_packages.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/ensure_installed_default_packages.test.ts @@ -77,7 +77,7 @@ describe('ensureInstalledDefaultPackages', () => { return [ { name: mockInstallation.attributes.name, - result: { assets: [], status: 'installed' }, + result: { assets: [], status: 'installed', installType: 'install' }, version: '', statusCode: 200, }, @@ -95,13 +95,13 @@ describe('ensureInstalledDefaultPackages', () => { return [ { name: 'success one', - result: { assets: [], status: 'installed' }, + result: { assets: [], status: 'installed', installType: 'install' }, version: '', statusCode: 200, }, { name: 'success two', - result: { assets: [], status: 'installed' }, + result: { assets: [], status: 'installed', installType: 'install' }, version: '', statusCode: 200, }, @@ -111,7 +111,7 @@ describe('ensureInstalledDefaultPackages', () => { }, { name: 'success three', - result: { assets: [], status: 'installed' }, + result: { assets: [], status: 'installed', installType: 'install' }, version: '', statusCode: 200, }, @@ -134,7 +134,7 @@ describe('ensureInstalledDefaultPackages', () => { return [ { name: 'undefined package', - result: { assets: [], status: 'installed' }, + result: { assets: [], status: 'installed', installType: 'install' }, version: '', statusCode: 200, }, From fd6418403a5ca534cc6277c185f5de61ee6221d3 Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Wed, 31 Mar 2021 16:34:07 +0200 Subject: [PATCH 03/11] Catch errors in all steps of install/upgrade. --- .../plugins/fleet/common/types/models/epm.ts | 2 +- .../server/services/epm/packages/install.ts | 136 +++++++++--------- 2 files changed, 73 insertions(+), 65 deletions(-) diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 3bc0d97d64646..1a594e77f4857 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -30,7 +30,7 @@ export enum InstallStatus { uninstalling = 'uninstalling', } -export type InstallType = 'reinstall' | 'reupdate' | 'rollback' | 'update' | 'install'; +export type InstallType = 'reinstall' | 'reupdate' | 'rollback' | 'update' | 'install' | 'unknown'; export type InstallSource = 'registry' | 'upload'; export type EpmPackageInstallStatus = 'installed' | 'installing'; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index 4ea223318d80f..1d643c07d55c9 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -201,76 +201,84 @@ async function installPackageFromRegistry({ // TODO: change epm API to /packageName/version so we don't need to do this const { pkgName, pkgVersion } = Registry.splitPkgKey(pkgkey); - // get the currently installed package - const installedPkg = await getInstallationObject({ savedObjectsClient, pkgName }); - const installType = getInstallType({ pkgVersion, installedPkg }); - - // get latest package version - const latestPackage = await Registry.fetchFindLatestPackage(pkgName); - - // let the user install if using the force flag or needing to reinstall or install a previous version due to failed update - const installOutOfDateVersionOk = - force || ['reinstall', 'reupdate', 'rollback'].includes(installType); + // if an error happens during getInstallType, report that we don't know + let installType: InstallType = 'unknown'; - // if the requested version is the same as installed version, check if we allow it based on - // current installed package status and force flag, if we don't allow it, - // just return the asset references from the existing installation - if ( - installedPkg?.attributes.version === pkgVersion && - installedPkg?.attributes.install_status === 'installed' - ) { - if (!force) { - logger.debug(`${pkgkey} is already installed, skipping installation`); - return { - assets: [ - ...installedPkg.attributes.installed_es, - ...installedPkg.attributes.installed_kibana, - ], - status: 'already_installed', - installType, - }; + try { + // get the currently installed package + const installedPkg = await getInstallationObject({ savedObjectsClient, pkgName }); + installType = getInstallType({ pkgVersion, installedPkg }); + + // get latest package version + const latestPackage = await Registry.fetchFindLatestPackage(pkgName); + + // let the user install if using the force flag or needing to reinstall or install a previous version due to failed update + const installOutOfDateVersionOk = + force || ['reinstall', 'reupdate', 'rollback'].includes(installType); + + // if the requested version is the same as installed version, check if we allow it based on + // current installed package status and force flag, if we don't allow it, + // just return the asset references from the existing installation + if ( + installedPkg?.attributes.version === pkgVersion && + installedPkg?.attributes.install_status === 'installed' + ) { + if (!force) { + logger.debug(`${pkgkey} is already installed, skipping installation`); + return { + assets: [ + ...installedPkg.attributes.installed_es, + ...installedPkg.attributes.installed_kibana, + ], + status: 'already_installed', + installType, + }; + } } - } - // if the requested version is out-of-date of the latest package version, check if we allow it - // if we don't allow it, return an error - if (semverLt(pkgVersion, latestPackage.version)) { - if (!installOutOfDateVersionOk) { - throw new PackageOutdatedError(`${pkgkey} is out-of-date and cannot be installed or updated`); + // if the requested version is out-of-date of the latest package version, check if we allow it + // if we don't allow it, return an error + if (semverLt(pkgVersion, latestPackage.version)) { + if (!installOutOfDateVersionOk) { + throw new PackageOutdatedError( + `${pkgkey} is out-of-date and cannot be installed or updated` + ); + } + logger.debug( + `${pkgkey} is out-of-date, installing anyway due to ${ + force ? 'force flag' : `install type ${installType}` + }` + ); } - logger.debug( - `${pkgkey} is out-of-date, installing anyway due to ${ - force ? 'force flag' : `install type ${installType}` - }` - ); - } - // get package info - const { paths, packageInfo } = await Registry.getRegistryPackage(pkgName, pkgVersion); + // get package info + const { paths, packageInfo } = await Registry.getRegistryPackage(pkgName, pkgVersion); - // try installing the package, if there was an error, call error handler and rethrow - try { - return _installPackage({ - savedObjectsClient, - esClient, - installedPkg, - paths, - packageInfo, - installType, - installSource: 'registry', - }).then((assets) => { - return { assets, status: 'installed', installType }; - }); + // try installing the package, if there was an error, call error handler and rethrow + try { + return _installPackage({ + savedObjectsClient, + esClient, + installedPkg, + paths, + packageInfo, + installType, + installSource: 'registry', + }).then((assets) => { + return { assets, status: 'installed', installType }; + }); + } catch (e) { + await handleInstallPackageFailure({ + savedObjectsClient, + error: e, + pkgName, + pkgVersion, + installedPkg, + esClient, + }); + throw e; + } } catch (e) { - await handleInstallPackageFailure({ - savedObjectsClient, - error: e, - pkgName, - pkgVersion, - installedPkg, - esClient, - }); - // throw e; return { error: e, installType, @@ -357,7 +365,7 @@ export async function installPackage(args: InstallPackageParams) { esClient, force, }).then(async (installResult) => { - if (skipPostInstall) { + if (skipPostInstall || installResult.error) { return installResult; } logger.debug(`install of ${pkgkey} finished, running post-install`); From 18c316a8ab91219a922c129cb6f2c211b2271778 Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Wed, 31 Mar 2021 17:51:04 +0200 Subject: [PATCH 04/11] Adjust handler for direct package upload. --- .../fleet/server/routes/epm/handlers.ts | 21 ++--- .../server/services/epm/packages/install.ts | 78 ++++++++++--------- 2 files changed, 53 insertions(+), 46 deletions(-) diff --git a/x-pack/plugins/fleet/server/routes/epm/handlers.ts b/x-pack/plugins/fleet/server/routes/epm/handlers.ts index 8518edfc91d48..16d583f8a8d1f 100644 --- a/x-pack/plugins/fleet/server/routes/epm/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/epm/handlers.ts @@ -293,20 +293,21 @@ export const installPackageByUploadHandler: RequestHandler< const esClient = context.core.elasticsearch.client.asCurrentUser; const contentType = request.headers['content-type'] as string; // from types it could also be string[] or undefined but this is checked later const archiveBuffer = Buffer.from(request.body); - try { - const res = await installPackage({ - installSource: 'upload', - savedObjectsClient, - esClient, - archiveBuffer, - contentType, - }); + + const res = await installPackage({ + installSource: 'upload', + savedObjectsClient, + esClient, + archiveBuffer, + contentType, + }); + if (!res.error) { const body: InstallPackageResponse = { response: res.assets || [], }; return response.ok({ body }); - } catch (error) { - return defaultIngestErrorHandler({ error, response }); + } else { + return defaultIngestErrorHandler({ error: res.error, response }); } }; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index 1d643c07d55c9..89231765aa07e 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -299,46 +299,52 @@ async function installPackageByUpload({ archiveBuffer, contentType, }: InstallUploadedArchiveParams): Promise { - const { packageInfo } = await parseAndVerifyArchiveEntries(archiveBuffer, contentType); + // if an error happens during getInstallType, report that we don't know + let installType: InstallType = 'unknown'; + try { + const { packageInfo } = await parseAndVerifyArchiveEntries(archiveBuffer, contentType); - const installedPkg = await getInstallationObject({ - savedObjectsClient, - pkgName: packageInfo.name, - }); + const installedPkg = await getInstallationObject({ + savedObjectsClient, + pkgName: packageInfo.name, + }); - const installType = getInstallType({ pkgVersion: packageInfo.version, installedPkg }); - if (installType !== 'install') { - throw new PackageOperationNotSupportedError( - `Package upload only supports fresh installations. Package ${packageInfo.name} is already installed, please uninstall first.` - ); - } + installType = getInstallType({ pkgVersion: packageInfo.version, installedPkg }); + if (installType !== 'install') { + throw new PackageOperationNotSupportedError( + `Package upload only supports fresh installations. Package ${packageInfo.name} is already installed, please uninstall first.` + ); + } - const installSource = 'upload'; - const paths = await unpackBufferToCache({ - name: packageInfo.name, - version: packageInfo.version, - installSource, - archiveBuffer, - contentType, - }); + const installSource = 'upload'; + const paths = await unpackBufferToCache({ + name: packageInfo.name, + version: packageInfo.version, + installSource, + archiveBuffer, + contentType, + }); - setPackageInfo({ - name: packageInfo.name, - version: packageInfo.version, - packageInfo, - }); + setPackageInfo({ + name: packageInfo.name, + version: packageInfo.version, + packageInfo, + }); - return _installPackage({ - savedObjectsClient, - esClient, - installedPkg, - paths, - packageInfo, - installType, - installSource, - }).then((assets) => { - return { assets, status: 'installed', installType }; - }); + return _installPackage({ + savedObjectsClient, + esClient, + installedPkg, + paths, + packageInfo, + installType, + installSource, + }).then((assets) => { + return { assets, status: 'installed', installType }; + }); + } catch (e) { + return { error: e, installType }; + } } export type InstallPackageParams = { @@ -387,7 +393,7 @@ export async function installPackage(args: InstallPackageParams) { archiveBuffer, contentType, }).then(async (installResult) => { - if (skipPostInstall) { + if (skipPostInstall || installResult.error) { return installResult; } logger.debug(`install of uploaded package finished, running post-install`); From 5f4ed7bc537c86ac4a481a9be9d9f6271e2cc51f Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Thu, 1 Apr 2021 17:04:01 +0200 Subject: [PATCH 05/11] Don't throw not-found errors on assets during rollback. --- .../plugins/fleet/server/services/epm/packages/remove.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 de798e822b029..706f1bbbaaf35 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/remove.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/remove.ts @@ -79,6 +79,7 @@ export async function removeInstallation(options: { return installedAssets; } +// TODO: this is very much like deleteKibanaSavedObjectsAssets below function deleteKibanaAssets( installedObjects: KibanaAssetReference[], savedObjectsClient: SavedObjectsClientContract @@ -136,6 +137,7 @@ async function deleteTemplate(esClient: ElasticsearchClient, name: string): Prom } } +// TODO: this is very much like deleteKibanaAssets above export async function deleteKibanaSavedObjectsAssets( savedObjectsClient: SavedObjectsClientContract, installedRefs: AssetReference[] @@ -153,6 +155,9 @@ export async function deleteKibanaSavedObjectsAssets( try { await Promise.all(deletePromises); } catch (err) { - logger.warn(err); + // in the rollback case, partial installs are likely, so missing assets are not an error + if (!savedObjectsClient.errors.isNotFoundError(err)) { + logger.error(err); + } } } From 8a4236d7144043ca800f21dbcc86a41146c1a40e Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Thu, 1 Apr 2021 18:19:36 +0200 Subject: [PATCH 06/11] Correctly catch errors from _installPackage() --- .../server/services/epm/packages/install.ts | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index 89231765aa07e..31d0732096790 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -255,29 +255,31 @@ async function installPackageFromRegistry({ const { paths, packageInfo } = await Registry.getRegistryPackage(pkgName, pkgVersion); // try installing the package, if there was an error, call error handler and rethrow - try { - return _installPackage({ - savedObjectsClient, - esClient, - installedPkg, - paths, - packageInfo, - installType, - installSource: 'registry', - }).then((assets) => { + // TODO: without the ts-ignore, TS complains about the type of the value of the returned InstallResult.status + // @ts-ignore + return _installPackage({ + savedObjectsClient, + esClient, + installedPkg, + paths, + packageInfo, + installType, + installSource: 'registry', + }) + .then((assets) => { return { assets, status: 'installed', installType }; + }) + .catch(async (err: Error) => { + await handleInstallPackageFailure({ + savedObjectsClient, + error: err, + pkgName, + pkgVersion, + installedPkg, + esClient, + }); + return { error: err, installType }; }); - } catch (e) { - await handleInstallPackageFailure({ - savedObjectsClient, - error: e, - pkgName, - pkgVersion, - installedPkg, - esClient, - }); - throw e; - } } catch (e) { return { error: e, @@ -330,7 +332,8 @@ async function installPackageByUpload({ version: packageInfo.version, packageInfo, }); - + // TODO: without the ts-ignore, TS complains about the type of the value of the returned InstallResult.status + // @ts-ignore return _installPackage({ savedObjectsClient, esClient, @@ -339,9 +342,13 @@ async function installPackageByUpload({ packageInfo, installType, installSource, - }).then((assets) => { - return { assets, status: 'installed', installType }; - }); + }) + .then((assets) => { + return { assets, status: 'installed', installType }; + }) + .catch(async (err: Error) => { + return { error: err, installType }; + }); } catch (e) { return { error: e, installType }; } From 5a52e2995aff0e47d65aeedcd09a82b32f8b7d55 Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Thu, 8 Apr 2021 11:27:34 +0200 Subject: [PATCH 07/11] Propagate error from installResult in bulk install case. --- .../epm/packages/bulk_install_packages.ts | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts index 7323263d4a70f..baaaaf6c6b0cf 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts @@ -32,22 +32,27 @@ export async function bulkInstallPackages({ ); logger.debug(`kicking off bulk install of ${packagesToInstall.join(', ')} from registry`); - const installResults = await Promise.allSettled( + const bulkInstallResults = await Promise.allSettled( latestPackagesResults.map(async (result, index) => { const packageName = packagesToInstall[index]; if (result.status === 'fulfilled') { const latestPackage = result.value; - return { - name: packageName, - version: latestPackage.version, - result: await installPackage({ - savedObjectsClient, - esClient, - pkgkey: Registry.pkgToPkgKey(latestPackage), - installSource, - skipPostInstall: true, - }), - }; + const installResult = await installPackage({ + savedObjectsClient, + esClient, + pkgkey: Registry.pkgToPkgKey(latestPackage), + installSource, + skipPostInstall: true, + }); + if (installResult.error) { + return { name: packageName, error: installResult.error }; + } else { + return { + name: packageName, + version: latestPackage.version, + result: installResult, + }; + } } return { name: packageName, error: result.reason }; }) @@ -56,18 +61,27 @@ export async function bulkInstallPackages({ // only install index patterns if we completed install for any package-version for the // first time, aka fresh installs or upgrades if ( - installResults.find( - (result) => result.status === 'fulfilled' && result.value.result?.status === 'installed' + bulkInstallResults.find( + (result) => + result.status === 'fulfilled' && + !result.value.result?.error && + result.value.result?.status === 'installed' ) ) { await installIndexPatterns({ savedObjectsClient, esClient, installSource }); } - return installResults.map((result, index) => { + return bulkInstallResults.map((result, index) => { const packageName = packagesToInstall[index]; - return result.status === 'fulfilled' - ? result.value - : { name: packageName, error: result.reason }; + if (result.status === 'fulfilled') { + if (result.value && result.value.error) { + return { name: packageName, error: result.value.error }; + } else { + return result.value; + } + } else { + return { name: packageName, error: result.reason }; + } }); } From 941cf796c0978c7f8f1b4c6631559dc5217dc095 Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Thu, 8 Apr 2021 17:17:02 +0200 Subject: [PATCH 08/11] Add tests for rollback. --- .../fleet_api_integration/apis/epm/index.js | 1 + .../apis/epm/install_error_rollback.ts | 74 +++++++++++++++++++ .../error_handling/0.1.0/docs/README.md | 3 + .../visualization/sample_visualization.json | 14 ++++ .../error_handling/0.1.0/manifest.yml | 20 +++++ .../error_handling/0.2.0/docs/README.md | 5 ++ .../visualization/sample_visualization.json | 14 ++++ .../error_handling/0.2.0/manifest.yml | 19 +++++ 8 files changed, 150 insertions(+) create mode 100644 x-pack/test/fleet_api_integration/apis/epm/install_error_rollback.ts create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/docs/README.md create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/kibana/visualization/sample_visualization.json create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/manifest.yml create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/docs/README.md create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/kibana/visualization/sample_visualization.json create mode 100644 x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/manifest.yml diff --git a/x-pack/test/fleet_api_integration/apis/epm/index.js b/x-pack/test/fleet_api_integration/apis/epm/index.js index 009e1a2dad5f1..445d9706bb9a9 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/index.js +++ b/x-pack/test/fleet_api_integration/apis/epm/index.js @@ -24,5 +24,6 @@ export default function loadTests({ loadTestFile }) { loadTestFile(require.resolve('./update_assets')); loadTestFile(require.resolve('./data_stream')); loadTestFile(require.resolve('./package_install_complete')); + loadTestFile(require.resolve('./install_error_rollback')); }); } diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_error_rollback.ts b/x-pack/test/fleet_api_integration/apis/epm/install_error_rollback.ts new file mode 100644 index 0000000000000..de93ab6746a63 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/epm/install_error_rollback.ts @@ -0,0 +1,74 @@ +/* + * 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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; +import { skipIfNoDockerRegistry } from '../../helpers'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const goodPackage = 'error_handling-0.1.0'; + const badPackage = 'error_handling-0.2.0'; + + const installPackage = async (pkgkey: string) => { + await supertest + .post(`/api/fleet/epm/packages/${pkgkey}`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }); + }; + + const deletePackage = async (pkgkey: string) => { + await supertest + .delete(`/api/fleet/epm/packages/${pkgkey}`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }); + }; + + const getPackageInfo = async (pkgkey: string) => { + return await supertest.get(`/api/fleet/epm/packages/${pkgkey}`).set('kbn-xsrf', 'xxxx'); + }; + + describe('package installation error handling and rollback', async () => { + skipIfNoDockerRegistry(providerContext); + before(async () => { + await esArchiver.load('empty_kibana'); + await esArchiver.load('fleet/empty_fleet_server'); + }); + after(async () => { + await esArchiver.unload('empty_kibana'); + await esArchiver.unload('fleet/empty_fleet_server'); + }); + + it('on a fresh install, it should uninstall a broken package during rollback', async function () { + await supertest + .post(`/api/fleet/epm/packages/${badPackage}`) + .set('kbn-xsrf', 'xxxx') + .expect(422); // the broken package contains a broken visualization triggering a 422 from Kibana + + const pkgInfoResponse = await getPackageInfo(badPackage); + expect(JSON.parse(pkgInfoResponse.text).response.status).to.be('not_installed'); + }); + + it('on an upgrade, it should fall back to the previous good version during rollback', async function () { + await installPackage(goodPackage); + await supertest + .post(`/api/fleet/epm/packages/${badPackage}`) + .set('kbn-xsrf', 'xxxx') + .expect(422); // the broken package contains a broken visualization triggering a 422 from Kibana + + const badPkgInfoResponse = await getPackageInfo(badPackage); + expect(JSON.parse(badPkgInfoResponse.text).response.status).to.be('not_installed'); + + const goodPkgInfoResponse = await getPackageInfo(goodPackage); + // TODO: status is not_installed here which looks like a bug + // expect(JSON.parse(goodPkgInfoResponse.text).response.status).to.be('installed'); + expect(JSON.parse(goodPkgInfoResponse.text).response.version).to.be('0.1.0'); + }); + }); +} diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/docs/README.md b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/docs/README.md new file mode 100644 index 0000000000000..260499f4b0078 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/docs/README.md @@ -0,0 +1,3 @@ +This package should install without errors. + +Version 0.2.0 of this package should fail during installation. We need this good version to test rollback. \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/kibana/visualization/sample_visualization.json b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/kibana/visualization/sample_visualization.json new file mode 100644 index 0000000000000..0683ad3682e8b --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/kibana/visualization/sample_visualization.json @@ -0,0 +1,14 @@ +{ + "attributes": { + "description": "sample visualization", + "title": "sample vis title", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"aggs\":[{\"enabled\":true,\"id\":\"1\",\"params\":{},\"schema\":\"metric\",\"type\":\"count\"},{\"enabled\":true,\"id\":\"2\",\"params\":{\"extended_bounds\":{},\"field\":\"@timestamp\",\"interval\":\"auto\",\"min_doc_count\":1},\"schema\":\"segment\",\"type\":\"date_histogram\"},{\"enabled\":true,\"id\":\"3\",\"params\":{\"customLabel\":\"Log Level\",\"field\":\"log.level\",\"order\":\"desc\",\"orderBy\":\"1\",\"size\":5},\"schema\":\"group\",\"type\":\"terms\"}],\"params\":{\"addLegend\":true,\"addTimeMarker\":false,\"addTooltip\":true,\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"labels\":{\"show\":true,\"truncate\":100},\"position\":\"bottom\",\"scale\":{\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{\"text\":\"@timestamp per day\"},\"type\":\"category\"}],\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"legendPosition\":\"right\",\"seriesParams\":[{\"data\":{\"id\":\"1\",\"label\":\"Count\"},\"drawLinesBetweenPoints\":true,\"mode\":\"stacked\",\"show\":\"true\",\"showCircles\":true,\"type\":\"histogram\",\"valueAxis\":\"ValueAxis-1\"}],\"times\":[],\"type\":\"histogram\",\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"labels\":{\"filter\":false,\"rotate\":0,\"show\":true,\"truncate\":100},\"name\":\"LeftAxis-1\",\"position\":\"left\",\"scale\":{\"mode\":\"normal\",\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{\"text\":\"Count\"},\"type\":\"value\"}]},\"title\":\"Log levels over time [Logs Kafka] ECS\",\"type\":\"histogram\"}" + }, + "id": "sample_visualization", + "type": "visualization", + "migrationVersion": { + "visualization": "12.7.0" + } +} diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/manifest.yml new file mode 100644 index 0000000000000..bba1a6a4c347d --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/manifest.yml @@ -0,0 +1,20 @@ +format_version: 1.0.0 +name: error_handling +title: Error handling +description: tests error handling and rollback +version: 0.1.0 +categories: [] +release: beta +type: integration +license: basic + +requirement: + elasticsearch: + versions: '>7.7.0' + kibana: + versions: '>7.7.0' + +icons: + - src: '/img/logo_overrides_64_color.svg' + size: '16x16' + type: 'image/svg+xml' \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/docs/README.md b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/docs/README.md new file mode 100644 index 0000000000000..c348f801b1780 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/docs/README.md @@ -0,0 +1,5 @@ +This package should fail during installation. + +Version 0.1.0 of this package should install without errors, and be rolled back to without errors. + +This package contains one Kibana visualization that requires a non-existent version of Kibana in order to trigger an error during installation. \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/kibana/visualization/sample_visualization.json b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/kibana/visualization/sample_visualization.json new file mode 100644 index 0000000000000..0a4867cfe1c11 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/kibana/visualization/sample_visualization.json @@ -0,0 +1,14 @@ +{ + "attributes": { + "description": "sample visualization", + "title": "sample vis title", + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"aggs\":[{\"enabled\":true,\"id\":\"1\",\"params\":{},\"schema\":\"metric\",\"type\":\"count\"},{\"enabled\":true,\"id\":\"2\",\"params\":{\"extended_bounds\":{},\"field\":\"@timestamp\",\"interval\":\"auto\",\"min_doc_count\":1},\"schema\":\"segment\",\"type\":\"date_histogram\"},{\"enabled\":true,\"id\":\"3\",\"params\":{\"customLabel\":\"Log Level\",\"field\":\"log.level\",\"order\":\"desc\",\"orderBy\":\"1\",\"size\":5},\"schema\":\"group\",\"type\":\"terms\"}],\"params\":{\"addLegend\":true,\"addTimeMarker\":false,\"addTooltip\":true,\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"labels\":{\"show\":true,\"truncate\":100},\"position\":\"bottom\",\"scale\":{\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{\"text\":\"@timestamp per day\"},\"type\":\"category\"}],\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"legendPosition\":\"right\",\"seriesParams\":[{\"data\":{\"id\":\"1\",\"label\":\"Count\"},\"drawLinesBetweenPoints\":true,\"mode\":\"stacked\",\"show\":\"true\",\"showCircles\":true,\"type\":\"histogram\",\"valueAxis\":\"ValueAxis-1\"}],\"times\":[],\"type\":\"histogram\",\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"labels\":{\"filter\":false,\"rotate\":0,\"show\":true,\"truncate\":100},\"name\":\"LeftAxis-1\",\"position\":\"left\",\"scale\":{\"mode\":\"normal\",\"type\":\"linear\"},\"show\":true,\"style\":{},\"title\":{\"text\":\"Count\"},\"type\":\"value\"}]},\"title\":\"Log levels over time [Logs Kafka] ECS\",\"type\":\"histogram\"}" + }, + "id": "sample_visualization", + "type": "visualization", + "migrationVersion": { + "visualization": "12.7.0" + } +} \ No newline at end of file diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/manifest.yml b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/manifest.yml new file mode 100644 index 0000000000000..2eb6a41a77ede --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.2.0/manifest.yml @@ -0,0 +1,19 @@ +format_version: 1.0.0 +name: error_handling +title: Error handling +description: tests error handling and rollback +version: 0.2.0 +categories: [] +release: beta +type: integration +license: basic + +requirement: + elasticsearch: + versions: '>7.7.0' + kibana: + versions: '>7.7.0' + +icons: + - src: '/img/logo_overrides_64_color.svg' + size: '16x16' \ No newline at end of file From 01180d4ced0030a4fbfe0a04ee1a40a791ae1f70 Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Thu, 8 Apr 2021 19:50:35 +0200 Subject: [PATCH 09/11] Remove unused code. --- .../apis/epm/install_error_rollback.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_error_rollback.ts b/x-pack/test/fleet_api_integration/apis/epm/install_error_rollback.ts index de93ab6746a63..3b0977e268ca9 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_error_rollback.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_error_rollback.ts @@ -23,13 +23,6 @@ export default function (providerContext: FtrProviderContext) { .send({ force: true }); }; - const deletePackage = async (pkgkey: string) => { - await supertest - .delete(`/api/fleet/epm/packages/${pkgkey}`) - .set('kbn-xsrf', 'xxxx') - .send({ force: true }); - }; - const getPackageInfo = async (pkgkey: string) => { return await supertest.get(`/api/fleet/epm/packages/${pkgkey}`).set('kbn-xsrf', 'xxxx'); }; From 0297a37e1f0cc06d3f014672474931b40a58b702 Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Thu, 8 Apr 2021 20:31:57 +0200 Subject: [PATCH 10/11] Skipping test that doesn't test what it says. --- .../fleet_api_integration/apis/epm/install_error_rollback.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_error_rollback.ts b/x-pack/test/fleet_api_integration/apis/epm/install_error_rollback.ts index 3b0977e268ca9..145a225b5e3c2 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_error_rollback.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_error_rollback.ts @@ -48,7 +48,7 @@ export default function (providerContext: FtrProviderContext) { expect(JSON.parse(pkgInfoResponse.text).response.status).to.be('not_installed'); }); - it('on an upgrade, it should fall back to the previous good version during rollback', async function () { + it.skip('on an upgrade, it should fall back to the previous good version during rollback', async function () { await installPackage(goodPackage); await supertest .post(`/api/fleet/epm/packages/${badPackage}`) From 9fa2e24cfa21d40da31654c929f1c669492259cc Mon Sep 17 00:00:00 2001 From: Sonja Krause-Harder Date: Fri, 9 Apr 2021 00:05:16 +0200 Subject: [PATCH 11/11] Fix and reenable test. --- .../apis/epm/install_error_rollback.ts | 14 ++++---------- .../kibana/visualization/sample_visualization.json | 2 +- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_error_rollback.ts b/x-pack/test/fleet_api_integration/apis/epm/install_error_rollback.ts index 145a225b5e3c2..6e2ea3b96aa58 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_error_rollback.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_error_rollback.ts @@ -29,13 +29,11 @@ export default function (providerContext: FtrProviderContext) { describe('package installation error handling and rollback', async () => { skipIfNoDockerRegistry(providerContext); - before(async () => { + beforeEach(async () => { await esArchiver.load('empty_kibana'); - await esArchiver.load('fleet/empty_fleet_server'); }); - after(async () => { + afterEach(async () => { await esArchiver.unload('empty_kibana'); - await esArchiver.unload('fleet/empty_fleet_server'); }); it('on a fresh install, it should uninstall a broken package during rollback', async function () { @@ -48,19 +46,15 @@ export default function (providerContext: FtrProviderContext) { expect(JSON.parse(pkgInfoResponse.text).response.status).to.be('not_installed'); }); - it.skip('on an upgrade, it should fall back to the previous good version during rollback', async function () { + it('on an upgrade, it should fall back to the previous good version during rollback', async function () { await installPackage(goodPackage); await supertest .post(`/api/fleet/epm/packages/${badPackage}`) .set('kbn-xsrf', 'xxxx') .expect(422); // the broken package contains a broken visualization triggering a 422 from Kibana - const badPkgInfoResponse = await getPackageInfo(badPackage); - expect(JSON.parse(badPkgInfoResponse.text).response.status).to.be('not_installed'); - const goodPkgInfoResponse = await getPackageInfo(goodPackage); - // TODO: status is not_installed here which looks like a bug - // expect(JSON.parse(goodPkgInfoResponse.text).response.status).to.be('installed'); + expect(JSON.parse(goodPkgInfoResponse.text).response.status).to.be('installed'); expect(JSON.parse(goodPkgInfoResponse.text).response.version).to.be('0.1.0'); }); }); diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/kibana/visualization/sample_visualization.json b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/kibana/visualization/sample_visualization.json index 0683ad3682e8b..01afe600853ef 100644 --- a/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/kibana/visualization/sample_visualization.json +++ b/x-pack/test/fleet_api_integration/apis/fixtures/test_packages/error_handling/0.1.0/kibana/visualization/sample_visualization.json @@ -9,6 +9,6 @@ "id": "sample_visualization", "type": "visualization", "migrationVersion": { - "visualization": "12.7.0" + "visualization": "7.7.0" } }