-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[EPM] More realistic datasource SO. Error if package not installed. #52229
Changes from 7 commits
6d955fc
118b2ca
4e4a285
d527dbd
c8e7b08
4ca68a5
09523d2
736610a
ac4d54d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,76 +8,94 @@ import { SavedObjectsClientContract } from 'src/core/server/'; | |
import { CallESAsCurrentUser } from '../lib/cluster_access'; | ||
import { installPipelines } from '../lib/elasticsearch/ingest_pipeline/ingest_pipelines'; | ||
import { installTemplates } from '../lib/elasticsearch/template/install'; | ||
import { AssetReference } from '../../common/types'; | ||
import { AssetReference, InstallationStatus, RegistryPackage } from '../../common/types'; | ||
import { SAVED_OBJECT_TYPE_DATASOURCES } from '../../common/constants'; | ||
import { Datasource, Asset, InputType } from '../../../ingest/server/libs/types'; | ||
import { getPackageInfo } from '../packages'; | ||
import * as Registry from '../registry'; | ||
|
||
const pkgToPkgKey = ({ name, version }: RegistryPackage) => `${name}-${version}`; | ||
export class PackageNotInstalledError extends Error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A class 🎉 😄 |
||
constructor(pkgkey: string) { | ||
super(`${pkgkey} is not installed`); | ||
} | ||
} | ||
|
||
export async function createDatasource(options: { | ||
savedObjectsClient: SavedObjectsClientContract; | ||
pkgkey: string; | ||
callCluster: CallESAsCurrentUser; | ||
}) { | ||
const { savedObjectsClient, pkgkey, callCluster } = options; | ||
const info = await getPackageInfo({ savedObjectsClient, pkgkey }); | ||
|
||
if (info.status !== InstallationStatus.installed) { | ||
throw new PackageNotInstalledError(pkgkey); | ||
} | ||
|
||
const toSave = await installPipelines({ pkgkey, callCluster }); | ||
// TODO: Clean up | ||
const info = await Registry.fetchInfo(pkgkey); | ||
await installTemplates(info, callCluster); | ||
const pkg = await Registry.fetchInfo(pkgkey); | ||
|
||
await saveDatasourceReferences({ | ||
savedObjectsClient, | ||
pkgkey, | ||
toSave, | ||
}); | ||
await Promise.all([ | ||
installTemplates(pkg, callCluster), | ||
saveDatasourceReferences({ | ||
savedObjectsClient, | ||
pkg, | ||
toSave, | ||
}), | ||
]); | ||
|
||
return toSave; | ||
} | ||
|
||
export async function saveDatasourceReferences(options: { | ||
async function saveDatasourceReferences(options: { | ||
savedObjectsClient: SavedObjectsClientContract; | ||
pkgkey: string; | ||
pkg: RegistryPackage; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the |
||
toSave: AssetReference[]; | ||
}) { | ||
const { savedObjectsClient, pkgkey, toSave } = options; | ||
const savedObject = await getDatasourceObject({ savedObjectsClient, pkgkey }); | ||
const savedRefs = savedObject?.attributes.package.assets; | ||
const mergeRefsReducer = (current: Asset[] = [], pending: Asset) => { | ||
const hasRef = current.find(c => c.id === pending.id && c.type === pending.type); | ||
if (!hasRef) current.push(pending); | ||
const { savedObjectsClient, pkg, toSave } = options; | ||
const savedDatasource = await getDatasource({ savedObjectsClient, pkg }); | ||
const savedAssets = savedDatasource?.package.assets; | ||
const assetsReducer = (current: Asset[] = [], pending: Asset) => { | ||
const hasAsset = current.find(c => c.id === pending.id && c.type === pending.type); | ||
if (!hasAsset) current.push(pending); | ||
return current; | ||
}; | ||
|
||
const toInstall = (toSave as Asset[]).reduce(mergeRefsReducer, savedRefs); | ||
const datasource: Datasource = createFakeDatasource(pkgkey, toInstall); | ||
const toInstall = (toSave as Asset[]).reduce(assetsReducer, savedAssets); | ||
const datasource: Datasource = createFakeDatasource(pkg, toInstall); | ||
await savedObjectsClient.create<Datasource>(SAVED_OBJECT_TYPE_DATASOURCES, datasource, { | ||
id: pkgkey, | ||
id: pkgToPkgKey(pkg), | ||
overwrite: true, | ||
}); | ||
|
||
return toInstall; | ||
} | ||
|
||
export async function getDatasourceObject(options: { | ||
async function getDatasource(options: { | ||
savedObjectsClient: SavedObjectsClientContract; | ||
pkgkey: string; | ||
pkg: RegistryPackage; | ||
}) { | ||
const { savedObjectsClient, pkgkey } = options; | ||
return savedObjectsClient | ||
.get<Datasource>(SAVED_OBJECT_TYPE_DATASOURCES, pkgkey) | ||
const { savedObjectsClient, pkg } = options; | ||
const datasource = await savedObjectsClient | ||
.get<Datasource>(SAVED_OBJECT_TYPE_DATASOURCES, pkgToPkgKey(pkg)) | ||
.catch(e => undefined); | ||
|
||
return datasource?.attributes; | ||
} | ||
|
||
function createFakeDatasource(pkgkey: string, assets: Asset[] = []): Datasource { | ||
function createFakeDatasource(pkg: RegistryPackage, assets: Asset[] = []): Datasource { | ||
return { | ||
id: pkgkey, | ||
id: pkgToPkgKey(pkg), | ||
name: 'name', | ||
read_alias: 'read_alias', | ||
package: { | ||
name: 'name', | ||
version: '1.0.1, 1.3.1', | ||
description: 'description', | ||
title: 'title', | ||
assets: assets as Asset[], | ||
name: pkg.name, | ||
version: pkg.version, | ||
description: pkg.description, | ||
title: pkg.title, | ||
assets, | ||
}, | ||
streams: [ | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,8 +5,14 @@ | |
*/ | ||
|
||
import * as Registry from '../registry'; | ||
import { cacheHas } from '../registry/cache'; | ||
import { RegistryPackage } from '../../common/types'; | ||
|
||
// paths from RegistryPackage are routes to the assets on EPR | ||
// paths for ArchiveEntry are routes to the assets in the archive | ||
// RegistryPackage paths have a `/package/` prefix compared to ArchiveEntry paths | ||
const EPR_PATH_PREFIX = '/package/'; | ||
|
||
export function getAssets( | ||
packageInfo: RegistryPackage, | ||
filter = (path: string): boolean => true, | ||
|
@@ -24,7 +30,7 @@ export function getAssets( | |
if (dataSet !== '') { | ||
// TODO: Filter for dataset path | ||
const comparePath = | ||
'/package/' + packageInfo.name + '-' + packageInfo.version + '/dataset/' + dataSet; | ||
EPR_PATH_PREFIX + packageInfo.name + '-' + packageInfo.version + '/dataset/' + dataSet; | ||
if (!path.includes(comparePath)) { | ||
continue; | ||
} | ||
|
@@ -40,26 +46,23 @@ export function getAssets( | |
return assets; | ||
} | ||
|
||
export function getAssetsData( | ||
export async function getAssetsData( | ||
packageInfo: RegistryPackage, | ||
filter = (path: string): boolean => true, | ||
dataSet: string = '' | ||
): Registry.ArchiveEntry[] { | ||
): Promise<Registry.ArchiveEntry[]> { | ||
// TODO: Needs to be called to fill the cache but should not be required | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could probably remove this comment here as it was odd having this extra request in the template code but here in assets it makes more sense, especially as it prechecks the cache. |
||
const pkgkey = packageInfo.name + '-' + packageInfo.version; | ||
if (!cacheHas(pkgkey)) await Registry.getArchiveInfo(pkgkey); | ||
|
||
// Gather all asset data | ||
const assets = getAssets(packageInfo, filter, dataSet); | ||
const entries: Registry.ArchiveEntry[] = assets.map(path => { | ||
const archivePath = path.replace(EPR_PATH_PREFIX, ''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nicer solution than the |
||
const buffer = Registry.getAsset(archivePath); | ||
|
||
const entries: Registry.ArchiveEntry[] = []; | ||
|
||
for (const asset of assets) { | ||
const subPath = asset.substring(9); | ||
const buf = Registry.getAsset(subPath); | ||
|
||
const entry: Registry.ArchiveEntry = { | ||
path: asset, | ||
buffer: buf, | ||
}; | ||
entries.push(entry); | ||
} | ||
return { path, buffer }; | ||
}); | ||
|
||
return entries; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
General question not related to this PR: I would normally put a method which is running with
RegistryPackage
close to the definition of it so it is easy to find again for others. I'm aware this one is only used here, but I'm pretty sure it will be used in other places in the future. In Typescript I see this pattern of thetypes.ts
file which does not contain any functions. Is this the pattern to follow? Just curious.