diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 5d79d41b7a631..7a6f6232b2d4f 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -252,12 +252,19 @@ export type PackageList = PackageListItem[]; export type PackageListItem = Installable; export type PackagesGroupedByStatus = Record, PackageList>; -export type PackageInfo = Installable< - // remove the properties we'll be altering/replacing from the base type - Omit & - // now add our replacement definitions - PackageAdditions ->; +export type PackageInfo = + | Installable< + // remove the properties we'll be altering/replacing from the base type + Omit & + // now add our replacement definitions + PackageAdditions + > + | Installable< + // remove the properties we'll be altering/replacing from the base type + Omit & + // now add our replacement definitions + PackageAdditions + >; export interface Installation extends SavedObjectAttributes { installed_kibana: KibanaAssetReference[]; diff --git a/x-pack/plugins/fleet/server/services/epm/archive/cache.ts b/x-pack/plugins/fleet/server/services/epm/archive/cache.ts index 280c34744289e..04aa1767b4f14 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/cache.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/cache.ts @@ -20,8 +20,7 @@ export interface SharedKey { } type SharedKeyString = string; -type ArchiveFilelist = string[]; -const archiveFilelistCache: Map = new Map(); +const archiveFilelistCache: Map = new Map(); export const getArchiveFilelist = (keyArgs: SharedKey) => archiveFilelistCache.get(sharedKey(keyArgs)); @@ -46,6 +45,15 @@ export const getPackageInfo = (args: SharedKey) => { } }; +export const getArchivePackage = (args: SharedKey) => { + const packageInfo = getPackageInfo(args); + const paths = getArchiveFilelist(args); + return { + paths, + packageInfo, + }; +}; + export const setPackageInfo = ({ name, version, diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index 2d4a94a2332d6..3df2d39419ab8 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -7,10 +7,11 @@ import { SavedObjectsClientContract, SavedObjectsFindOptions } from 'src/core/server'; import { isPackageLimited, installationStatuses } from '../../../../common'; import { PACKAGES_SAVED_OBJECT_TYPE } from '../../../constants'; -import { ValueOf } from '../../../../common/types'; +import { ArchivePackage, InstallSource, RegistryPackage, ValueOf } from '../../../../common/types'; import { Installation, InstallationStatus, PackageInfo, KibanaAssetType } from '../../../types'; import * as Registry from '../registry'; import { createInstallableFrom, isRequiredPackage } from './index'; +import { getArchivePackage } from '../archive'; export { fetchFile as getFile, SearchParams } from '../registry'; @@ -109,23 +110,53 @@ export async function getPackageInfo(options: { pkgVersion: string; }): Promise { const { savedObjectsClient, pkgName, pkgVersion } = options; - const [savedObject, latestPackage, { paths: assets, packageInfo: item }] = await Promise.all([ + const [savedObject, latestPackage] = await Promise.all([ getInstallationObject({ savedObjectsClient, pkgName }), Registry.fetchFindLatestPackage(pkgName), - Registry.getRegistryPackage(pkgName, pkgVersion), ]); - // add properties that aren't (or aren't yet) on Registry response + const getPackageRes = await getPackageFromSource({ + pkgName, + pkgVersion, + pkgInstallSource: savedObject?.attributes.install_source, + }); + const paths = getPackageRes.paths; + const packageInfo = getPackageRes.packageInfo; + + // add properties that aren't (or aren't yet) on the package const updated = { - ...item, + ...packageInfo, latestVersion: latestPackage.version, - title: item.title || nameAsTitle(item.name), - assets: Registry.groupPathsByService(assets || []), + title: packageInfo.title || nameAsTitle(packageInfo.name), + assets: Registry.groupPathsByService(paths || []), removable: !isRequiredPackage(pkgName), }; return createInstallableFrom(updated, savedObject); } +// gets package from install_source if it exists otherwise gets from registry +export async function getPackageFromSource(options: { + pkgName: string; + pkgVersion: string; + pkgInstallSource?: InstallSource; +}): Promise<{ paths: string[] | undefined; packageInfo: RegistryPackage | ArchivePackage }> { + const { pkgName, pkgVersion, pkgInstallSource } = options; + // TODO: Check package storage before checking registry + let res; + if (pkgInstallSource === 'upload') { + res = getArchivePackage({ + name: pkgName, + version: pkgVersion, + installSource: pkgInstallSource, + }); + if (!res.packageInfo) + throw new Error(`installed package ${pkgName}-${pkgVersion} does not exist in cache`); + } else { + res = await Registry.getRegistryPackage(pkgName, pkgVersion); + } + return res; +} + export async function getInstallationObject(options: { savedObjectsClient: SavedObjectsClientContract; pkgName: string; diff --git a/x-pack/test/fleet_api_integration/apis/epm/get.ts b/x-pack/test/fleet_api_integration/apis/epm/get.ts index c6de3a7f2b9dc..53982affa128c 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/get.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/get.ts @@ -4,16 +4,73 @@ * you may not use this file except in compliance with the Elastic License. */ +import expect from '@kbn/expect'; +import fs from 'fs'; +import path from 'path'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; import { warnAndSkipTest } from '../../helpers'; -export default function ({ getService }: FtrProviderContext) { +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; const log = getService('log'); const supertest = getService('supertest'); const dockerServers = getService('dockerServers'); const server = dockerServers.get('registry'); + const testPkgKey = 'apache-0.1.4'; + + const uninstallPackage = async (pkg: string) => { + await supertest.delete(`/api/fleet/epm/packages/${pkg}`).set('kbn-xsrf', 'xxxx'); + }; + const installPackage = async (pkg: string) => { + await supertest + .post(`/api/fleet/epm/packages/${pkg}`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }); + }; + + const testPkgArchiveZip = path.join( + path.dirname(__filename), + '../fixtures/direct_upload_packages/apache_0.1.4.zip' + ); + describe('EPM - get', () => { + it('returns package info from the registry if it was installed from the registry', async function () { + if (server.enabled) { + // this will install through the registry by default + await installPackage(testPkgKey); + const res = await supertest.get(`/api/fleet/epm/packages/${testPkgKey}`).expect(200); + const packageInfo = res.body.response; + // the uploaded version will have this description + expect(packageInfo.description).to.not.equal('Apache Uploaded Test Integration'); + // download property should exist + expect(packageInfo.download).to.not.equal(undefined); + await uninstallPackage(testPkgKey); + } else { + warnAndSkipTest(this, log); + } + }); + it('returns correct package info if it was installed by upload', async function () { + if (server.enabled) { + const buf = fs.readFileSync(testPkgArchiveZip); + await supertest + .post(`/api/fleet/epm/packages`) + .set('kbn-xsrf', 'xxxx') + .type('application/zip') + .send(buf) + .expect(200); + + const res = await supertest.get(`/api/fleet/epm/packages/${testPkgKey}`).expect(200); + const packageInfo = res.body.response; + // the uploaded version will have this description + expect(packageInfo.description).to.equal('Apache Uploaded Test Integration'); + // download property should not exist on uploaded packages + expect(packageInfo.download).to.equal(undefined); + await uninstallPackage(testPkgKey); + } else { + warnAndSkipTest(this, log); + } + }); it('returns a 500 for a package key without a proper name', async function () { if (server.enabled) { await supertest.get('/api/fleet/epm/packages/-0.1.0').expect(500); diff --git a/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts b/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts index a5f1aa8003f04..885386b092108 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/install_by_upload.ts @@ -80,7 +80,7 @@ export default function (providerContext: FtrProviderContext) { .type('application/zip') .send(buf) .expect(200); - expect(res.body.response.length).to.be(18); + expect(res.body.response.length).to.be(23); }); it('should throw an error if the archive is zip but content type is gzip', async function () { diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/direct_upload_packages/apache_0.1.4.tar.gz b/x-pack/test/fleet_api_integration/apis/fixtures/direct_upload_packages/apache_0.1.4.tar.gz index 9cc4009d35c31..b1f2ac6797fb3 100644 Binary files a/x-pack/test/fleet_api_integration/apis/fixtures/direct_upload_packages/apache_0.1.4.tar.gz and b/x-pack/test/fleet_api_integration/apis/fixtures/direct_upload_packages/apache_0.1.4.tar.gz differ diff --git a/x-pack/test/fleet_api_integration/apis/fixtures/direct_upload_packages/apache_0.1.4.zip b/x-pack/test/fleet_api_integration/apis/fixtures/direct_upload_packages/apache_0.1.4.zip index 410b00ecde2be..2095ed0dba345 100644 Binary files a/x-pack/test/fleet_api_integration/apis/fixtures/direct_upload_packages/apache_0.1.4.zip and b/x-pack/test/fleet_api_integration/apis/fixtures/direct_upload_packages/apache_0.1.4.zip differ