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
JavaScript
;
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