UNPKG

redis-memory-server

Version:

Redis Server for testing. The server will allow you to connect your favorite client library to the Redis Server and run parallel integration tests isolated from each other.

328 lines 15.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const url_1 = __importDefault(require("url")); const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const rimraf_1 = require("rimraf"); const http_1 = __importDefault(require("http")); const https_1 = __importDefault(require("https")); const tar_1 = __importDefault(require("tar")); const extract_zip_1 = __importDefault(require("extract-zip")); const RedisBinaryDownloadUrl_1 = __importDefault(require("./RedisBinaryDownloadUrl")); const RedisBinary_1 = require("./RedisBinary"); const https_proxy_agent_1 = require("https-proxy-agent"); const child_process_1 = require("child_process"); const util_1 = require("util"); require("./resolve-config"); const debug_1 = __importDefault(require("debug")); const log = (0, debug_1.default)('RedisMS:RedisBinaryDownload'); /** * Download and extract the "redis-server" binary */ class RedisBinaryDownload { constructor({ downloadDir, version }) { this.version = version !== null && version !== void 0 ? version : RedisBinary_1.LATEST_VERSION; this.downloadDir = path_1.default.resolve(downloadDir || 'redis-download'); this.dlProgress = { current: 0, length: 0, totalMb: 0, lastPrintedAt: 0, }; } /** * Get the path of the already downloaded "redis-server" file * otherwise download it and then return the path */ getRedisServerPath() { return __awaiter(this, void 0, void 0, function* () { const binaryName = process.platform === 'win32' ? 'memurai.exe' : 'redis-server'; const redisServerPath = path_1.default.resolve(this.downloadDir, this.version, binaryName); if (yield this.locationExists(redisServerPath)) { return redisServerPath; } const redisArchive = yield this.startDownload(); const extractDir = yield this.extract(redisArchive); if (process.platform === 'win32') { yield this.makeInstallWin32(extractDir); } else { yield this.makeInstall(extractDir); } fs_1.default.unlinkSync(redisArchive); if (yield this.locationExists(redisServerPath)) { return redisServerPath; } throw new Error(`Cannot find downloaded redis-server binary by path ${redisServerPath}`); }); } /** * Download the Redis Archive * @returns The Redis Archive location */ startDownload() { return __awaiter(this, void 0, void 0, function* () { const mbdUrl = new RedisBinaryDownloadUrl_1.default({ version: this.version, }); if (!fs_1.default.existsSync(this.downloadDir)) { fs_1.default.mkdirSync(this.downloadDir); } const downloadUrl = yield mbdUrl.getDownloadUrl(); return this.download(downloadUrl); }); } /** * Download file from downloadUrl * @param downloadUrl URL to download a File */ download(downloadUrl) { return __awaiter(this, void 0, void 0, function* () { const proxy = process.env['yarn_https-proxy'] || process.env.yarn_proxy || process.env['npm_config_https-proxy'] || process.env.npm_config_proxy || process.env.https_proxy || process.env.http_proxy || process.env.HTTPS_PROXY || process.env.HTTP_PROXY; const strictSsl = process.env.npm_config_strict_ssl === 'true'; const urlObject = url_1.default.parse(downloadUrl); if (!urlObject.hostname || !urlObject.path) { throw new Error(`Provided incorrect download url: ${downloadUrl}`); } const downloadOptions = { hostname: urlObject.hostname, port: urlObject.port || '443', path: urlObject.path, protocol: urlObject.protocol || 'https:', method: 'GET', rejectUnauthorized: strictSsl, agent: proxy ? new https_proxy_agent_1.HttpsProxyAgent(proxy) : undefined, }; const filename = (urlObject.pathname || '').split('/').pop(); if (!filename) { throw new Error(`RedisBinaryDownload: missing filename for url ${downloadUrl}`); } const downloadLocation = path_1.default.resolve(this.downloadDir, filename); const tempDownloadLocation = path_1.default.resolve(this.downloadDir, `${filename}.downloading`); log(`Downloading${proxy ? ` via proxy ${proxy}` : ''}: "${downloadUrl}"`); if (yield this.locationExists(downloadLocation)) { log('Already downloaded archive found, skipping download'); return downloadLocation; } this._downloadingUrl = downloadUrl; const downloadedFile = yield this.httpDownload(downloadOptions, downloadLocation, tempDownloadLocation); return downloadedFile; }); } /** * Extract given Archive * @param redisArchive Archive location * @returns extracted directory location */ extract(redisArchive) { return __awaiter(this, void 0, void 0, function* () { var _a; const extractDir = path_1.default.resolve(this.downloadDir, this.version, 'extracted'); log(`extract(): ${extractDir}`); if (!fs_1.default.existsSync(extractDir)) { fs_1.default.mkdirSync(extractDir, { recursive: true }); } if (redisArchive.endsWith('.zip')) { yield this.extractZip(redisArchive, extractDir); } else if (redisArchive.endsWith('.tar.gz')) { yield this.extractTarGz(redisArchive, extractDir); } else if (redisArchive.endsWith('.msi')) { yield this.extractMsi(redisArchive, extractDir); } else { throw new Error(`RedisBinaryDownload: unsupported archive ${redisArchive} (downloaded from ${(_a = this._downloadingUrl) !== null && _a !== void 0 ? _a : 'unkown'}). Broken archive from Redis Provider?`); } return extractDir; }); } /** * Extract a .tar.gz archive * @param redisArchive Archive location * @param extractDir Directory to extract to */ extractTarGz(redisArchive, extractDir) { return __awaiter(this, void 0, void 0, function* () { yield tar_1.default.extract({ file: redisArchive, cwd: extractDir, strip: 1, }); }); } /** * Extract a .zip archive * @param redisArchive Archive location * @param extractDir Directory to extract to */ extractZip(redisArchive, extractDir) { return __awaiter(this, void 0, void 0, function* () { yield (0, extract_zip_1.default)(redisArchive, { dir: extractDir }); }); } /** * Extract a .msi archive * @param redisArchive Archive location * @param extractDir Directory to extract to */ extractMsi(redisArchive, extractDir) { return __awaiter(this, void 0, void 0, function* () { yield (0, util_1.promisify)(child_process_1.execFile)('msiexec', ['/quiet', '/a', redisArchive, `TARGETDIR=${extractDir}`]); }); } /** * Downlaod given httpOptions to tempDownloadLocation, then move it to downloadLocation * @param httpOptions The httpOptions directly passed to https.get * @param downloadLocation The location the File should be after the download * @param tempDownloadLocation The location the File should be while downloading */ httpDownload(httpOptions, downloadLocation, tempDownloadLocation) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { const fileStream = fs_1.default.createWriteStream(tempDownloadLocation); log(`trying to download ${httpOptions.protocol}//${httpOptions.hostname}${httpOptions.path}`); (httpOptions.protocol === 'https:' ? https_1.default : http_1.default) .get(httpOptions, (response) => { // "as any" because otherwise the "agent" wouldnt match if (response.statusCode != 200) { if (response.statusCode === 404) { reject(new Error('Status Code is 404\n' + "This means that the requested version doesn't exist\n" + ` Used Url: "${httpOptions.protocol}//${httpOptions.hostname}${httpOptions.path}"\n` + "Try to use different version 'new RedisMemoryServer({ binary: { version: 'X.Y.Z' } })'\n")); return; } reject(new Error('Status Code isnt 200!')); return; } if (typeof response.headers['content-length'] != 'string') { reject(new Error('Response header "content-length" is empty!')); return; } this.dlProgress.current = 0; this.dlProgress.length = parseInt(response.headers['content-length'], 10); this.dlProgress.totalMb = Math.round((this.dlProgress.length / 1048576) * 10) / 10; response.pipe(fileStream); fileStream.on('finish', () => __awaiter(this, void 0, void 0, function* () { if (this.dlProgress.current < this.dlProgress.length) { const downloadUrl = this._downloadingUrl || `${httpOptions.protocol}//${httpOptions.hostname}/${httpOptions.path}`; reject(new Error(`Too small (${this.dlProgress.current} bytes) redis-server binary downloaded from ${downloadUrl}`)); return; } fileStream.close(); yield (0, util_1.promisify)(fs_1.default.rename)(tempDownloadLocation, downloadLocation); log(`moved ${tempDownloadLocation} to ${downloadLocation}`); resolve(downloadLocation); })); response.on('data', (chunk) => { this.printDownloadProgress(chunk); }); }) .on('error', (e) => { // log it without having debug enabled console.error(`Couldnt download ${httpOptions.path}!`, e.message); reject(e); }); }); }); } /** * Print the Download Progress to STDOUT * @param chunk A chunk to get the length */ printDownloadProgress(chunk) { this.dlProgress.current += chunk.length; const now = Date.now(); if (now - this.dlProgress.lastPrintedAt < 2000) { return; } this.dlProgress.lastPrintedAt = now; const percentComplete = Math.round(((100.0 * this.dlProgress.current) / this.dlProgress.length) * 10) / 10; const mbComplete = Math.round((this.dlProgress.current / 1048576) * 10) / 10; const crReturn = '\r'; const message = `Downloading Redis ${this.version}: ${percentComplete} % (${mbComplete}mb / ${this.dlProgress.totalMb}mb)${crReturn}`; if (process.stdout.isTTY) { // if TTY overwrite last line over and over until finished process.stdout.write(message); } else { console.log(message); } } /** * Make and install given extracted directory * @param extractDir Extracted directory location * @returns void */ makeInstall(extractDir) { return __awaiter(this, void 0, void 0, function* () { const binaryName = 'redis-server'; log(`makeInstall(): ${extractDir}`); const makeArgs = [ // https://github.com/redis/redis/issues/12759 'JEMALLOC_CONFIGURE_OPTS=--with-lg-vaddr=48', ]; yield (0, util_1.promisify)(child_process_1.exec)(`make${makeArgs.map((arg) => ` ${arg}`).join('')}`, { cwd: extractDir, }); yield (0, util_1.promisify)(fs_1.default.copyFile)(path_1.default.resolve(extractDir, 'src', binaryName), path_1.default.resolve(extractDir, '..', binaryName)); yield (0, rimraf_1.rimraf)(extractDir); }); } /** * copy binary to parent folder and delete given extracted directory * @param extractDir Extracted directory location * @returns void */ makeInstallWin32(extractDir) { return __awaiter(this, void 0, void 0, function* () { log(`makeInstallWin32(): ${extractDir}`); yield new Promise((resolve, reject) => { fs_1.default.cp(path_1.default.resolve(extractDir, 'Memurai'), path_1.default.resolve(extractDir, '..'), { recursive: true }, (err) => (err ? reject(err) : resolve())); }); yield (0, rimraf_1.rimraf)(extractDir); }); } /** * Test if the location given is already used * Does *not* dereference links * @param location The Path to test */ locationExists(location) { return __awaiter(this, void 0, void 0, function* () { try { yield (0, util_1.promisify)(fs_1.default.lstat)(location); return true; } catch (e) { if (e.code !== 'ENOENT') { throw e; } return false; } }); } } exports.default = RedisBinaryDownload; //# sourceMappingURL=RedisBinaryDownload.js.map