diff --git a/x-pack/legacy/plugins/integrations_manager/common/routes.ts b/x-pack/legacy/plugins/integrations_manager/common/routes.ts index e0b09f20db424e..6c75e61ab8bcac 100644 --- a/x-pack/legacy/plugins/integrations_manager/common/routes.ts +++ b/x-pack/legacy/plugins/integrations_manager/common/routes.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ import { PLUGIN } from './constants'; -import { AssetType, CategoryId } from './types'; +import { CategoryId } from './types'; export const API_ROOT = `/api/${PLUGIN.ID}`; export const API_LIST_PATTERN = `${API_ROOT}/list`; export const API_INFO_PATTERN = `${API_ROOT}/package/{pkgkey}`; -export const API_INSTALL_PATTERN = `${API_ROOT}/install/{pkgkey}/{asset?}`; -export const API_DELETE_PATTERN = `${API_ROOT}/delete/{pkgkey}/{asset?}`; +export const API_INSTALL_PATTERN = `${API_ROOT}/install/{pkgkey}`; +export const API_DELETE_PATTERN = `${API_ROOT}/delete/{pkgkey}`; export const API_CATEGORIES_PATTERN = `${API_ROOT}/categories`; export interface ListParams { @@ -32,15 +32,10 @@ export function getInfoPath(pkgkey: string) { export function getFilePath(filePath: string) { return `${API_ROOT}${filePath}`; } - -export function getInstallPath(pkgkey: string, asset?: AssetType) { - return API_INSTALL_PATTERN.replace('{pkgkey}', pkgkey) - .replace('{asset?}', asset || '') - .replace(/\/$/, ''); // trim trailing slash +export function getInstallPath(pkgkey: string) { + return API_INSTALL_PATTERN.replace('{pkgkey}', pkgkey).replace(/\/$/, ''); // trim trailing slash } -export function getRemovePath(pkgkey: string, asset?: AssetType) { - return API_DELETE_PATTERN.replace('{pkgkey}', pkgkey) - .replace('{asset?}', asset || '') - .replace(/\/$/, ''); // trim trailing slash +export function getRemovePath(pkgkey: string) { + return API_DELETE_PATTERN.replace('{pkgkey}', pkgkey).replace(/\/$/, ''); // trim trailing slash } diff --git a/x-pack/legacy/plugins/integrations_manager/common/types.ts b/x-pack/legacy/plugins/integrations_manager/common/types.ts index 7dfae06ed43573..96622a84bbe71d 100644 --- a/x-pack/legacy/plugins/integrations_manager/common/types.ts +++ b/x-pack/legacy/plugins/integrations_manager/common/types.ts @@ -10,14 +10,15 @@ export { Request, ResponseToolkit, Server, ServerRoute } from 'hapi'; export type InstallationStatus = Installed['status'] | NotInstalled['status']; -export type AssetType = - | 'config' - | 'dashboard' - | 'index-pattern' - | 'ingest-pipeline' - | 'search' - | 'timelion-sheet' - | 'visualization'; +export enum AssetType { + config = 'config', + dashboard = 'dashboard', + visualization = 'visualization', + search = 'search', + ingestPipeline = 'ingest-pipeline', + indexPattern = 'index-pattern', + timelionSheet = 'timelion-sheet', +} // Registry's response types // from /search diff --git a/x-pack/legacy/plugins/integrations_manager/server/packages/handlers.ts b/x-pack/legacy/plugins/integrations_manager/server/packages/handlers.ts index 4b265e732dac69..b03e70a88b29d5 100644 --- a/x-pack/legacy/plugins/integrations_manager/server/packages/handlers.ts +++ b/x-pack/legacy/plugins/integrations_manager/server/packages/handlers.ts @@ -27,24 +27,19 @@ interface ListPackagesRequest extends Request { query: Request['query'] & SearchParams; } -interface PackageRequest extends Request { +interface PackageInfoRequest extends Request { params: { pkgkey: string; }; } -interface InstallAssetRequest extends Request { - params: AssetRequestParams; -} - -interface DeleteAssetRequest extends Request { - params: AssetRequestParams; +interface InstallDeletePackageRequest extends Request { + params: { + pkgkey: string; + asset: AssetType; + }; } -type AssetRequestParams = PackageRequest['params'] & { - asset?: AssetType; -}; - export async function handleGetCategories(req: Request, extra: Extra) { return getCategories(); } @@ -59,7 +54,7 @@ export async function handleGetList(req: ListPackagesRequest, extra: Extra) { return packageList; } -export async function handleGetInfo(req: PackageRequest, extra: Extra) { +export async function handleGetInfo(req: PackageInfoRequest, extra: Extra) { const { pkgkey } = req.params; const savedObjectsClient = getClient(req); const packageInfo = await getPackageInfo({ savedObjectsClient, pkgkey }); @@ -81,26 +76,20 @@ export const handleGetFile = async (req: Request, extra: Extra) => { return epmResponse; }; -export async function handleRequestInstall(req: InstallAssetRequest, extra: Extra) { - const { pkgkey, asset } = req.params; - if (!asset) throw new Error('Unhandled empty/default asset case'); - +export async function handleRequestInstall(req: InstallDeletePackageRequest, extra: Extra) { + const { pkgkey } = req.params; const savedObjectsClient = getClient(req); - const callCluster = getClusterAccessor(extra.context.esClient, req); - const object = await installPackage({ + return await installPackage({ savedObjectsClient, pkgkey, - asset, - callCluster, }); - - return object; } -export async function handleRequestDelete(req: DeleteAssetRequest, extra: Extra) { +export async function handleRequestDelete(req: InstallDeletePackageRequest, extra: Extra) { const { pkgkey } = req.params; const savedObjectsClient = getClient(req); - const deleted = await removeInstallation({ savedObjectsClient, pkgkey }); + const callCluster = getClusterAccessor(extra.context.esClient, req); + const deleted = await removeInstallation({ savedObjectsClient, pkgkey, callCluster }); return deleted; } diff --git a/x-pack/legacy/plugins/integrations_manager/server/packages/index.ts b/x-pack/legacy/plugins/integrations_manager/server/packages/index.ts index e72a4cb39649a7..301b7b1c2de7f3 100644 --- a/x-pack/legacy/plugins/integrations_manager/server/packages/index.ts +++ b/x-pack/legacy/plugins/integrations_manager/server/packages/index.ts @@ -15,12 +15,12 @@ export * from './handlers'; export type CallESAsCurrentUser = ScopedClusterClient['callAsCurrentUser']; export const SAVED_OBJECT_TYPES = new Set([ - 'config', - 'dashboard', - 'index-pattern', - 'search', - 'timelion-sheet', - 'visualization', + AssetType.config, + AssetType.dashboard, + AssetType.indexPattern, + AssetType.search, + AssetType.timelionSheet, + AssetType.visualization, ]); export function getClusterAccessor(esClient: IClusterClient, req: Request) { @@ -40,6 +40,6 @@ export function createInstallableFrom(from: T, savedObject?: Installation): I }; } -export function assetUsesObjects(asset: AssetType) { - return SAVED_OBJECT_TYPES.has(asset); +export function assetUsesObjects(assetType: AssetType) { + return SAVED_OBJECT_TYPES.has(assetType); } diff --git a/x-pack/legacy/plugins/integrations_manager/server/packages/install.ts b/x-pack/legacy/plugins/integrations_manager/server/packages/install.ts index 72b3118cf5af6a..7aec7408469787 100644 --- a/x-pack/legacy/plugins/integrations_manager/server/packages/install.ts +++ b/x-pack/legacy/plugins/integrations_manager/server/packages/install.ts @@ -8,35 +8,30 @@ import { SavedObject, SavedObjectsClientContract } from 'src/core/server/'; import { SAVED_OBJECT_TYPE } from '../../common/constants'; import { AssetReference, AssetType, InstallationAttributes } from '../../common/types'; import * as Registry from '../registry'; -import { CallESAsCurrentUser, assetUsesObjects, getInstallationObject } from './index'; +import { getInstallationObject, getPackageInfo } from './index'; import { getObjects } from './get_objects'; export async function installPackage(options: { savedObjectsClient: SavedObjectsClientContract; pkgkey: string; - asset: AssetType; - callCluster: CallESAsCurrentUser; }) { - const { savedObjectsClient, pkgkey, asset, callCluster } = options; - // install any assets (in ES, as Saved Objects, etc) as required. Get references to them + const { savedObjectsClient, pkgkey } = options; + const toSave = await installAssets({ savedObjectsClient, pkgkey, - asset, - callCluster, }); if (toSave.length) { - // saved those references in the package manager's state object - const saved = await saveInstallationReferences({ + // Save those references in the integration manager's state saved object + await saveInstallationReferences({ savedObjectsClient, pkgkey, toSave, }); - return saved; } - return []; + return getPackageInfo({ savedObjectsClient, pkgkey }); } // the function which how to install each of the various asset types @@ -45,20 +40,20 @@ export async function installPackage(options: { export async function installAssets(options: { savedObjectsClient: SavedObjectsClientContract; pkgkey: string; - asset: AssetType; - callCluster: CallESAsCurrentUser; }) { - const { savedObjectsClient, pkgkey, asset, callCluster } = options; - if (assetUsesObjects(asset)) { - const references = await installObjects({ savedObjectsClient, pkgkey, asset }); - return references; - } - if (asset === 'ingest-pipeline') { - const references = await installPipelines({ callCluster, pkgkey }); - return references; - } + const { savedObjectsClient, pkgkey } = options; + + // Only install certain Kibana assets during package installation. + // All other asset types need special handling + const typesToInstall = [AssetType.visualization, AssetType.dashboard, AssetType.search]; + + const installationPromises = typesToInstall.map(async assetType => + installKibanaSavedObjects({ savedObjectsClient, pkgkey, assetType }) + ); - return []; + // installKibanaSavedObjects returns AssetReference[], so .map creates AssetReference[][] + // call .flat to flatten into one dimensional array + return Promise.all(installationPromises).then(results => results.flat()); } export async function saveInstallationReferences(options: { @@ -85,57 +80,29 @@ export async function saveInstallationReferences(options: { return results; } -async function installObjects({ +async function installKibanaSavedObjects({ savedObjectsClient, pkgkey, - asset, + assetType, }: { savedObjectsClient: SavedObjectsClientContract; pkgkey: string; - asset: AssetType; -}) { - const isSameType = ({ path }: Registry.ArchiveEntry) => asset === Registry.pathParts(path).type; - const toBeSavedObjects = await getObjects(pkgkey, isSameType); - const createResults = await savedObjectsClient.bulkCreate(toBeSavedObjects, { overwrite: true }); - const createdObjects = createResults.saved_objects; - const installed = createdObjects.map(toAssetReference); - - return installed; -} - -async function installPipelines({ - callCluster, - pkgkey, -}: { - callCluster: CallESAsCurrentUser; - pkgkey: string; + assetType: AssetType; }) { - const isPipeline = ({ path }: Registry.ArchiveEntry) => - Registry.pathParts(path).type === 'ingest-pipeline'; - const paths = await Registry.getArchiveInfo(pkgkey, isPipeline); - const installationPromises = paths.map(path => installPipeline({ callCluster, path })); - const references = await Promise.all(installationPromises); - - return references; -} + const isSameType = ({ path }: Registry.ArchiveEntry) => + assetType === Registry.pathParts(path).type; -async function installPipeline({ - callCluster, - path, -}: { - callCluster: CallESAsCurrentUser; - path: string; -}): Promise { - const buffer = Registry.getAsset(path); - // sample data is invalid json. strip the offending parts before parsing - const json = buffer.toString('utf8').replace(/\\/g, ''); - const pipeline = JSON.parse(json); - const { file, type } = Registry.pathParts(path); - const id = file.replace('.json', ''); - // TODO: any sort of error, not "happy path", handling - await callCluster('ingest.putPipeline', { id, body: pipeline }); - - return { id, type }; + const toBeSavedObjects = await getObjects(pkgkey, isSameType); + if (toBeSavedObjects.length === 0) { + return []; + } else { + const createResults = await savedObjectsClient.bulkCreate(toBeSavedObjects, { + overwrite: true, + }); + const createdObjects = createResults.saved_objects; + const installed = createdObjects.map(toAssetReference); + return installed; + } } function toAssetReference({ id, type }: SavedObject) { diff --git a/x-pack/legacy/plugins/integrations_manager/server/packages/remove.ts b/x-pack/legacy/plugins/integrations_manager/server/packages/remove.ts index 76f0a28e4f84f7..030c68ca20b488 100644 --- a/x-pack/legacy/plugins/integrations_manager/server/packages/remove.ts +++ b/x-pack/legacy/plugins/integrations_manager/server/packages/remove.ts @@ -6,13 +6,20 @@ import { SavedObjectsClientContract } from 'src/core/server/'; import { SAVED_OBJECT_TYPE } from '../../common/constants'; -import { getInstallationObject } from './index'; +import { + getInstallationObject, + assetUsesObjects, + CallESAsCurrentUser, + getPackageInfo, +} from './index'; +import { AssetType } from '../../common/types'; export async function removeInstallation(options: { savedObjectsClient: SavedObjectsClientContract; pkgkey: string; + callCluster: CallESAsCurrentUser; }) { - const { savedObjectsClient, pkgkey } = options; + const { savedObjectsClient, pkgkey, callCluster } = options; const installation = await getInstallationObject({ savedObjectsClient, pkgkey }); const installedObjects = (installation && installation.attributes.installed) || []; @@ -21,11 +28,24 @@ export async function removeInstallation(options: { await savedObjectsClient.delete(SAVED_OBJECT_TYPE, pkgkey); // Delete the installed assets - const deletePromises = installedObjects.map(async ({ id, type }) => - savedObjectsClient.delete(type, id) - ); + const deletePromises = installedObjects.map(async ({ id, type }) => { + if (assetUsesObjects(type as AssetType)) { + savedObjectsClient.delete(type, id); + } else if (type === AssetType.ingestPipeline) { + deletePipeline(callCluster, id); + } + }); await Promise.all(deletePromises); // successful delete's in SO client return {}. return something more useful - return installedObjects; + const packageInfo = await getPackageInfo({ savedObjectsClient, pkgkey }); + + return packageInfo; +} + +async function deletePipeline(callCluster: CallESAsCurrentUser, id: string): Promise { + // '*' shouldn't ever appear here, but it still would delete all ingest pipelines + if (id && id !== '*') { + await callCluster('ingest.deletePipeline', { id }); + } }