scripts: Download Linux binaries on Windows.

This also includes refactoring so the download bits don't just grab
functions out of a random script.

Signed-off-by: Mark Yen <mark.yen@suse.com>
This commit is contained in:
Mark Yen
2021-07-09 14:35:43 -07:00
parent a1b21eaf0d
commit fa871b9962
8 changed files with 391 additions and 344 deletions

1
.gitignore vendored
View File

@@ -1,6 +1,7 @@
/node_modules/
.DS_Store
/resources/darwin/
/resources/linux/
/resources/win32/
/coverage/
/dist/**

View File

@@ -1,334 +0,0 @@
import childProcess from 'child_process';
import crypto from 'crypto';
import fs from 'fs';
import os from 'os';
import path from 'path';
import fetch from 'node-fetch';
/**
* Execute a process and wait for it to finish.
* @param command {readonly string} The executable to run.
* @param args {readonly string[]} Arguments to the executable.
*/
function spawnSync(command, ...args) {
/** @type {childProcess.SpawnOptions} */
const options = { stdio: 'inherit', windowsHide: true };
const { status, signal, error } = childProcess.spawnSync(command, args, options);
if (error) {
throw error;
}
if (signal !== null && signal !== 'SIGTERM') {
throw new Error(`${ command } exited with signal ${ signal }`);
}
if (status !== null && status !== 0) {
throw new Error(`${ command } exited with status ${ status }`);
}
}
/** The platform string, as used by golang / Kubernetes. */
const kubePlatform = {
darwin: 'darwin',
linux: 'linux',
win32: 'windows',
}[os.platform()];
const resourcesDir = path.join(process.cwd(), 'resources', os.platform());
const binDir = path.join(resourcesDir, 'bin');
const onWindows = kubePlatform === 'windows';
function exeName(name) {
return `${ name }${ onWindows ? '.exe' : '' }`;
}
async function getChecksumForFile(inputPath, checksumAlgorithm = 'sha256') {
const hash = crypto.createHash(checksumAlgorithm);
await new Promise((resolve) => {
hash.on('finish', resolve);
fs.createReadStream(inputPath).pipe(hash);
});
return hash.digest('hex');
}
/**
* @typedef DownloadOptions Object
* @prop {string} [expectedChecksum] The expected checksum for the file.
* @prop {string} [checksumAlgorithm="sha256"] Checksum algorithm.
* @prop {boolean} [overwrite=false] Whether to re-download files that already exist.
* @prop {number} [access=fs.constants.X_OK] The file mode required.
*/
/**
* Download the given URL, making the result executable
* @param url {string} The URL to download
* @param destPath {string} The path to download to
* @param options {DownloadOptions} Additional options for the download.
*/
export async function download(url, destPath, options = {}) {
const { expectedChecksum, overwrite } = options;
const checksumAlgorithm = options.checksumAlgorithm ?? 'sha256';
const access = options.access ?? fs.constants.X_OK;
if (!overwrite) {
try {
await fs.promises.access(destPath, access);
console.log(`${ destPath } already exists, not re-downloading.`);
return;
} catch (ex) {
if (ex.code !== 'ENOENT') {
throw ex;
}
}
}
console.log(`Downloading ${ url } to ${ destPath }...`);
await fs.promises.mkdir(path.dirname(destPath), { recursive: true });
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Error downloading ${ url }: ${ response.statusText }`);
}
const tempPath = `${ destPath }.download`;
try {
const file = fs.createWriteStream(tempPath);
const promise = new Promise(resolve => file.on('finish', resolve));
response.body.pipe(file);
await promise;
if (expectedChecksum) {
const actualChecksum = await getChecksumForFile(tempPath, checksumAlgorithm);
if (actualChecksum !== expectedChecksum) {
throw new Error(`Expecting URL ${ url } to have ${ checksumAlgorithm } [${ expectedChecksum }], got [${ actualChecksum }]`);
}
}
const mode =
(access & fs.constants.X_OK) ? 0o755 : (access & fs.constants.W_OK) ? 0o644 : 0o444;
await fs.promises.chmod(tempPath, mode);
await fs.promises.rename(tempPath, destPath);
} finally {
try {
await fs.promises.unlink(tempPath);
} catch (ex) {
if (ex.code !== 'ENOENT') {
console.error(ex);
}
}
}
}
/**
* Download a tar.gz file to a temp dir, expand,
* and move the expected binary to the final dir
*
* @param url {string} The URL to download.
* @param expectedChecksum {string} The URL's hash URL; empty string turns off sha checking.
* @param binaryBasename {string} The base name of the executable to find.
* @param platformDir {string} The platform-specific part of the path that holds the expanded executable.
* @returns {Promise<string>} The full path of the final binary if successful, '' otherwise.
*/
async function downloadTarGZ(url, expectedChecksum, binaryBasename, platformDir) {
const workDir = fs.mkdtempSync(path.join(os.tmpdir(), `rd-${ binaryBasename }-`));
let binaryFinalPath = '';
const fileToExtract = path.join(platformDir, exeName(binaryBasename));
try {
const tgzPath = path.join(workDir, `${ binaryBasename }.tar.gz`);
const args = ['tar', '-zxvf', tgzPath, '--directory', workDir, fileToExtract.replace(/\\/g, '/')];
await download(url, tgzPath, { expectedChecksum, access: fs.constants.W_OK });
if (onWindows) {
// On Windows, force use the bundled bsdtar.
// We may find GNU tar on the path, which looks at the Windows-style path
// and considers C:\Temp to be a reference to a remote host named `C`.
args[0] = path.join(process.env.SystemRoot, 'system32', 'tar.exe');
}
spawnSync(...args);
binaryFinalPath = path.join(binDir, exeName(binaryBasename));
fs.copyFileSync(path.join(workDir, fileToExtract), binaryFinalPath);
fs.chmodSync(binaryFinalPath, 0o755);
} finally {
console.log('finishing...');
fs.rmSync(workDir, { recursive: true, maxRetries: 10 });
}
return binaryFinalPath;
}
/**
* Download a zip to a temp dir, expand,
* and move the expected binary to the final dir
*
* @param url {string} The URL to download.
* @param expectedChecksum {string} The URL's hash URL; empty string turns off sha checking.
* @param binaryBasename {string} The base name of the executable to find.
* @param platformDir {string} The platform-specific part of the path that holds the expanded executable.
* @returns {Promise<string>} The full path of the final binary if successful, '' otherwise.
*/
async function downloadZip(url, expectedChecksum, binaryBasename, platformDir) {
const zipDir = fs.mkdtempSync(path.join(os.tmpdir(), `rd-${ binaryBasename }-`));
let binaryFinalPath = '';
const fileToExtract = path.join(platformDir, exeName(binaryBasename));
try {
const zipPath = path.join(zipDir, `${ binaryBasename }.zip`);
const args = ['unzip', '-o', zipPath, fileToExtract.replace(/\\/g, '/'), '-d', zipDir];
await download(url, zipPath, { expectedChecksum, access: fs.constants.W_OK });
spawnSync(...args);
binaryFinalPath = path.join(binDir, exeName(binaryBasename));
fs.copyFileSync(path.join(zipDir, fileToExtract), binaryFinalPath);
fs.chmodSync(binaryFinalPath, 0o755);
} finally {
console.log('finishing...');
fs.rmSync(zipDir, { recursive: true, maxRetries: 10 });
}
return binaryFinalPath;
}
export async function getResource(url) {
return await (await fetch(url)).text();
}
/**
* Find the home directory, in a way that is compatible with
* kuberlr
*/
async function findHome() {
const tryAccess = async(path) => {
try {
await fs.promises.access(path);
return true;
} catch {
return false;
}
};
const osHomeDir = os.homedir();
if (osHomeDir && await tryAccess(osHomeDir)) {
return osHomeDir;
}
if (process.env.HOME && await tryAccess(process.env.HOME)) {
return process.env.HOME;
}
if (onWindows) {
if (process.env.USERPROFILE && await tryAccess(process.env.USERPROFILE)) {
return process.env.USERPROFILE;
}
if (process.env.HOMEDRIVE && process.env.HOMEPATH) {
const homePath = path.join(process.env.HOMEDRIVE, process.env.HOMEPATH);
if (await tryAccess(homePath)) {
return homePath;
}
}
}
return null;
}
async function downloadKuberlr(kuberlrBaseURL, finalKuberlrSHA, kuberlrPlatformDir, onWindows) {
if (onWindows) {
return await downloadZip(`${ kuberlrBaseURL }.zip`, finalKuberlrSHA, 'kuberlr', kuberlrPlatformDir);
}
return await downloadTarGZ(`${ kuberlrBaseURL }.tar.gz`, finalKuberlrSHA, 'kuberlr', kuberlrPlatformDir);
}
export default async function main() {
fs.mkdirSync(binDir, { recursive: true });
const kuberlrVersion = '0.3.2';
const kuberlrBase = `https://github.com/flavio/kuberlr/releases/download/v${ kuberlrVersion }`;
const kuberlrBaseURL = `${ kuberlrBase }/kuberlr_${ kuberlrVersion }_${ kubePlatform }_amd64`;
const kuberlrPlatformDir = `kuberlr_${ kuberlrVersion }_${ kubePlatform }_amd64`;
const allKuberlrSHAs = await getResource(`${ kuberlrBase }/checksums.txt`);
const kuberlrSHA = allKuberlrSHAs.split(/\r?\n/).filter(line => line.includes(`kuberlr_${ kuberlrVersion }_${ kubePlatform }_amd64`));
switch (kuberlrSHA.length) {
case 0:
throw new Error(`Couldn't find a matching SHA for [kuberlr_${ kuberlrVersion }_${ kubePlatform }-amd64] in [${ allKuberlrSHAs }]`);
case 1:
break;
default:
throw new Error(`Matched ${ kuberlrSHA.length } hits, not exactly 1, for platform ${ kubePlatform } in [${ allKuberlrSHAs }]`);
}
const finalKuberlrSHA = kuberlrSHA[0].split(/\s+/, 1)[0];
const kuberlrPath = await downloadKuberlr(kuberlrBaseURL, finalKuberlrSHA, kuberlrPlatformDir, onWindows);
// Download Kubectl into kuberlr's directory of versioned kubectl's
const kubeVersion = (await getResource('https://dl.k8s.io/release/stable.txt')).trim();
const kubectlURL = `https://dl.k8s.io/${ kubeVersion }/bin/${ kubePlatform }/amd64/${ exeName('kubectl') }`;
const kubectlSHA = await getResource(`${ kubectlURL }.sha256`);
const kuberlrDir = path.join(await findHome(), '.kuberlr', `${ kubePlatform }-amd64`);
const managedKubectlPath = path.join(kuberlrDir, exeName(`kubectl${ kubeVersion.replace(/^v/, '') }`));
await download(kubectlURL, managedKubectlPath, { expectedChecksum: kubectlSHA });
await bindKubectlToKuberlr(kuberlrPath);
// Download Helm. It is a tar.gz file that needs to be expanded and file moved.
const helmVersion = '3.6.1';
const helmURL = `https://get.helm.sh/helm-v${ helmVersion }-${ kubePlatform }-amd64.tar.gz`;
const helmSHA = (await getResource(`${ helmURL }.sha256sum`)).split(/\s+/, 1)[0];
await downloadTarGZ(helmURL, helmSHA, 'helm', `${ kubePlatform }-amd64`);
// Download Kim
const kimVersion = '0.1.0-beta.2';
const kimURLBase = `https://github.com/rancher/kim/releases/download/v${ kimVersion }`;
const kimURL = `${ kimURLBase }/${ exeName(`kim-${ kubePlatform }-amd64`) }`;
const kimPath = path.join(binDir, exeName( 'kim'));
const allKimSHAs = await getResource(`${ kimURLBase }/sha256sum.txt`);
const kimSHA = allKimSHAs.split(/\r?\n/).filter(line => line.includes(`kim-${ kubePlatform }-amd64`));
switch (kimSHA.length) {
case 0:
throw new Error(`Couldn't find a matching SHA for [kim-${ kubePlatform }-amd64] in [${ allKimSHAs }]`);
case 1:
break;
default:
throw new Error(`Matched ${ kimSHA.length } hits, not exactly 1, for platform ${ kubePlatform } in [${ allKimSHAs }]`);
}
await download(kimURL, kimPath, { expectedChecksum: kimSHA[0].split(/\s+/, 1)[0] });
}
/**
* Desired: on Windows, .../bin/kubectl.exe is a copy of .../bin/kuberlr.exe
* elsewhere: .../bin/kubectl is a symlink to .../bin/kuberlr
* @param kuberlrPath {string}
* @returns {Promise<void>}
*/
async function bindKubectlToKuberlr(kuberlrPath) {
const binKubectlPath = path.join(binDir, exeName('kubectl'));
if (onWindows) {
await fs.promises.copyFile(kuberlrPath, binKubectlPath);
return;
}
try {
const binKubectlStat = await fs.promises.lstat(binKubectlPath);
if (binKubectlStat.isSymbolicLink()) {
const actualTarget = await fs.promises.readlink(binKubectlPath);
if (actualTarget === 'kuberlr') {
// The link is already there
return;
} else {
console.log(`Deleting symlink ${ binKubectlPath } unexpectedly pointing to ${ actualTarget }`);
}
}
await fs.promises.rm(binKubectlPath);
} catch (_) {
// .../bin/kubectl doesn't exist, so there's nothing to clean up
}
await fs.promises.symlink('kuberlr', binKubectlPath);
}

View File

@@ -7,7 +7,7 @@ import childProcess from 'child_process';
import path from 'path';
import util from 'util';
import { download } from '../download-resources.mjs';
import { download } from '../lib/download.mjs';
// The version of hyperkit to build
const ver = 'v0.20210107';

View File

@@ -4,7 +4,7 @@ import childProcess from 'child_process';
import fs from 'fs';
import os from 'os';
import path from 'path';
import { download, getResource } from '../download-resources.mjs';
import { download, getResource } from '../lib/download.mjs';
const limaRepo = 'https://github.com/rancher-sandbox/lima';
const limaTag = 'v0.5.0';

170
scripts/download/tools.mjs Normal file
View File

@@ -0,0 +1,170 @@
import childProcess from 'child_process';
import fs from 'fs';
import os from 'os';
import path from 'path';
import { download, downloadZip, downloadTarGZ, getResource } from '../lib/download.mjs';
/**
* Find the home directory, in a way that is compatible with kuberlr
*
* @param {boolean} [onWindows] Whether we're running on Windows
*/
async function findHome(onWindows) {
const tryAccess = async(path) => {
try {
await fs.promises.access(path);
return true;
} catch {
return false;
}
};
const osHomeDir = os.homedir();
if (osHomeDir && await tryAccess(osHomeDir)) {
return osHomeDir;
}
if (process.env.HOME && await tryAccess(process.env.HOME)) {
return process.env.HOME;
}
if (onWindows) {
if (process.env.USERPROFILE && await tryAccess(process.env.USERPROFILE)) {
return process.env.USERPROFILE;
}
if (process.env.HOMEDRIVE && process.env.HOMEPATH) {
const homePath = path.join(process.env.HOMEDRIVE, process.env.HOMEPATH);
if (await tryAccess(homePath)) {
return homePath;
}
}
}
return null;
}
async function downloadKuberlr(kubePlatform, destDir) {
const kuberlrVersion = '0.3.2';
const baseURL = `https://github.com/flavio/kuberlr/releases/download/v${ kuberlrVersion }`;
const platformDir = `kuberlr_${ kuberlrVersion }_${ kubePlatform }_amd64`;
const archiveName = platformDir + (kubePlatform.startsWith('win') ? '.zip' : '.tar.gz');
const exeName = kubePlatform.startsWith('win') ? 'kuberlr.exe' : 'kuberlr';
const allChecksums = (await getResource(`${ baseURL }/checksums.txt`)).split(/\r?\n/);
const checksums = allChecksums.filter(line => line.includes(platformDir));
switch (checksums.length) {
case 0:
throw new Error(`Couldn't find a matching SHA for [${ platformDir }] in [${ allChecksums }]`);
case 1:
break;
default:
throw new Error(`Matched ${ checksums.length } hits, not exactly 1, for platform ${ kubePlatform } in [${ allChecksums }]`);
}
/** @type import('../lib/download.mjs').ArchiveDownloadOptions */
const options = {
expectedChecksum: checksums[0].split(/\s+/)[0],
entryName: `${ platformDir }/${ exeName }`,
};
if (kubePlatform.startsWith('win')) {
return await downloadZip(`${ baseURL }/${ archiveName }`, path.join(destDir, exeName), options);
}
return await downloadTarGZ(`${ baseURL }/${ archiveName }`, path.join(destDir, exeName), options);
}
export default async function main(platform) {
/** The platform string, as used by golang / Kubernetes. */
const kubePlatform = {
darwin: 'darwin',
linux: 'linux',
win32: 'windows',
}[platform];
const resourcesDir = path.join(process.cwd(), 'resources', platform);
const binDir = path.join(resourcesDir, 'bin');
const onWindows = kubePlatform === 'windows';
function exeName(name) {
return `${ name }${ onWindows ? '.exe' : '' }`;
}
fs.mkdirSync(binDir, { recursive: true });
const kuberlrPath = await downloadKuberlr(kubePlatform, binDir);
await bindKubectlToKuberlr(kuberlrPath, path.join(binDir, exeName('kubectl')));
// Download Kubectl into kuberlr's directory of versioned kubectl's
if (platform === os.platform()) {
const kubeVersion = (await getResource('https://dl.k8s.io/release/stable.txt')).trim();
const kubectlURL = `https://dl.k8s.io/${ kubeVersion }/bin/${ kubePlatform }/amd64/${ exeName('kubectl') }`;
const kubectlSHA = await getResource(`${ kubectlURL }.sha256`);
const kuberlrDir = path.join(await findHome(onWindows), '.kuberlr', `${ kubePlatform }-amd64`);
const managedKubectlPath = path.join(kuberlrDir, exeName(`kubectl${ kubeVersion.replace(/^v/, '') }`));
await download(kubectlURL, managedKubectlPath, { expectedChecksum: kubectlSHA });
}
// Download Helm. It is a tar.gz file that needs to be expanded and file moved.
const helmVersion = '3.6.1';
const helmURL = `https://get.helm.sh/helm-v${ helmVersion }-${ kubePlatform }-amd64.tar.gz`;
await downloadTarGZ(helmURL, path.join(binDir, exeName('helm')), {
expectedChecksum: (await getResource(`${ helmURL }.sha256sum`)).split(/\s+/, 1)[0],
entryName: `${ kubePlatform }-amd64/${ exeName('helm') }`,
});
// Download Kim
const kimVersion = '0.1.0-beta.2';
const kimURLBase = `https://github.com/rancher/kim/releases/download/v${ kimVersion }`;
const kimURL = `${ kimURLBase }/${ exeName(`kim-${ kubePlatform }-amd64`) }`;
const kimPath = path.join(binDir, exeName('kim'));
const allKimSHAs = await getResource(`${ kimURLBase }/sha256sum.txt`);
const kimSHA = allKimSHAs.split(/\r?\n/).filter(line => line.includes(`kim-${ kubePlatform }-amd64`));
switch (kimSHA.length) {
case 0:
throw new Error(`Couldn't find a matching SHA for [kim-${ kubePlatform }-amd64] in [${ allKimSHAs }]`);
case 1:
break;
default:
throw new Error(`Matched ${ kimSHA.length } hits, not exactly 1, for platform ${ kubePlatform } in [${ allKimSHAs }]`);
}
await download(kimURL, kimPath, { expectedChecksum: kimSHA[0].split(/\s+/, 1)[0] });
}
/**
* Desired: on Windows, .../bin/kubectl.exe is a copy of .../bin/kuberlr.exe
* elsewhere: .../bin/kubectl is a symlink to .../bin/kuberlr
* @param kuberlrPath {string}
* @returns {Promise<void>}
*/
async function bindKubectlToKuberlr(kuberlrPath, binKubectlPath) {
if (os.platform().startsWith('win')) {
await fs.promises.copyFile(kuberlrPath, binKubectlPath);
return;
}
try {
const binKubectlStat = await fs.promises.lstat(binKubectlPath);
if (binKubectlStat.isSymbolicLink()) {
const actualTarget = await fs.promises.readlink(binKubectlPath);
if (actualTarget === 'kuberlr') {
// The link is already there
return;
} else {
console.log(`Deleting symlink ${ binKubectlPath } unexpectedly pointing to ${ actualTarget }`);
}
}
await fs.promises.rm(binKubectlPath);
} catch (_) {
// .../bin/kubectl doesn't exist, so there's nothing to clean up
}
await fs.promises.symlink('kuberlr', binKubectlPath);
}

View File

@@ -5,7 +5,7 @@ import fs from 'fs';
import os from 'os';
import path from 'path';
import { download } from '../download-resources.mjs';
import { download } from '../lib/download.mjs';
export default async function main() {
await download(

211
scripts/lib/download.mjs Normal file
View File

@@ -0,0 +1,211 @@
/**
* Helpers for downloading files.
*/
import crypto from 'crypto';
import fs from 'fs';
import os from 'os';
import path from 'path';
import { spawnSync } from 'child_process';
import fetch from 'node-fetch';
/**
* @typedef DownloadOptions Object
* @prop {string} [expectedChecksum] The expected checksum for the file.
* @prop {string} [checksumAlgorithm="sha256"] Checksum algorithm.
* @prop {boolean} [overwrite=false] Whether to re-download files that already exist.
* @prop {number} [access=fs.constants.X_OK] The file mode required.
*/
/**
* Download the given URL, making the result executable
* @param {string} [url] The URL to download
* @param {string} [destPath] The path to download to
* @param {DownloadOptions} [options] Additional options for the download.
* @returns {Promise<void>}
*/
export async function download(url, destPath, options = {}) {
const { expectedChecksum, overwrite } = options;
const checksumAlgorithm = options.checksumAlgorithm ?? 'sha256';
const access = options.access ?? fs.constants.X_OK;
if (!overwrite) {
try {
await fs.promises.access(destPath, access);
console.log(`${ destPath } already exists, not re-downloading.`);
return;
} catch (ex) {
if (ex.code !== 'ENOENT') {
throw ex;
}
}
}
console.log(`Downloading ${ url } to ${ destPath }...`);
await fs.promises.mkdir(path.dirname(destPath), { recursive: true });
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Error downloading ${ url }: ${ response.statusText }`);
}
const tempPath = `${ destPath }.download`;
try {
const file = fs.createWriteStream(tempPath);
const promise = new Promise(resolve => file.on('finish', resolve));
response.body.pipe(file);
await promise;
if (expectedChecksum) {
const actualChecksum = await getChecksumForFile(tempPath, checksumAlgorithm);
if (actualChecksum !== expectedChecksum) {
throw new Error(`Expecting URL ${ url } to have ${ checksumAlgorithm } [${ expectedChecksum }], got [${ actualChecksum }]`);
}
}
const mode =
(access & fs.constants.X_OK) ? 0o755 : (access & fs.constants.W_OK) ? 0o644 : 0o444;
await fs.promises.chmod(tempPath, mode);
await fs.promises.rename(tempPath, destPath);
} finally {
try {
await fs.promises.unlink(tempPath);
} catch (ex) {
if (ex.code !== 'ENOENT') {
console.error(ex);
}
}
}
}
/**
* Compute the checksum for a given file
* @param {string} inputPath The file to checksum.
* @param {'sha256' | 'sha1'} checksumAlgorithm The checksum algorithm to use.
* @returns {string} The hex-encoded checksum of the file.
*/
async function getChecksumForFile(inputPath, checksumAlgorithm = 'sha256') {
const hash = crypto.createHash(checksumAlgorithm);
await new Promise((resolve) => {
hash.on('finish', resolve);
fs.createReadStream(inputPath).pipe(hash);
});
return hash.digest('hex');
}
/**
* Return the contents of a given URL.
* @param {string} url The URL to download
* @returns {string} The file contents.
*/
export async function getResource(url) {
return await (await fetch(url)).text();
}
/**
* @typedef ArchiveDownloadOptions DownloadOptions
* @prop {string} [entryName] The name in the archive of the file; defaults to base name of the destination.
*/
/**
* Download a tar.gz file to a temp dir, expand,
* and move the expected binary to the final dir
*
* @param url {string} The URL to download.
* @param destPath {string} The path to download to, including the executable name.
* @param options {ArchiveDownloadOptions} Additional options for the download.
* @returns {Promise<string>} The full path of the final binary.
*/
export async function downloadTarGZ(url, destPath, options = {}) {
const { overwrite } = options;
const access = options.access ?? fs.constants.X_OK;
if (!overwrite) {
try {
await fs.promises.access(destPath, access);
console.log(`${ destPath } already exists, not re-downloading.`);
return destPath;
} catch (ex) {
if (ex.code !== 'ENOENT') {
throw ex;
}
}
}
const binaryBasename = path.basename(destPath, '.exe');
const workDir = fs.mkdtempSync(path.join(os.tmpdir(), `rd-${ binaryBasename }-`));
const fileToExtract = options.entryName || path.basename(destPath);
try {
const tgzPath = path.join(workDir, `${ binaryBasename }.tar.gz`);
const args = ['tar', '-zxvf', tgzPath, '--directory', workDir, fileToExtract];
const mode =
(access & fs.constants.X_OK) ? 0o755 : (access & fs.constants.W_OK) ? 0o644 : 0o444;
await download(url, tgzPath, { ...options, access: fs.constants.W_OK });
if (os.platform().startsWith('win')) {
// On Windows, force use the bundled bsdtar.
// We may find GNU tar on the path, which looks at the Windows-style path
// and considers C:\Temp to be a reference to a remote host named `C`.
args[0] = path.join(process.env.SystemRoot, 'system32', 'tar.exe');
}
spawnSync(args[0], args.slice(1), { stdio: 'inherit' });
fs.copyFileSync(path.join(workDir, fileToExtract), destPath);
fs.chmodSync(destPath, mode);
} finally {
fs.rmSync(workDir, { recursive: true, maxRetries: 10 });
}
return destPath;
}
/**
* Download a zip file to a temp dir, expand,
* and move the expected binary to the final dir
*
* @param url {string} The URL to download.
* @param destPath {string} The path to download to, including the executable name.
* @param options {ArchiveDownloadOptions} Additional options for the download.
* @returns {Promise<string>} The full path of the final binary.
*/
export async function downloadZip(url, destPath, options = {}) {
const { overwrite } = options;
const access = options.access ?? fs.constants.X_OK;
if (!overwrite) {
try {
await fs.promises.access(destPath, access);
console.log(`${ destPath } already exists, not re-downloading.`);
return destPath;
} catch (ex) {
if (ex.code !== 'ENOENT') {
throw ex;
}
}
}
const binaryBasename = path.basename(destPath, '.exe');
const workDir = fs.mkdtempSync(path.join(os.tmpdir(), `rd-${ binaryBasename }-`));
const fileToExtract = options.entryName || path.basename(destPath);
const mode =
(access & fs.constants.X_OK) ? 0o755 : (access & fs.constants.W_OK) ? 0o644 : 0o444;
try {
const zipPath = path.join(workDir, `${ binaryBasename }.tar.gz`);
const args = ['unzip', '-o', zipPath, fileToExtract, '-d', workDir];
await download(url, zipPath, { ...options, access: fs.constants.W_OK });
spawnSync(args[0], args.slice(1), { stdio: 'inherit' });
fs.copyFileSync(path.join(workDir, fileToExtract), destPath);
fs.chmodSync(destPath, mode);
} finally {
fs.rmSync(workDir, { recursive: true, maxRetries: 10 });
}
return destPath;
}

View File

@@ -2,19 +2,18 @@ import { execFileSync } from 'child_process';
import os from 'os';
async function runScripts() {
const scripts = ['download-resources'];
switch (os.platform()) {
case 'darwin':
scripts.push('download/hyperkit', 'download/lima');
await (await import('./download/tools.mjs')).default('darwin');
await (await import('./download/hyperkit.mjs')).default();
await (await import('./download/lima.mjs')).default();
break;
case 'win32':
scripts.push('download/wsl');
await (await import('./download/tools.mjs')).default('win32');
await (await import('./download/tools.mjs')).default('linux');
await (await import('./download/wsl.mjs')).default();
break;
}
for (const script of scripts) {
await (await import(`./${ script }.mjs`)).default();
}
}
runScripts().then(() => {