Skip to content

Commit 808cc7e

Browse files
committed
feat(DryMongoBinary::generateOptions): parse options from ARCHIVE_NAME or DOWNLOAD_URL
fixes #528
1 parent fb02eac commit 808cc7e

File tree

3 files changed

+298
-12
lines changed

3 files changed

+298
-12
lines changed

packages/mongodb-memory-server-core/src/util/DryMongoBinary.ts

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import debug from 'debug';
22
import { envToBool, resolveConfig, ResolveConfigVariables } from './resolveConfig';
3-
import { checkBinaryPermissions, isNullOrUndefined, pathExists } from './utils';
3+
import { assertion, checkBinaryPermissions, isNullOrUndefined, pathExists } from './utils';
44
import * as path from 'path';
55
import { arch, homedir, platform } from 'os';
66
import findCacheDir from 'find-cache-dir';
7-
import getOS, { AnyOS, isLinuxOS } from './getos';
8-
import { NoSystemBinaryFoundError } from './errors';
7+
import getOS, { AnyOS, isLinuxOS, OtherOS } from './getos';
8+
import { NoRegexMatchError, NoSystemBinaryFoundError, ParseArchiveRegexError } from './errors';
99

1010
const log = debug('MongoMS:DryMongoBinary');
1111

@@ -34,6 +34,16 @@ export interface DryMongoBinaryPaths {
3434
relative: string;
3535
}
3636

37+
/**
38+
* Interface for "DryMongoBinary.parseArchiveNameRegex"'s regex groups
39+
*/
40+
export interface DryMongoBinaryArchiveRegexGroups {
41+
platform?: string;
42+
arch?: string;
43+
dist?: string;
44+
version?: string;
45+
}
46+
3747
/**
3848
* Locate an Binary, without downloading / locking
3949
*/
@@ -117,9 +127,83 @@ export class DryMongoBinary {
117127

118128
final.downloadDir = path.dirname((await this.generateDownloadPath(final))[1]);
119129

130+
// if truthy
131+
if (
132+
resolveConfig(ResolveConfigVariables.ARCHIVE_NAME) ||
133+
resolveConfig(ResolveConfigVariables.DOWNLOAD_URL)
134+
) {
135+
// "DOWNLOAD_URL" will be used over "ARCHIVE_NAME"
136+
// the "as string" cast is there because it is already checked that one of the 2 exists, and "resolveConfig" ensures it only returns strings
137+
const input = (resolveConfig(ResolveConfigVariables.DOWNLOAD_URL) ||
138+
resolveConfig(ResolveConfigVariables.ARCHIVE_NAME)) as string;
139+
140+
log(
141+
`generateOptions: ARCHIVE_NAME or DOWNLOAD_URL defined, generating options based on that (input: "${input}")`
142+
);
143+
144+
return this.parseArchiveNameRegex(input, final);
145+
}
146+
120147
return final;
121148
}
122149

150+
/**
151+
* Parse "input" into DryMongoBinaryOptions
152+
* @param input The Input to be parsed with the regex
153+
* @param opts The Options which will be augmented with "input"
154+
* @returns The Augmented options
155+
*/
156+
static parseArchiveNameRegex(
157+
input: string,
158+
opts: Required<DryMongoBinaryOptions>
159+
): Required<DryMongoBinaryOptions> {
160+
log(`parseArchiveNameRegex (input: "${input}")`);
161+
162+
const archiveMatches =
163+
/mongodb-(?<platform>linux|win32|osx)(?:-ssl-|-)(?<arch>\w{4,})(?:-(?<dist>\w+)|)(?:-ssl-|-)(?:v|)(?<version>[\d.]+(?:-latest|))\./gim.exec(
164+
input
165+
);
166+
167+
assertion(!isNullOrUndefined(archiveMatches), new NoRegexMatchError('input'));
168+
169+
// this error is kinda impossible to test, because the regex we use either has matches that are groups or no matches
170+
assertion(!isNullOrUndefined(archiveMatches.groups), new NoRegexMatchError('input', 'groups'));
171+
172+
const groups: DryMongoBinaryArchiveRegexGroups = archiveMatches.groups;
173+
174+
assertion(
175+
typeof groups.version === 'string' && groups.version.length > 1,
176+
new ParseArchiveRegexError('version')
177+
);
178+
// the following 2 assertions are hard to test, because the regex has restrictions that are more strict than the assertions
179+
assertion(
180+
typeof groups.platform === 'string' && groups.platform.length > 1,
181+
new ParseArchiveRegexError('platform')
182+
);
183+
assertion(
184+
typeof groups.arch === 'string' && groups.arch.length >= 4,
185+
new ParseArchiveRegexError('arch')
186+
);
187+
188+
opts.version = groups.version;
189+
opts.arch = groups.arch;
190+
191+
if (groups.platform === 'linux') {
192+
const distMatches = !!groups.dist ? /([a-z]+)(\d*)/gim.exec(groups.dist) : null;
193+
194+
opts.os = {
195+
os: 'linux',
196+
dist: typeof distMatches?.[1] === 'string' ? distMatches[1] : 'unknown',
197+
// "release" should be able to be discarded in this case
198+
release: '',
199+
};
200+
} else {
201+
opts.os = { os: groups.platform } as OtherOS;
202+
}
203+
204+
return opts;
205+
}
206+
123207
/**
124208
* Get the full path with filename
125209
* @return Absoulte Path with FileName
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`DryBinary locateBinary should throw an error if SystemBinary was provided, but not found 1`] = `"{\\"binaryPath\\":\\"/usr/local/bin/mongod\\"}"`;
4+
5+
exports[`DryBinary parseArchiveNameRegex should throw a Error when no "version" is found [ParseArchiveRegexError] 1`] = `"Expected \\"version\\" to be found in regex groups"`;
6+
7+
exports[`DryBinary parseArchiveNameRegex should throw a Error when no matches are found [NoRegexMatchError] 1`] = `"Expected \\"input\\" to have Regex Matches"`;

packages/mongodb-memory-server-core/src/util/__tests__/dryBinary.test.ts

Lines changed: 207 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { envName, ResolveConfigVariables } from '../resolveConfig';
66
import * as utils from '../utils';
77
import mkdirp from 'mkdirp';
88
import { LinuxOS, OtherOS } from '../getos';
9-
import { NoSystemBinaryFoundError } from '../errors';
9+
import { NoRegexMatchError, NoSystemBinaryFoundError, ParseArchiveRegexError } from '../errors';
10+
import { assertIsError } from '../../__tests__/testUtils/test_utils';
1011

1112
tmp.setGracefulCleanup();
1213

@@ -402,7 +403,14 @@ describe('DryBinary', () => {
402403
let osmock: LinuxOS;
403404
const mockBinary: string = '/custom/path';
404405

405-
beforeAll(() => {
406+
beforeEach(() => {
407+
delete process.env[envName(ResolveConfigVariables.DOWNLOAD_DIR)];
408+
delete process.env[envName(ResolveConfigVariables.SYSTEM_BINARY)];
409+
delete process.env[envName(ResolveConfigVariables.VERSION)];
410+
delete process.env[envName(ResolveConfigVariables.ARCHIVE_NAME)];
411+
delete process.env[envName(ResolveConfigVariables.DOWNLOAD_URL)];
412+
process.env['INIT_CWD'] = undefined; // removing this, because it would mess with stuff - but still should be used when available (postinstall)
413+
406414
jest.spyOn(binary.DryMongoBinary, 'generateDownloadPath').mockImplementation(async (opts) => {
407415
return [true, opts.downloadDir ? opts.downloadDir : mockBinary];
408416
});
@@ -412,14 +420,8 @@ describe('DryBinary', () => {
412420
release: '20.04',
413421
};
414422
});
415-
beforeEach(() => {
416-
delete process.env[envName(ResolveConfigVariables.DOWNLOAD_DIR)];
417-
delete process.env[envName(ResolveConfigVariables.SYSTEM_BINARY)];
418-
delete process.env[envName(ResolveConfigVariables.VERSION)];
419-
process.env['INIT_CWD'] = undefined; // removing this, because it would mess with stuff - but still should be used when available (postinstall)
420-
});
421423

422-
afterAll(() => {
424+
afterEach(() => {
423425
jest.restoreAllMocks();
424426
});
425427

@@ -483,5 +485,201 @@ describe('DryBinary', () => {
483485
downloadDir: path.dirname(envdldir),
484486
});
485487
});
488+
489+
it('should use "parseArchiveNameRegex" when DOWNLOAD_URL is defined', async () => {
490+
const parseArchiveNameRegexSpy = jest.spyOn(binary.DryMongoBinary, 'parseArchiveNameRegex');
491+
const envURL = 'https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.0.25.tgz';
492+
process.env[envName(ResolveConfigVariables.DOWNLOAD_URL)] = envURL;
493+
const customos: OtherOS = {
494+
os: 'win32',
495+
};
496+
const origOptions: Required<binary.DryMongoBinaryOptions> = {
497+
version: '4.4.4',
498+
arch: 'arm64',
499+
os: customos,
500+
downloadDir: '/path/to/somewhere',
501+
systemBinary: '',
502+
};
503+
504+
const output = await binary.DryMongoBinary.generateOptions(origOptions);
505+
506+
expect(output).toStrictEqual<binary.DryMongoBinaryOptions>({
507+
version: '4.0.25',
508+
arch: 'x86_64',
509+
downloadDir: path.dirname(origOptions.downloadDir),
510+
systemBinary: '',
511+
os: {
512+
os: 'linux',
513+
dist: 'ubuntu',
514+
release: '',
515+
},
516+
});
517+
518+
expect(parseArchiveNameRegexSpy).toHaveBeenCalledTimes(1);
519+
});
520+
521+
it('should use "parseArchiveNameRegex" when ARCHIVE_NAME is defined', async () => {
522+
const parseArchiveNameRegexSpy = jest.spyOn(binary.DryMongoBinary, 'parseArchiveNameRegex');
523+
const envARCHIVE = 'mongodb-linux-x86_64-ubuntu1604-4.0.24.tgz';
524+
process.env[envName(ResolveConfigVariables.ARCHIVE_NAME)] = envARCHIVE;
525+
const customos: OtherOS = {
526+
os: 'win32',
527+
};
528+
const origOptions: Required<binary.DryMongoBinaryOptions> = {
529+
version: '4.4.4',
530+
arch: 'arm64',
531+
os: customos,
532+
downloadDir: '/path/to/somewhere',
533+
systemBinary: '',
534+
};
535+
536+
const output = await binary.DryMongoBinary.generateOptions(origOptions);
537+
538+
expect(output).toStrictEqual<binary.DryMongoBinaryOptions>({
539+
version: '4.0.24',
540+
arch: 'x86_64',
541+
downloadDir: path.dirname(origOptions.downloadDir),
542+
systemBinary: '',
543+
os: {
544+
os: 'linux',
545+
dist: 'ubuntu',
546+
release: '',
547+
},
548+
});
549+
550+
expect(parseArchiveNameRegexSpy).toHaveBeenCalledTimes(1);
551+
});
552+
});
553+
554+
describe('parseArchiveNameRegex', () => {
555+
it('should parse and overwrite input options LINUX-UBUNTU', async () => {
556+
const input = 'https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.0.25.tgz';
557+
// The following options are made different to check that the function actually changes them
558+
const customos: OtherOS = {
559+
os: 'win32',
560+
};
561+
const origOptions: Required<binary.DryMongoBinaryOptions> = {
562+
version: '4.4.4',
563+
arch: 'arm64',
564+
os: customos,
565+
downloadDir: '/path/to/somewhere',
566+
systemBinary: '',
567+
};
568+
569+
const output = binary.DryMongoBinary.parseArchiveNameRegex(input, origOptions);
570+
571+
expect(output).toStrictEqual<binary.DryMongoBinaryOptions>({
572+
version: '4.0.25',
573+
arch: 'x86_64',
574+
downloadDir: origOptions.downloadDir,
575+
systemBinary: '',
576+
os: {
577+
os: 'linux',
578+
dist: 'ubuntu',
579+
release: '',
580+
},
581+
});
582+
});
583+
584+
it('should parse and overwrite input options MACOS', async () => {
585+
const input = 'http://downloads.mongodb.org/osx/mongodb-osx-ssl-x86_64-4.0.25.tgz';
586+
// The following options are made different to check that the function actually changes them
587+
const customos: OtherOS = {
588+
os: 'win32',
589+
};
590+
const origOptions: Required<binary.DryMongoBinaryOptions> = {
591+
version: '4.4.4',
592+
arch: 'arm64',
593+
os: customos,
594+
downloadDir: '/path/to/somewhere',
595+
systemBinary: '',
596+
};
597+
598+
const output = binary.DryMongoBinary.parseArchiveNameRegex(input, origOptions);
599+
600+
expect(output).toStrictEqual<binary.DryMongoBinaryOptions>({
601+
version: '4.0.25',
602+
arch: 'x86_64',
603+
downloadDir: origOptions.downloadDir,
604+
systemBinary: '',
605+
os: {
606+
os: 'osx',
607+
},
608+
});
609+
});
610+
611+
it('should parse and overwrite input options WINDOWS', async () => {
612+
const input =
613+
'https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-4.0.25.zip';
614+
// The following options are made different to check that the function actually changes them
615+
const customos: OtherOS = {
616+
os: 'osx',
617+
};
618+
const origOptions: Required<binary.DryMongoBinaryOptions> = {
619+
version: '4.4.4',
620+
arch: 'arm64',
621+
os: customos,
622+
downloadDir: '/path/to/somewhere',
623+
systemBinary: '',
624+
};
625+
626+
const output = binary.DryMongoBinary.parseArchiveNameRegex(input, origOptions);
627+
628+
expect(output).toStrictEqual<binary.DryMongoBinaryOptions>({
629+
version: '4.0.25',
630+
arch: 'x86_64',
631+
downloadDir: origOptions.downloadDir,
632+
systemBinary: '',
633+
os: {
634+
os: 'win32',
635+
},
636+
});
637+
});
638+
639+
it('should throw a Error when no matches are found [NoRegexMatchError]', async () => {
640+
const customos: OtherOS = {
641+
os: 'win32',
642+
};
643+
const origOptions: Required<binary.DryMongoBinaryOptions> = {
644+
version: '4.4.4',
645+
arch: 'arm64',
646+
os: customos,
647+
downloadDir: '/path/to/somewhere',
648+
systemBinary: '',
649+
};
650+
651+
try {
652+
binary.DryMongoBinary.parseArchiveNameRegex('', origOptions);
653+
654+
fail('Expected generateOptions to throw "NoRegexMatchError"');
655+
} catch (err) {
656+
expect(err).toBeInstanceOf(NoRegexMatchError);
657+
assertIsError(err);
658+
expect(err.message).toMatchSnapshot();
659+
}
660+
});
661+
662+
it('should throw a Error when no "version" is found [ParseArchiveRegexError]', async () => {
663+
const customos: OtherOS = {
664+
os: 'win32',
665+
};
666+
const origOptions: Required<binary.DryMongoBinaryOptions> = {
667+
version: '4.4.4',
668+
arch: 'arm64',
669+
os: customos,
670+
downloadDir: '/path/to/somewhere',
671+
systemBinary: '',
672+
};
673+
674+
try {
675+
binary.DryMongoBinary.parseArchiveNameRegex('mongodb-linux-x86_64-u-4.tgz', origOptions);
676+
677+
fail('Expected generateOptions to throw "ParseArchiveRegexError"');
678+
} catch (err) {
679+
expect(err).toBeInstanceOf(ParseArchiveRegexError);
680+
assertIsError(err);
681+
expect(err.message).toMatchSnapshot();
682+
}
683+
});
486684
});
487685
});

0 commit comments

Comments
 (0)