mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-01-18 02:39:49 +00:00
build, deploy: Fix error handling when QEMU download fails
Change-type: patch
This commit is contained in:
parent
8b99cd7170
commit
bcea5193a1
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Copyright 2017-2020 Balena Ltd.
|
* Copyright 2017-2021 Balena Ltd.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -16,6 +16,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type * as Dockerode from 'dockerode';
|
import type * as Dockerode from 'dockerode';
|
||||||
|
|
||||||
|
import { ExpectedError } from '../errors';
|
||||||
import { getBalenaSdk, stripIndent } from './lazy';
|
import { getBalenaSdk, stripIndent } from './lazy';
|
||||||
import Logger = require('./logger');
|
import Logger = require('./logger');
|
||||||
|
|
||||||
@ -63,7 +65,8 @@ export function copyQemu(context: string, arch: string) {
|
|||||||
.then(() => path.relative(context, binPath));
|
.then(() => path.relative(context, binPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getQemuPath = function (arch: string) {
|
export const getQemuPath = function (balenaArch: string) {
|
||||||
|
const qemuArch = balenaArchToQemuArch(balenaArch);
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
const path = require('path') as typeof import('path');
|
const path = require('path') as typeof import('path');
|
||||||
const { promises: fs } = require('fs') as typeof import('fs');
|
const { promises: fs } = require('fs') as typeof import('fs');
|
||||||
@ -79,64 +82,76 @@ export const getQemuPath = function (arch: string) {
|
|||||||
throw err;
|
throw err;
|
||||||
})
|
})
|
||||||
.then(() =>
|
.then(() =>
|
||||||
path.join(binDir, `${QEMU_BIN_NAME}-${arch}-${QEMU_VERSION}`),
|
path.join(binDir, `${QEMU_BIN_NAME}-${qemuArch}-${QEMU_VERSION}`),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function installQemu(arch: string) {
|
async function installQemu(arch: string, qemuPath: string) {
|
||||||
const request = require('request') as typeof import('request');
|
const qemuArch = balenaArchToQemuArch(arch);
|
||||||
const fs = require('fs') as typeof import('fs');
|
const fileVersion = QEMU_VERSION.replace('v', '').replace('+', '.');
|
||||||
const zlib = require('zlib') as typeof import('zlib');
|
const urlFile = encodeURIComponent(`qemu-${fileVersion}-${qemuArch}.tar.gz`);
|
||||||
const tar = require('tar-stream') as typeof import('tar-stream');
|
const urlVersion = encodeURIComponent(QEMU_VERSION);
|
||||||
|
const qemuUrl = `https://github.com/balena-io/qemu/releases/download/${urlVersion}/${urlFile}`;
|
||||||
|
|
||||||
return getQemuPath(arch).then(
|
const request = await import('request');
|
||||||
(qemuPath) =>
|
const fs = await import('fs');
|
||||||
new Promise(function (resolve, reject) {
|
const zlib = await import('zlib');
|
||||||
const installStream = fs.createWriteStream(qemuPath);
|
const tar = await import('tar-stream');
|
||||||
|
|
||||||
const qemuArch = balenaArchToQemuArch(arch);
|
// createWriteStream creates a zero-length file on disk that
|
||||||
const fileVersion = QEMU_VERSION.replace('v', '').replace('+', '.');
|
// needs to be deleted if the download fails
|
||||||
const urlFile = encodeURIComponent(
|
const installStream = fs.createWriteStream(qemuPath);
|
||||||
`qemu-${fileVersion}-${qemuArch}.tar.gz`,
|
try {
|
||||||
);
|
await new Promise<void>((resolve, reject) => {
|
||||||
const urlVersion = encodeURIComponent(QEMU_VERSION);
|
const extract = tar.extract();
|
||||||
const qemuUrl = `https://github.com/balena-io/qemu/releases/download/${urlVersion}/${urlFile}`;
|
extract.on('entry', function (header, stream, next) {
|
||||||
|
try {
|
||||||
const extract = tar.extract();
|
|
||||||
extract.on('entry', function (header, stream, next) {
|
|
||||||
stream.on('end', next);
|
stream.on('end', next);
|
||||||
if (header.name.includes(`qemu-${qemuArch}-static`)) {
|
if (header.name.includes(`qemu-${qemuArch}-static`)) {
|
||||||
stream.pipe(installStream);
|
stream.pipe(installStream);
|
||||||
} else {
|
} else {
|
||||||
stream.resume();
|
stream.resume();
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
request(qemuUrl)
|
||||||
|
.on('error', reject)
|
||||||
|
.pipe(zlib.createGunzip())
|
||||||
|
.on('error', reject)
|
||||||
|
.pipe(extract)
|
||||||
|
.on('error', reject)
|
||||||
|
.on('finish', function () {
|
||||||
|
fs.chmodSync(qemuPath, '755');
|
||||||
|
resolve();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
return request(qemuUrl)
|
} catch (err) {
|
||||||
.on('error', reject)
|
try {
|
||||||
.pipe(zlib.createGunzip())
|
await fs.promises.unlink(qemuPath);
|
||||||
.on('error', reject)
|
} catch {
|
||||||
.pipe(extract)
|
// ignore
|
||||||
.on('error', reject)
|
}
|
||||||
.on('finish', function () {
|
throw err;
|
||||||
fs.chmodSync(qemuPath, '755');
|
}
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const balenaArchToQemuArch = function (arch: string) {
|
const balenaArchToQemuArch = function (arch: string) {
|
||||||
switch (arch) {
|
switch (arch) {
|
||||||
case 'armv7hf':
|
|
||||||
case 'rpi':
|
case 'rpi':
|
||||||
|
case 'arm':
|
||||||
case 'armhf':
|
case 'armhf':
|
||||||
|
case 'armv7hf':
|
||||||
return 'arm';
|
return 'arm';
|
||||||
|
case 'arm64':
|
||||||
case 'aarch64':
|
case 'aarch64':
|
||||||
return 'aarch64';
|
return 'aarch64';
|
||||||
default:
|
default:
|
||||||
throw new Error(`Cannot install emulator for architecture ${arch}`);
|
throw new ExpectedError(stripIndent`
|
||||||
|
Unknown ARM architecture identifier "${arch}".
|
||||||
|
Known ARM identifiers: rpi arm armhf armv7hf arm64 aarch64`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -155,11 +170,17 @@ export async function installQemuIfNeeded(
|
|||||||
const { promises: fs } = await import('fs');
|
const { promises: fs } = await import('fs');
|
||||||
const qemuPath = await getQemuPath(arch);
|
const qemuPath = await getQemuPath(arch);
|
||||||
try {
|
try {
|
||||||
|
const stats = await fs.stat(qemuPath);
|
||||||
|
// Earlier versions of the CLI with broken error handling would leave
|
||||||
|
// behind files with size 0. If such a file is found, delete it.
|
||||||
|
if (stats.size === 0) {
|
||||||
|
await fs.unlink(qemuPath);
|
||||||
|
}
|
||||||
await fs.access(qemuPath);
|
await fs.access(qemuPath);
|
||||||
} catch {
|
} catch {
|
||||||
// Qemu doesn't exist so install it
|
// QEMU not found in cache folder (~/.balena/bin/), so install it
|
||||||
logger.logInfo(`Installing qemu for ${arch} emulation...`);
|
logger.logInfo(`Installing qemu for ${arch} emulation...`);
|
||||||
await installQemu(arch);
|
await installQemu(arch, qemuPath);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -257,12 +257,16 @@ describe('balena build', function () {
|
|||||||
const qemuMod = require(qemuModPath);
|
const qemuMod = require(qemuModPath);
|
||||||
const qemuBinPath = await qemuMod.getQemuPath(arch);
|
const qemuBinPath = await qemuMod.getQemuPath(arch);
|
||||||
try {
|
try {
|
||||||
|
// patch fs.access and fs.stat to pretend that a copy of the Qemu binary
|
||||||
|
// already exists locally, thus preventing a download during tests
|
||||||
mock(fsModPath, {
|
mock(fsModPath, {
|
||||||
...fsMod,
|
...fsMod,
|
||||||
promises: {
|
promises: {
|
||||||
...fsMod.promises,
|
...fsMod.promises,
|
||||||
access: async (p: string) =>
|
access: async (p: string) =>
|
||||||
p === qemuBinPath ? undefined : fsMod.promises.access(p),
|
p === qemuBinPath ? undefined : fsMod.promises.access(p),
|
||||||
|
stat: async (p: string) =>
|
||||||
|
p === qemuBinPath ? { size: 1 } : fsMod.promises.stat(p),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
mock(qemuModPath, {
|
mock(qemuModPath, {
|
||||||
|
Loading…
Reference in New Issue
Block a user