UNPKG

balena-cli

Version:

The official balena Command Line Interface

288 lines • 10.7 kB
"use strict"; 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