From 178c3f91547b6d94c7bcd98db4a6320e90440e33 Mon Sep 17 00:00:00 2001 From: Pagan Gazzard Date: Wed, 1 Jul 2020 15:26:40 +0100 Subject: [PATCH] Remove all module level bluebird usage from eagerly loaded modules Change-type: patch --- lib/actions/build.js | 48 +++---- lib/actions/deploy.js | 106 +++++++-------- lib/actions/device.js | 5 +- lib/actions/os.js | 3 +- lib/{app-capitano.js => app-capitano.ts} | 17 +-- lib/events.ts | 63 +++++---- lib/utils/compose.js | 30 +++-- lib/utils/device/live.ts | 6 +- lib/utils/docker-js.js | 5 +- lib/utils/helpers.ts | 39 +++--- lib/utils/patterns.ts | 164 ++++++++++++----------- lib/utils/qemu.ts | 23 +++- lib/utils/tunnel.ts | 11 +- typings/capitano/index.d.ts | 6 +- 14 files changed, 267 insertions(+), 259 deletions(-) rename lib/{app-capitano.js => app-capitano.ts} (88%) diff --git a/lib/actions/build.js b/lib/actions/build.js index 0ac4ce77..6e5084ed 100644 --- a/lib/actions/build.js +++ b/lib/actions/build.js @@ -15,10 +15,6 @@ * limitations under the License. */ -// Imported here because it's needed for the setup -// of this action -import * as Bluebird from 'bluebird'; - import * as dockerUtils from '../utils/docker'; import * as compose from '../utils/compose'; import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages'; @@ -39,7 +35,7 @@ import { getBalenaSdk } from '../utils/lazy'; */ const buildProject = function (docker, logger, composeOpts, opts) { const { loadProject } = require('../utils/compose_ts'); - return Bluebird.resolve(loadProject(logger, composeOpts)) + return loadProject(logger, composeOpts) .then(function (project) { const appType = opts.app?.application_type?.[0]; if ( @@ -74,8 +70,9 @@ const buildProject = function (docker, logger, composeOpts, opts) { logger.outputDeferredMessages(); logger.logSuccess('Build succeeded!'); }) - .tapCatch(() => { + .catch((err) => { logger.logError('Build failed'); + throw err; }); }; @@ -135,7 +132,7 @@ Examples: }, ]), ), - action(params, options) { + async action(params, options) { // compositions with many services trigger misleading warnings // @ts-ignore editing property that isn't typed but does exist require('events').defaultMaxListeners = 1000; @@ -159,27 +156,24 @@ Examples: const { application, arch, deviceType } = options; - return Bluebird.try(function () { - if ( - (application == null && (arch == null || deviceType == null)) || - (application != null && (arch != null || deviceType != null)) - ) { - throw new ExpectedError( - 'You must specify either an application or an arch/deviceType pair to build for', - ); - } - if (application) { - return checkLoggedIn(); - } + if ( + (application == null && (arch == null || deviceType == null)) || + (application != null && (arch != null || deviceType != null)) + ) { + throw new ExpectedError( + 'You must specify either an application or an arch/deviceType pair to build for', + ); + } + if (application) { + await checkLoggedIn(); + } + + return validateProjectDirectory(sdk, { + dockerfilePath: options.dockerfile, + noParentCheck: options['noparent-check'] || false, + projectPath: options.source || '.', + registrySecretsPath: options['registry-secrets'], }) - .then(() => - validateProjectDirectory(sdk, { - dockerfilePath: options.dockerfile, - noParentCheck: options['noparent-check'] || false, - projectPath: options.source || '.', - registrySecretsPath: options['registry-secrets'], - }), - ) .then(function ({ dockerfilePath, registrySecrets }) { options.dockerfile = dockerfilePath; options['registry-secrets'] = registrySecrets; diff --git a/lib/actions/deploy.js b/lib/actions/deploy.js index 51a6564d..3f73ec6f 100644 --- a/lib/actions/deploy.js +++ b/lib/actions/deploy.js @@ -15,10 +15,6 @@ * limitations under the License. */ -// Imported here because it's needed for the setup -// of this action -import * as Bluebird from 'bluebird'; - import * as dockerUtils from '../utils/docker'; import * as compose from '../utils/compose'; import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages'; @@ -41,6 +37,7 @@ import { getBalenaSdk, getChalk } from '../utils/lazy'; * @param {any} opts */ const deployProject = function (docker, logger, composeOpts, opts) { + const Bluebird = require('bluebird'); const _ = require('lodash'); const doodles = require('resin-doodles'); const sdk = getBalenaSdk(); @@ -49,7 +46,7 @@ const deployProject = function (docker, logger, composeOpts, opts) { loadProject, } = require('../utils/compose_ts'); - return Bluebird.resolve(loadProject(logger, composeOpts, opts.image)) + return loadProject(logger, composeOpts, opts.image) .then(function (project) { if ( project.descriptors.length > 1 && @@ -178,8 +175,9 @@ const deployProject = function (docker, logger, composeOpts, opts) { console.log(doodles.getDoodle()); // Show charlie console.log(); }) - .tapCatch(() => { + .catch((err) => { logger.logError('Deploy failed'); + throw err; }); }; @@ -245,7 +243,7 @@ Examples: }, ]), ), - action(params, options) { + async action(params, options) { // compositions with many services trigger misleading warnings // @ts-ignore editing property that isn't typed but does exist require('events').defaultMaxListeners = 1000; @@ -268,55 +266,53 @@ Examples: appName = appName_raw || appName || options.application; delete options.application; - return Bluebird.try(function () { - if (appName == null) { - throw new ExpectedError( - 'Please specify the name of the application to deploy', - ); - } + if (appName == null) { + throw new ExpectedError( + 'Please specify the name of the application to deploy', + ); + } - if (image != null && options.build) { - throw new ExpectedError( - 'Build option is not applicable when specifying an image', - ); - } - }) - .then(function () { - if (image) { - return getRegistrySecrets(sdk, options['registry-secrets']).then( - (registrySecrets) => { - options['registry-secrets'] = registrySecrets; - }, - ); - } else { - return validateProjectDirectory(sdk, { - dockerfilePath: options.dockerfile, - noParentCheck: options['noparent-check'] || false, - projectPath: options.source || '.', - registrySecretsPath: options['registry-secrets'], - }).then(function ({ dockerfilePath, registrySecrets }) { - options.dockerfile = dockerfilePath; - options['registry-secrets'] = registrySecrets; - }); - } - }) - .then(() => helpers.getAppWithArch(appName)) - .then(function (app) { - return Promise.all([ - dockerUtils.getDocker(options), - dockerUtils.generateBuildOpts(options), - compose.generateOpts(options), - ]).then(([docker, buildOpts, composeOpts]) => - deployProject(docker, logger, composeOpts, { - app, - appName, // may be prefixed by 'owner/', unlike app.app_name - image, - shouldPerformBuild: !!options.build, - shouldUploadLogs: !options.nologupload, - buildEmulated: !!options.emulated, - buildOpts, - }), - ); + if (image != null && options.build) { + throw new ExpectedError( + 'Build option is not applicable when specifying an image', + ); + } + + if (image) { + const registrySecrets = await getRegistrySecrets( + sdk, + options['registry-secrets'], + ); + options['registry-secrets'] = registrySecrets; + } else { + const { + dockerfilePath, + registrySecrets, + } = await validateProjectDirectory(sdk, { + dockerfilePath: options.dockerfile, + noParentCheck: options['noparent-check'] || false, + projectPath: options.source || '.', + registrySecretsPath: options['registry-secrets'], }); + options.dockerfile = dockerfilePath; + options['registry-secrets'] = registrySecrets; + } + + const app = await helpers.getAppWithArch(appName); + + const [docker, buildOpts, composeOpts] = await Promise.all([ + dockerUtils.getDocker(options), + dockerUtils.generateBuildOpts(options), + compose.generateOpts(options), + ]); + await deployProject(docker, logger, composeOpts, { + app, + appName, // may be prefixed by 'owner/', unlike app.app_name + image, + shouldPerformBuild: !!options.build, + shouldUploadLogs: !options.nologupload, + buildEmulated: !!options.emulated, + buildOpts, + }); }, }; diff --git a/lib/actions/device.js b/lib/actions/device.js index 0cec6b04..7bae6688 100644 --- a/lib/actions/device.js +++ b/lib/actions/device.js @@ -81,7 +81,7 @@ Examples: return Bluebird.using(download(), (tempPath) => runCommand(`device register ${application.app_name}`) .then(balena.models.device.get) - .tap(function (device) { + .then(function (device) { let configureCommand = `os configure '${tempPath}' --device ${device.uuid}`; if (options.config) { configureCommand += ` --config '${options.config}'`; @@ -103,7 +103,8 @@ Examples: balena.models.device.remove(device.uuid).finally(function () { throw error; }), - ); + ) + .then(() => device); }), ).then(function (device) { console.log('Done'); diff --git a/lib/actions/os.js b/lib/actions/os.js index e96f4973..01e3f948 100644 --- a/lib/actions/os.js +++ b/lib/actions/os.js @@ -312,8 +312,7 @@ ${INIT_WARNING_MESSAGE}\ `Going to erase ${answers.drive}.`, true, ) - .return(answers.drive) - .then(umountAsync); + .then(() => umountAsync(answers.drive)); }) .tap((answers) => helpers.sudo([ diff --git a/lib/app-capitano.js b/lib/app-capitano.ts similarity index 88% rename from lib/app-capitano.js rename to lib/app-capitano.ts index 3cf57cc0..752e8977 100644 --- a/lib/app-capitano.js +++ b/lib/app-capitano.ts @@ -14,11 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -import * as Bluebird from 'bluebird'; - import * as capitano from 'capitano'; import * as actions from './actions'; import * as events from './events'; +import { promisify } from 'util'; capitano.permission('user', (done) => require('./utils/patterns').checkLoggedIn().then(done, done), @@ -76,17 +75,17 @@ capitano.command(actions.local.flash); // ---------- Public utils ---------- capitano.command(actions.util.availableDrives); -//------------ Local build and deploy ------- +// ------------ Local build and deploy ------- capitano.command(actions.build); capitano.command(actions.deploy); -//------------ Push/remote builds ------- +// ------------ Push/remote builds ------- capitano.command(actions.push.push); -export function run(argv) { +export function run(argv: string[]) { const cli = capitano.parse(argv.slice(2)); const runCommand = function () { - const capitanoExecuteAsync = Bluebird.promisify(capitano.execute); + const capitanoExecuteAsync = promisify(capitano.execute); if (cli.global?.help) { return capitanoExecuteAsync({ command: `help ${cli.command ?? ''}`, @@ -97,9 +96,7 @@ export function run(argv) { }; const trackCommand = function () { - const getMatchCommandAsync = Bluebird.promisify( - capitano.state.getMatchCommand, - ); + const getMatchCommandAsync = promisify(capitano.state.getMatchCommand); return getMatchCommandAsync(cli.command).then(function (command) { // cmdSignature is literally a string like, for example: // "push " @@ -111,7 +108,7 @@ export function run(argv) { }); }; - return Bluebird.all([trackCommand(), runCommand()]).catch( + return Promise.all([trackCommand(), runCommand()]).catch( require('./errors').handleError, ); } diff --git a/lib/events.ts b/lib/events.ts index 0f7cf061..aa711623 100644 --- a/lib/events.ts +++ b/lib/events.ts @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as Bluebird from 'bluebird'; import * as _ from 'lodash'; import * as Mixpanel from 'mixpanel'; @@ -49,15 +48,15 @@ interface CachedUsername { * (mainly unexpected/unhandled exceptions -- see also `lib/errors.ts`). */ export async function trackCommand(commandSignature: string) { - const Sentry = await import('@sentry/node'); - Sentry.configureScope((scope) => { - scope.setExtra('command', commandSignature); - }); - const settings = await import('balena-settings-client'); - const balenaUrl = settings.get('balenaUrl') as string; + try { + const Sentry = await import('@sentry/node'); + Sentry.configureScope((scope) => { + scope.setExtra('command', commandSignature); + }); + const settings = await import('balena-settings-client'); + const balenaUrl = settings.get('balenaUrl') as string; - return Bluebird.props({ - username: (async () => { + const username = await (async () => { const getStorage = await import('balena-settings-storage'); const dataDirectory = settings.get('dataDirectory') as string; const storage = getStorage({ dataDirectory }); @@ -78,34 +77,34 @@ export async function trackCommand(commandSignature: string) { } try { const balena = getBalenaSdk(); - const username = await balena.auth.whoami(); + const $username = await balena.auth.whoami(); storage.set('cachedUsername', { token, - username, + username: $username, } as CachedUsername); - return username; + return $username; } catch { return; } - })(), - mixpanel: getMixpanel(balenaUrl), - }) - .then(({ username, mixpanel }) => { - Sentry.configureScope((scope) => { - scope.setUser({ - id: username, - username, - }); + })(); + + const mixpanel = getMixpanel(balenaUrl); + + Sentry.configureScope((scope) => { + scope.setUser({ + id: username, + username, }); - return mixpanel.track(`[CLI] ${commandSignature}`, { - distinct_id: username, - version: packageJSON.version, - node: process.version, - arch: process.arch, - balenaUrl, // e.g. 'balena-cloud.com' or 'balena-staging.com' - platform: process.platform, - }); - }) - .timeout(100) - .catchReturn(undefined); + }); + await mixpanel.track(`[CLI] ${commandSignature}`, { + distinct_id: username, + version: packageJSON.version, + node: process.version, + arch: process.arch, + balenaUrl, // e.g. 'balena-cloud.com' or 'balena-staging.com' + platform: process.platform, + }); + } catch { + // ignore + } } diff --git a/lib/utils/compose.js b/lib/utils/compose.js index 3ea262e4..b8cf1d81 100644 --- a/lib/utils/compose.js +++ b/lib/utils/compose.js @@ -15,7 +15,6 @@ * limitations under the License. */ -import * as Bluebird from 'bluebird'; import * as path from 'path'; import { ExpectedError } from '../errors'; @@ -196,12 +195,12 @@ export function createProject(composePath, composeStr, projectName = null) { * optionally converting text file line endings (CRLF to LF). * @param {string} dir Source directory * @param {import('./compose-types').TarDirectoryOptions} param - * @returns {Bluebird} + * @returns {Promise} */ export function tarDirectory(dir, param) { let { nogitignore = false } = param; if (nogitignore) { - return Bluebird.resolve(require('./compose_ts').tarDirectory(dir, param)); + return require('./compose_ts').tarDirectory(dir, param); } else { return originalTarDirectory(dir, param); } @@ -213,7 +212,7 @@ export function tarDirectory(dir, param) { * deleted in CLI v13. * @param {string} dir Source directory * @param {import('./compose-types').TarDirectoryOptions} param - * @returns {Bluebird} + * @returns {Promise} */ function originalTarDirectory(dir, param) { let { @@ -225,6 +224,7 @@ function originalTarDirectory(dir, param) { convertEol = false; } + const Bluebird = require('bluebird'); const tar = require('tar-stream'); const klaw = require('klaw'); const { promises: fs } = require('fs'); @@ -323,6 +323,7 @@ export function buildProject( nogitignore, multiDockerignore, ) { + const Bluebird = require('bluebird'); const _ = require('lodash'); const humanize = require('humanize'); const compose = require('resin-compose-parse'); @@ -377,12 +378,14 @@ export function buildProject( .then(( needsQemu, // Tar up the directory, ready for the build stream ) => - tarDirectory(projectPath, { - composition, - convertEol, - multiDockerignore, - nogitignore, - }) + Bluebird.resolve( + tarDirectory(projectPath, { + composition, + convertEol, + multiDockerignore, + nogitignore, + }), + ) .then((tarStream) => makeBuildTasks( composition, @@ -555,7 +558,7 @@ export function buildProject( * @param {number} userId * @param {number} appId * @param {import('resin-compose-parse').Composition} composition - * @returns {Bluebird} + * @returns {Promise} */ export const createRelease = function ( apiEndpoint, @@ -638,7 +641,7 @@ export const tagServiceImages = (docker, images, serviceImages) => * @param {import('docker-toolbelt')} docker * @param {import('./logger')} logger * @param {number} appID - * @returns {Bluebird} + * @returns {Promise} */ export const getPreviousRepos = (sdk, docker, logger, appID) => sdk.pine @@ -691,7 +694,7 @@ export const getPreviousRepos = (sdk, docker, logger, appID) => * @param {string} registry * @param {string[]} images * @param {string[]} previousRepos - * @returns {Bluebird} + * @returns {Promise} */ export const authorizePush = function ( sdk, @@ -734,6 +737,7 @@ export const pushAndUpdateServiceImages = function ( const { DockerProgress } = require('docker-progress'); const { retry } = require('./helpers'); const tty = require('./tty')(process.stdout); + const Bluebird = require('bluebird'); const opts = { authconfig: { registrytoken: token } }; diff --git a/lib/utils/device/live.ts b/lib/utils/device/live.ts index d8f0011a..bf77747f 100644 --- a/lib/utils/device/live.ts +++ b/lib/utils/device/live.ts @@ -1,4 +1,3 @@ -import * as Bluebird from 'bluebird'; import * as chokidar from 'chokidar'; import type * as Dockerode from 'dockerode'; import Livepush, { ContainerNotRunningError } from 'livepush'; @@ -20,6 +19,7 @@ import { } from './deploy'; import { BuildError } from './errors'; import { getServiceColourFn } from './logs'; +import { delay } from '../helpers'; // How often do we want to check the device state // engine has settled (delay in ms) @@ -251,7 +251,7 @@ export class LivepushManager { this.logger.logDebug( `Device state not settled, retrying in ${DEVICE_STATUS_SETTLE_CHECK_INTERVAL}ms`, ); - await Bluebird.delay(DEVICE_STATUS_SETTLE_CHECK_INTERVAL); + await delay(DEVICE_STATUS_SETTLE_CHECK_INTERVAL); await this.awaitDeviceStateSettle(); } @@ -308,7 +308,7 @@ export class LivepushManager { ); await this.cancelRebuild(serviceName); while (this.rebuildsCancelled[serviceName]) { - await Bluebird.delay(1000); + await delay(1000); } } diff --git a/lib/utils/docker-js.js b/lib/utils/docker-js.js index f9c74ba2..fe96d1b5 100644 --- a/lib/utils/docker-js.js +++ b/lib/utils/docker-js.js @@ -17,7 +17,6 @@ // Functions to help actions which rely on using docker -import * as Bluebird from 'bluebird'; import * as _ from 'lodash'; import { ExpectedError } from '../errors'; @@ -109,6 +108,7 @@ Implements the same feature as the "docker build --cache-from" option.`, const generateConnectOpts = function (opts) { const { promises: fs } = require('fs'); + const Bluebird = require('bluebird'); return Bluebird.try(function () { const connectOpts = {}; @@ -213,7 +213,7 @@ export function generateBuildOpts(options) { * port?: number; * timeout?: number; * }} options - * @returns {Bluebird} + * @returns {Promise} */ export function getDocker(options) { return generateConnectOpts(options) @@ -223,6 +223,7 @@ export function getDocker(options) { const getDockerToolbelt = _.once(function () { const Docker = require('docker-toolbelt'); + const Bluebird = require('bluebird'); Bluebird.promisifyAll(Docker.prototype, { filter(name) { return name === 'run'; diff --git a/lib/utils/helpers.ts b/lib/utils/helpers.ts index 35a590fc..eddc649a 100644 --- a/lib/utils/helpers.ts +++ b/lib/utils/helpers.ts @@ -16,7 +16,6 @@ limitations under the License. import type { InitializeEmitter, OperationState } from 'balena-device-init'; import type * as BalenaSdk from 'balena-sdk'; -import * as Bluebird from 'bluebird'; import { spawn, SpawnOptions } from 'child_process'; import * as _ from 'lodash'; import * as os from 'os'; @@ -25,6 +24,7 @@ import type * as ShellEscape from 'shell-escape'; import type { Device, PineOptionsFor } from 'balena-sdk'; import { ExpectedError } from '../errors'; import { getBalenaSdk, getChalk, getVisuals } from './lazy'; +import { promisify } from 'util'; export function getGroupDefaults(group: { options: Array<{ name: string; default?: string }>; @@ -95,9 +95,10 @@ export async function sudo( await executeWithPrivileges(command, stderr, isCLIcmd); } -export function runCommand(command: string): Bluebird { - const capitano = require('capitano'); - return Bluebird.fromCallback((resolver) => capitano.run(command, resolver)); +export function runCommand(command: string): Promise { + const capitano = require('capitano') as typeof import('capitano'); + const capitanoRunAsync = promisify(capitano.run); + return capitanoRunAsync(command); } export async function getManifest( @@ -188,6 +189,8 @@ function getApplication(applicationName: string) { return balena.models.application.get(applicationName, extraOptions); } +export const delay = promisify(setTimeout); + /** * Call `func`, and if func() throws an error or returns a promise that * eventually rejects, retry it `times` many times, each time printing a @@ -197,33 +200,31 @@ function getApplication(applicationName: string) { * @param func: The function to call and, if needed, retry calling * @param times: How many times to retry calling func() * @param label: Label to include in the retry log message - * @param delayMs: How long to wait before the first retry + * @param startingDelayMs: How long to wait before the first retry * @param backoffScaler: Multiplier to previous wait time * @param count: Used "internally" for the recursive calls */ -export function retry( +export async function retry( func: () => T, times: number, label: string, - delayMs = 1000, + startingDelayMs = 1000, backoffScaler = 2, - count = 0, -): Bluebird { - let promise = Bluebird.try(func); - if (count < times) { - promise = promise.catch((err: Error) => { - const delay = backoffScaler ** count * delayMs; +): Promise { + for (let count = 0; count < times - 1; count++) { + try { + return await func(); + } catch (err) { + const delayMS = backoffScaler ** count * startingDelayMs; console.log( - `Retrying "${label}" after ${(delay / 1000).toFixed(2)}s (${ + `Retrying "${label}" after ${(delayMS / 1000).toFixed(2)}s (${ count + 1 } of ${times}) due to: ${err}`, ); - return Bluebird.delay(delay).then(() => - retry(func, times, label, delayMs, backoffScaler, count + 1), - ); - }); + await delay(delayMS); + } } - return promise; + return await func(); } /** diff --git a/lib/utils/patterns.ts b/lib/utils/patterns.ts index f0070d81..cb6b0a87 100644 --- a/lib/utils/patterns.ts +++ b/lib/utils/patterns.ts @@ -15,7 +15,6 @@ limitations under the License. */ import { BalenaApplicationNotFound } from 'balena-errors'; import type * as BalenaSdk from 'balena-sdk'; -import Bluebird = require('bluebird'); import _ = require('lodash'); import _form = require('resin-cli-form'); @@ -27,10 +26,11 @@ import { } from '../errors'; import { getBalenaSdk, getVisuals, stripIndent } from './lazy'; import validation = require('./validation'); +import { delay } from './helpers'; const getForm = _.once((): typeof _form => require('resin-cli-form')); -export function authenticate(options: {}): Bluebird { +export function authenticate(options: {}): Promise { const balena = getBalenaSdk(); return getForm() .run( @@ -134,34 +134,32 @@ export function selectDeviceType() { }); } -export function confirm( +export async function confirm( yesOption: boolean, message: string, yesMessage?: string, exitIfDeclined = false, ) { - return Bluebird.try(function () { - if (yesOption) { - if (yesMessage) { - console.log(yesMessage); - } - return true; + if (yesOption) { + if (yesMessage) { + console.log(yesMessage); } + return; + } - return getForm().ask({ - message, - type: 'confirm', - default: false, - }); - }).then(function (confirmed) { - if (!confirmed) { - const err = new Error('Aborted'); - if (exitIfDeclined) { - exitWithExpectedError(err); - } - throw err; - } + const confirmed = await getForm().ask({ + message, + type: 'confirm', + default: false, }); + + if (!confirmed) { + const err = new Error('Aborted'); + if (exitIfDeclined) { + exitWithExpectedError(err); + } + throw err; + } } export function selectApplication( @@ -234,76 +232,82 @@ export function selectOrCreateApplication() { }); } -export function awaitDevice(uuid: string) { +export async function awaitDevice(uuid: string) { const balena = getBalenaSdk(); - return balena.models.device.getName(uuid).then((deviceName) => { - const visuals = getVisuals(); - const spinner = new visuals.Spinner( - `Waiting for ${deviceName} to come online`, - ); + const deviceName = await balena.models.device.getName(uuid); + const visuals = getVisuals(); + const spinner = new visuals.Spinner( + `Waiting for ${deviceName} to come online`, + ); - const poll = (): Bluebird => { - return balena.models.device.isOnline(uuid).then(function (isOnline) { - if (isOnline) { - spinner.stop(); - console.info(`The device **${deviceName}** is online!`); - return; - } else { - // Spinner implementation is smart enough to - // not start again if it was already started - spinner.start(); + const poll = async (): Promise => { + const isOnline = await balena.models.device.isOnline(uuid); + if (isOnline) { + spinner.stop(); + console.info(`The device **${deviceName}** is online!`); + return; + } else { + // Spinner implementation is smart enough to + // not start again if it was already started + spinner.start(); - return Bluebird.delay(3000).then(poll); - } - }); - }; + await delay(3000); + await poll(); + } + }; - console.info(`Waiting for ${deviceName} to connect to balena...`); - return poll().return(uuid); - }); + console.info(`Waiting for ${deviceName} to connect to balena...`); + await poll(); + return uuid; } -export function awaitDeviceOsUpdate(uuid: string, targetOsVersion: string) { +export async function awaitDeviceOsUpdate( + uuid: string, + targetOsVersion: string, +) { const balena = getBalenaSdk(); - return balena.models.device.getName(uuid).then((deviceName) => { - const visuals = getVisuals(); - const progressBar = new visuals.Progress( - `Updating the OS of ${deviceName} to v${targetOsVersion}`, - ); - progressBar.update({ percentage: 0 }); + const deviceName = await balena.models.device.getName(uuid); + const visuals = getVisuals(); + const progressBar = new visuals.Progress( + `Updating the OS of ${deviceName} to v${targetOsVersion}`, + ); + progressBar.update({ percentage: 0 }); - const poll = (): Bluebird => { - return Bluebird.all([ - balena.models.device.getOsUpdateStatus(uuid), - balena.models.device.get(uuid, { $select: 'overall_progress' }), - ]).then(([osUpdateStatus, { overall_progress: osUpdateProgress }]) => { - if (osUpdateStatus.status === 'done') { - console.info( - `The device ${deviceName} has been updated to v${targetOsVersion} and will restart shortly!`, - ); - return; - } + const poll = async (): Promise => { + const [ + osUpdateStatus, + { overall_progress: osUpdateProgress }, + ] = await Promise.all([ + balena.models.device.getOsUpdateStatus(uuid), + balena.models.device.get(uuid, { $select: 'overall_progress' }), + ]); + if (osUpdateStatus.status === 'done') { + console.info( + `The device ${deviceName} has been updated to v${targetOsVersion} and will restart shortly!`, + ); + return; + } - if (osUpdateStatus.error) { - console.error( - `Failed to complete Host OS update on device ${deviceName}!`, - ); - exitWithExpectedError(osUpdateStatus.error); - return; - } + if (osUpdateStatus.error) { + console.error( + `Failed to complete Host OS update on device ${deviceName}!`, + ); + exitWithExpectedError(osUpdateStatus.error); + return; + } - if (osUpdateProgress !== null) { - // Avoid resetting to 0% at end of process when device goes offline. - progressBar.update({ percentage: osUpdateProgress }); - } + if (osUpdateProgress !== null) { + // Avoid resetting to 0% at end of process when device goes offline. + progressBar.update({ percentage: osUpdateProgress }); + } - return Bluebird.delay(3000).then(poll); - }); - }; + await delay(3000); + await poll(); + }; - return poll().return(uuid); - }); + await poll(); + return uuid; } export function inferOrSelectDevice(preferredUuid: string) { @@ -411,7 +415,7 @@ export async function getOnlineTargetUuid( export function selectFromList( message: string, choices: Array, -): Bluebird { +): Promise { return getForm().ask({ message, type: 'list', diff --git a/lib/utils/qemu.ts b/lib/utils/qemu.ts index c3581e92..6f572af0 100644 --- a/lib/utils/qemu.ts +++ b/lib/utils/qemu.ts @@ -15,7 +15,6 @@ * limitations under the License. */ -import * as Bluebird from 'bluebird'; import type * as Dockerode from 'dockerode'; import { getBalenaSdk, stripIndent } from './lazy'; import Logger = require('./logger'); @@ -37,9 +36,14 @@ export function copyQemu(context: string, arch: string) { const binDir = path.join(context, '.balena'); const binPath = path.join(binDir, QEMU_BIN_NAME); - return Bluebird.resolve(fs.promises.mkdir(binDir)) - .catch({ code: 'EEXIST' }, function () { - // noop + return fs.promises + .mkdir(binDir) + .catch(function (err) { + if (err.code === 'EEXIST') { + // ignore + return; + } + throw err; }) .then(() => getQemuPath(arch)) .then( @@ -65,9 +69,14 @@ export const getQemuPath = function (arch: string) { const { promises: fs } = require('fs') as typeof import('fs'); return balena.settings.get('binDirectory').then((binDir) => - Bluebird.resolve(fs.mkdir(binDir)) - .catch({ code: 'EEXIST' }, function () { - // noop + fs + .mkdir(binDir) + .catch(function (err) { + if (err.code === 'EEXIST') { + // ignore + return; + } + throw err; }) .then(() => path.join(binDir, `${QEMU_BIN_NAME}-${arch}-${QEMU_VERSION}`), diff --git a/lib/utils/tunnel.ts b/lib/utils/tunnel.ts index 088e3387..04bf64a5 100644 --- a/lib/utils/tunnel.ts +++ b/lib/utils/tunnel.ts @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ import type { BalenaSDK } from 'balena-sdk'; -import * as Bluebird from 'bluebird'; import { Socket } from 'net'; import { TypedError } from 'typed-error'; @@ -42,11 +41,11 @@ export const tunnelConnectionToDevice = ( port: number, sdk: BalenaSDK, ) => { - return Bluebird.props({ - vpnUrl: sdk.settings.get('vpnUrl'), - whoami: sdk.auth.whoami(), - token: sdk.auth.getToken(), - }).then(({ vpnUrl, whoami, token }) => { + return Promise.all([ + sdk.settings.get('vpnUrl'), + sdk.auth.whoami(), + sdk.auth.getToken(), + ]).then(([vpnUrl, whoami, token]) => { const auth = { user: whoami || 'root', password: token, diff --git a/typings/capitano/index.d.ts b/typings/capitano/index.d.ts index 054cc431..70212ea9 100644 --- a/typings/capitano/index.d.ts +++ b/typings/capitano/index.d.ts @@ -81,9 +81,13 @@ declare module 'capitano' { globalOptions: OptionDefinition[]; }; + export function run( + command: string, + callback: (err: Error | null, result: any) => void, + ): void; export function execute( args: any, - callback: (err?: any, result: any) => void, + callback: (err?: Error, result: any) => void, ): void; export function globalOption(option: OptionDefinition): void; export function permission(