UNPKG

mongodb-download-url

Version:
387 lines (356 loc) 11.1 kB
import os from 'os'; import path from 'path'; import semver from 'semver'; import type { DownloadInfo, VersionListOpts } from './version-list'; import { getVersion, clearCache } from './version-list'; import { getCurrentLinuxDistro } from './linux-distro'; import { inspect } from 'util'; import _debug from 'debug'; const debug = _debug('mongodb-download-url'); type PriorityValue<T> = { value: T; priority: number }; type ArtifactOptions = { /** * Specify the version as a semver string. This also accepts the special values: * - 'stable' (or 'latest') for the latest stable version (default) * - 'unstable' for the latest unstable version * - 'latest-alpha' for the latest alpha release of the server */ version?: string; /** * Specify the binary architecture. Default is host architecture. */ arch?: string; /** * Specify the binary platforn. Default is host platform, as given by * `process.platform`. */ platform?: string; /** * If known, specify a mongodb DISTRO_ID string to pick a specific Linux distro. */ distro?: string; /** * If true, this will return enterprise servers rather than community servers. */ enterprise?: boolean; /** * If true, this will return the mongocryptd-only package, if available. * (This typically only makes sense with { enterprise: true }.) */ cryptd?: boolean; /** * If true, this will return the mongo_crypt_shared library package, if available. * (This typically only makes sense with { enterprise: true }.) */ crypt_shared?: boolean; /** * @deprecated Use crypt_shared instead. */ csfle?: boolean; /** * @deprecated Use arch instead. */ bits?: '32' | '64' | 32 | 64; }; export type Options = ArtifactOptions & VersionListOpts; export type DownloadArtifactInfo = Required<Omit<ArtifactOptions, 'csfle'>> & { /** Full download URL. */ url: string; /** Filename for the given download URL. */ artifact: string; /** Currently always 'mongodb'. */ name: string; // Legacy properties: /** @deprecated */ ext: string; /** @deprecated */ filenamePlatform: string; /** @deprecated */ debug: false; /** @deprecated */ branch: 'master'; }; type ProcessedOptions = { version: string; arch: string[]; target: PriorityValue<string>[]; enterprise: boolean; cryptd: boolean; crypt_shared: boolean; }; function getPriority<T>(values: PriorityValue<T>[], candidate: T): number { for (const { value, priority } of values) { if (value === candidate) { return priority; } } return 0; } function maximizer<T>(values: T[], evaluator: (v: T) => number): T | undefined { let max = -Infinity; let maximizer: T | undefined; for (const v of values) { const result = evaluator(v); if (result > max) { max = result; maximizer = v; } } return maximizer; } function parseArch(arch: string): string[] { if (['i686', 'i386', 'x86', 'ia32'].includes(arch)) { return ['i686', 'i386', 'x86', 'ia32']; } if (['x86_64', 'x64'].includes(arch)) { return ['x86_64', 'x64']; } if (['arm64', 'aarch64'].includes(arch)) { return ['arm64', 'aarch64']; } if (['ppc64', 'ppc64le'].includes(arch)) { return ['ppc64', 'ppc64le']; } return [arch]; } async function parseTarget( distro: string | undefined, platform: string, archs: string[], version: string, ): Promise<PriorityValue<string>[]> { if (platform === 'linux') { const results: PriorityValue<string>[] = []; if (distro) { results.push({ value: distro, priority: 1000 }); if (archs.includes('x86_64')) { if (distro === 'amzn64' || distro === 'amazon1') { results.push({ value: 'amazon', priority: 900 }); } if (distro === 'amazon' || distro === 'amazon1') { results.push({ value: 'amzn64', priority: 900 }); } } } if (archs.includes('x86_64')) { results.push({ value: 'linux_x86_64', priority: 1 }); } else if (archs.includes('i686')) { results.push({ value: 'linux_i686', priority: 1 }); } let distroResultsErr; try { results.push(...(await getCurrentLinuxDistro())); } catch (err) { distroResultsErr = err; } if ( distro === undefined && distroResultsErr && (version === '*' || version === 'latest-alpha' || semver.gte(version, '4.0.0')) ) { throw distroResultsErr; } return results; } else if (platform === 'sunos') { return [{ value: 'sunos5', priority: 1 }]; } else if (['win32', 'windows'].includes(platform)) { if (archs.includes('i686')) { return [ { value: 'windows', priority: 1 }, { value: 'windows_i686', priority: 10 }, ]; } else { return [ { value: 'windows', priority: 1 }, { value: 'windows_x86_64', priority: 10 }, { value: 'windows_x86_64-2008plus', priority: 10 }, { value: 'windows_x86_64-2008plus-ssl', priority: 100 }, { value: 'windows_x86_64-2012plus', priority: 100 }, ]; } } else if (['darwin', 'osx', 'macos'].includes(platform)) { return [ { value: 'osx', priority: 1 }, { value: 'osx-ssl', priority: 10 }, { value: 'darwin', priority: 1 }, { value: 'macos', priority: 1 }, ]; } return [{ value: platform, priority: 1 }]; } async function resolve(opts: ProcessedOptions): Promise<DownloadArtifactInfo> { let download: DownloadInfo | undefined; if (opts.version === 'latest-alpha' && opts.enterprise) { const targets = opts.target.map(({ value }) => value); const arch = opts.arch.includes('arm64') ? 'arm64' : 'x86_64'; let url, target: string | undefined; if (targets.includes('macos')) { url = `https://downloads.mongodb.com/osx/mongodb-macos-${arch}-enterprise-latest.tgz`; target = 'macos'; } else if (targets.includes('linux_x86_64')) { target = maximizer(opts.target, (candidate) => candidate.priority)!.value; url = `https://downloads.mongodb.com/linux/mongodb-linux-${arch}-enterprise-${target}-latest.tgz`; } else if (targets.includes('windows_x86_64')) { target = 'windows'; url = `https://downloads.mongodb.com/windows/mongodb-windows-${arch}-enterprise-latest.zip`; } if (url) { download = { target, edition: 'enterprise', arch: 'x86_64', archive: { url, sha1: '', sha256: '', debug_symbols: '', }, }; } } let version; if (!download) { version = await getVersion(opts); if (!version) { throw new Error(`Could not find version matching ${inspect(opts)}`); } const bestDownload = maximizer( version.downloads.map((candidate: DownloadInfo) => { if (opts.enterprise) { if (candidate.edition !== 'enterprise') { return { value: candidate, priority: 0 }; } } else { if ( candidate.edition !== 'targeted' && candidate.edition !== 'base' ) { return { value: candidate, priority: 0 }; } } if (!candidate.arch || !opts.arch.includes(candidate.arch)) { return { value: candidate, priority: 0 }; } const targetPriority = getPriority(opts.target, candidate.target); return { value: candidate, priority: targetPriority }; }), (candidate: PriorityValue<DownloadInfo>) => candidate.priority, ); if (bestDownload && bestDownload.priority > 0) { download = bestDownload.value; } } if (!download) { throw new Error( `Could not find download URL for version ${version?.version} ${inspect( opts, )}`, ); } const wantsCryptd = opts.cryptd && download.target; const wantsCryptShared = opts.crypt_shared && download.target; if (wantsCryptShared && !download.crypt_shared && !download.csfle) { throw new Error( `No crypt_shared library download for version ${ version?.version } available ${inspect(opts)}`, ); } debug('fully resolved', JSON.stringify(opts, null, 2), download); // mongocryptd is contained in the regular enterprise archive, the csfle lib is not let { url } = wantsCryptShared ? (download.crypt_shared ?? download.csfle)! : ((wantsCryptd ? download.cryptd : null) ?? download.archive); if (wantsCryptd) { // cryptd package on Windows was buggy: https://jira.mongodb.org/browse/BUILD-13653 url = url.replace('mongodb-shell-windows', 'mongodb-cryptd-windows'); } return { ...opts, name: 'mongodb', url: url, arch: download.arch!, distro: download.target!, platform: download.target!, filenamePlatform: download.target!, version: version?.version ?? '*', artifact: path.basename(url), debug: false, enterprise: download.edition === 'enterprise', branch: 'master', bits: ['i386', 'i686'].includes(download.arch!) ? '32' : '64', ext: url.match(/\.([^.]+)$/)?.[1] ?? 'tgz', }; } async function options( opts: Options | string = {}, ): Promise<ProcessedOptions & VersionListOpts> { if (typeof opts === 'string') { opts = { version: opts, }; } else { opts = { ...opts }; } opts.crypt_shared ??= opts.csfle; if (opts.cryptd && opts.crypt_shared) { throw new Error('Cannot request both cryptd and csfle package'); } if (opts.bits && !opts.arch) { opts.arch = +opts.bits === 32 ? 'ia32' : 'x64'; } if (!opts.arch) { opts.arch = os.arch(); } if (!opts.platform) { opts.platform = os.platform(); } if (!opts.version) { opts.version = process.env.MONGODB_VERSION || 'stable'; } if (opts.productionOnly) { opts.allowedTags = ['production_release']; } if ( opts.version === 'stable' || opts.version === 'latest' || opts.version === '*' ) { opts.version = '*'; opts.allowedTags ??= ['production_release']; } else if (opts.version === 'rapid' || opts.version === 'continuous') { // a.k.a. quarterly etc. opts.version = '*'; opts.allowedTags ??= ['production_release', 'continuous_release']; } else if (opts.version === 'unstable') { opts.version = '*'; opts.allowedTags ??= ['*']; } const processedOptions: ProcessedOptions & VersionListOpts = { ...opts, arch: parseArch(opts.arch), target: [], enterprise: !!opts.enterprise, cryptd: !!opts.cryptd, crypt_shared: !!opts.crypt_shared, version: opts.version, }; processedOptions.target = await parseTarget( opts.distro, opts.platform, processedOptions.arch, processedOptions.version, ); return processedOptions; } export async function getDownloadURL( opts?: Options | string, ): Promise<DownloadArtifactInfo> { const parsedOptions = await options(opts); debug('Building URL for options `%j`', parsedOptions); return await resolve(parsedOptions); } export default getDownloadURL; export { clearCache };