balena-cli
Version:
The official balena Command Line Interface
288 lines • 10.7 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.expandForAppName = exports.delay = exports.areDeviceTypesCompatible = void 0;
exports.getGroupDefaults = getGroupDefaults;
exports.stateToString = stateToString;
exports.sudo = sudo;
exports.runCommand = runCommand;
exports.getManifest = getManifest;
exports.osProgressHandler = osProgressHandler;
exports.getAppWithArch = getAppWithArch;
exports.retry = retry;
exports.isWindowsComExeShell = isWindowsComExeShell;
exports.shellEscape = shellEscape;
exports.getProxyConfig = getProxyConfig;
exports.addSIGINTHandler = addSIGINTHandler;
exports.awaitInterruptibleTask = awaitInterruptibleTask;
exports.pickAndRename = pickAndRename;
const _ = require("lodash");
const util_1 = require("util");
const lazy_1 = require("./lazy");
function getGroupDefaults(group) {
return _.chain(group)
.get('options')
.map((question) => [question.name, question.default])
.fromPairs()
.value();
}
function stateToString(state) {
const percentage = _.padStart(`${state.percentage}`, 3, '0');
const chalk = (0, lazy_1.getChalk)();
const result = `${chalk.blue(percentage + '%')} ${chalk.cyan(state.operation.command)}`;
switch (state.operation.command) {
case 'copy':
return `${result} ${state.operation.from.path} -> ${state.operation.to.path}`;
case 'replace':
return `${result} ${state.operation.file.path}, ${state.operation.copy} -> ${state.operation.replace}`;
case 'run-script':
return `${result} ${state.operation.script}`;
default:
throw new Error(`Unsupported operation: ${state.operation.command}`);
}
}
async function sudo(command, { stderr, msg, isCLIcmd, } = {}) {
const { executeWithPrivileges } = await Promise.resolve().then(() => require('./sudo'));
if (process.platform !== 'win32') {
console.log(msg ||
'Admin privileges required: you may be asked for your computer password to continue.');
}
isCLIcmd !== null && isCLIcmd !== void 0 ? isCLIcmd : (isCLIcmd = true);
await executeWithPrivileges(command, stderr, isCLIcmd);
}
async function runCommand(commandArgs) {
const { isSubcommand } = require('../preparser');
if (await isSubcommand(commandArgs)) {
commandArgs = [
commandArgs[0] + ':' + commandArgs[1],
...commandArgs.slice(2),
];
}
const { run } = require('@oclif/core');
return run(commandArgs);
}
async function getManifest(image, deviceType) {
const init = await Promise.resolve().then(() => require('balena-device-init'));
const sdk = (0, lazy_1.getBalenaSdk)();
const manifest = await init.getImageManifest(image);
if (manifest != null &&
manifest.slug !== deviceType &&
manifest.slug !== (await sdk.models.deviceType.get(deviceType)).slug) {
const { ExpectedError } = await Promise.resolve().then(() => require('../errors'));
throw new ExpectedError(`The device type of the provided OS image ${manifest.slug}, does not match the expected device type ${deviceType}`);
}
return (manifest !== null && manifest !== void 0 ? manifest : (await sdk.models.config.getDeviceTypeManifestBySlug(deviceType)));
}
const areDeviceTypesCompatible = async (appDeviceTypeSlug, osDeviceTypeSlug) => {
if (appDeviceTypeSlug === osDeviceTypeSlug) {
return true;
}
const sdk = (0, lazy_1.getBalenaSdk)();
const pineOptions = {
$select: 'is_of__cpu_architecture',
$expand: {
is_of__cpu_architecture: {
$select: 'slug',
},
},
};
const [appDeviceType, osDeviceType] = await Promise.all([appDeviceTypeSlug, osDeviceTypeSlug].map((dtSlug) => sdk.models.deviceType.get(dtSlug, pineOptions)));
return sdk.models.os.isArchitectureCompatibleWith(osDeviceType.is_of__cpu_architecture[0].slug, appDeviceType.is_of__cpu_architecture[0].slug);
};
exports.areDeviceTypesCompatible = areDeviceTypesCompatible;
async function osProgressHandler(step) {
step.on('stdout', process.stdout.write.bind(process.stdout));
step.on('stderr', process.stderr.write.bind(process.stderr));
step.on('state', function (state) {
if (state.operation.command === 'burn') {
return;
}
console.log(exports.stateToString(state));
});
const visuals = (0, lazy_1.getVisuals)();
const progressBars = {
write: new visuals.Progress('Writing Device OS'),
check: new visuals.Progress('Validating Device OS'),
};
step.on('burn', (state) => progressBars[state.type].update(state));
await new Promise((resolve, reject) => {
step.on('error', reject);
step.on('end', resolve);
});
}
async function getAppWithArch(applicationName) {
const { getApplication } = await Promise.resolve().then(() => require('./sdk'));
const balena = (0, lazy_1.getBalenaSdk)();
const app = await getApplication(balena, applicationName, {
$expand: {
application_type: {
$select: ['name', 'slug', 'supports_multicontainer'],
},
is_for__device_type: {
$select: 'slug',
$expand: {
is_of__cpu_architecture: {
$select: 'slug',
},
},
},
},
});
return {
...app,
arch: app.is_for__device_type[0].is_of__cpu_architecture[0].slug,
};
}
const second = 1000;
const minute = 60 * second;
exports.delay = (0, util_1.promisify)(setTimeout);
async function retry({ func, maxAttempts, label, initialDelayMs = 1000, backoffScaler = 2, maxSingleDelayMs = 1 * minute, }) {
const { SIGINTError } = await Promise.resolve().then(() => require('../errors'));
let delayMs = initialDelayMs;
for (let count = 0; count < maxAttempts - 1; count++) {
const lastAttemptMs = Date.now();
try {
return await func();
}
catch (err) {
if (err instanceof SIGINTError) {
throw err;
}
if (count) {
const elapsedMs = Math.max(0, Date.now() - lastAttemptMs);
delayMs = Math.max(initialDelayMs, delayMs - elapsedMs);
delayMs = Math.min(maxSingleDelayMs, delayMs * backoffScaler);
}
const sec = delayMs / 1000;
const secStr = sec < 10 ? sec.toFixed(1) : Math.round(sec).toString();
console.log(`Retrying "${label}" after ${secStr}s (${count + 1} of ${maxAttempts - 1}) due to: ${err}`);
await (0, exports.delay)(delayMs);
}
}
return await func();
}
function isWindowsComExeShell() {
return (process.env.SHELL == null &&
process.env.ComSpec != null &&
process.env.ComSpec.endsWith('cmd.exe'));
}
function shellEscape(args, detectShell = false) {
const isCmdExe = detectShell
? isWindowsComExeShell()
: process.platform === 'win32';
if (isCmdExe) {
return args.map((v) => windowsCmdExeEscapeArg(v));
}
else {
const shellEscapeFunc = require('shell-escape');
return args.map((v) => shellEscapeFunc([v]));
}
}
function windowsCmdExeEscapeArg(arg) {
if (arg.length > 1 && arg.startsWith('"') && arg.endsWith('"')) {
arg = arg.slice(1, -1);
}
arg = arg.replace(/[()%!^<>&|]/g, '^$&');
return `"${arg.replace(/["]/g, '""')}"`;
}
function getProxyConfig() {
const tunnelNgConfig = global.PROXY_CONFIG;
if (tunnelNgConfig) {
let username;
let password;
const proxyAuth = tunnelNgConfig.proxyAuth;
if (proxyAuth) {
const i = proxyAuth.lastIndexOf(':');
if (i > 0) {
username = proxyAuth.substring(0, i);
password = proxyAuth.substring(i + 1);
}
}
return {
host: tunnelNgConfig.host,
port: `${tunnelNgConfig.port}`,
username,
password,
proxyAuth: tunnelNgConfig.proxyAuth,
};
}
else {
const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
if (proxyUrl) {
const { URL } = require('url');
let url;
try {
url = new URL(proxyUrl);
}
catch (_e) {
return;
}
return {
host: url.hostname,
port: url.port,
username: url.username,
password: url.password,
proxyAuth: url.username && url.password
? `${url.username}:${url.password}`
: undefined,
};
}
}
}
exports.expandForAppName = {
$expand: {
belongs_to__application: { $select: ['app_name', 'slug'] },
is_of__device_type: { $select: 'slug' },
is_running__release: { $select: 'commit' },
},
};
const installReadlineSigintEmitter = _.once(function emitSigint() {
if (process.platform === 'win32') {
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.on('SIGINT', () => process.emit('SIGINT'));
}
});
function addSIGINTHandler(sigintHandler, once = true) {
installReadlineSigintEmitter();
if (once) {
process.once('SIGINT', sigintHandler);
}
else {
process.on('SIGINT', sigintHandler);
}
}
async function awaitInterruptibleTask(task, ...theArgs) {
let sigintHandler = () => undefined;
const sigintPromise = new Promise((_resolve, reject) => {
sigintHandler = () => {
const { SIGINTError } = require('../errors');
reject(new SIGINTError('Task aborted on SIGINT signal'));
};
addSIGINTHandler(sigintHandler);
});
try {
return await Promise.race([sigintPromise, task(...theArgs)]);
}
finally {
process.removeListener('SIGINT', sigintHandler);
}
}
function pickAndRename(obj, fields) {
const rename = {};
fields = fields.map((f) => {
let renameFrom = f;
let renameTo = f;
const match = f.match(/(?<from>\S+)\s+=>\s+(?<to>\S+)/);
if (match && match.groups) {
renameFrom = match.groups.from;
renameTo = match.groups.to;
}
rename[renameFrom] = renameTo;
return renameFrom;
});
return _.mapKeys(_.pick(obj, fields), (_val, key) => rename[key]);
}
//# sourceMappingURL=helpers.js.map
;