Remove all module level bluebird usage from eagerly loaded modules

Change-type: patch
This commit is contained in:
Pagan Gazzard 2020-07-01 15:26:40 +01:00
parent a74815c1bb
commit 178c3f9154
14 changed files with 267 additions and 259 deletions

View File

@ -15,10 +15,6 @@
* limitations under the License. * 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 dockerUtils from '../utils/docker';
import * as compose from '../utils/compose'; import * as compose from '../utils/compose';
import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages'; import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages';
@ -39,7 +35,7 @@ import { getBalenaSdk } from '../utils/lazy';
*/ */
const buildProject = function (docker, logger, composeOpts, opts) { const buildProject = function (docker, logger, composeOpts, opts) {
const { loadProject } = require('../utils/compose_ts'); const { loadProject } = require('../utils/compose_ts');
return Bluebird.resolve(loadProject(logger, composeOpts)) return loadProject(logger, composeOpts)
.then(function (project) { .then(function (project) {
const appType = opts.app?.application_type?.[0]; const appType = opts.app?.application_type?.[0];
if ( if (
@ -74,8 +70,9 @@ const buildProject = function (docker, logger, composeOpts, opts) {
logger.outputDeferredMessages(); logger.outputDeferredMessages();
logger.logSuccess('Build succeeded!'); logger.logSuccess('Build succeeded!');
}) })
.tapCatch(() => { .catch((err) => {
logger.logError('Build failed'); 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 // compositions with many services trigger misleading warnings
// @ts-ignore editing property that isn't typed but does exist // @ts-ignore editing property that isn't typed but does exist
require('events').defaultMaxListeners = 1000; require('events').defaultMaxListeners = 1000;
@ -159,7 +156,6 @@ Examples:
const { application, arch, deviceType } = options; const { application, arch, deviceType } = options;
return Bluebird.try(function () {
if ( if (
(application == null && (arch == null || deviceType == null)) || (application == null && (arch == null || deviceType == null)) ||
(application != null && (arch != null || deviceType != null)) (application != null && (arch != null || deviceType != null))
@ -169,17 +165,15 @@ Examples:
); );
} }
if (application) { if (application) {
return checkLoggedIn(); await checkLoggedIn();
} }
})
.then(() => return validateProjectDirectory(sdk, {
validateProjectDirectory(sdk, {
dockerfilePath: options.dockerfile, dockerfilePath: options.dockerfile,
noParentCheck: options['noparent-check'] || false, noParentCheck: options['noparent-check'] || false,
projectPath: options.source || '.', projectPath: options.source || '.',
registrySecretsPath: options['registry-secrets'], registrySecretsPath: options['registry-secrets'],
}), })
)
.then(function ({ dockerfilePath, registrySecrets }) { .then(function ({ dockerfilePath, registrySecrets }) {
options.dockerfile = dockerfilePath; options.dockerfile = dockerfilePath;
options['registry-secrets'] = registrySecrets; options['registry-secrets'] = registrySecrets;

View File

@ -15,10 +15,6 @@
* limitations under the License. * 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 dockerUtils from '../utils/docker';
import * as compose from '../utils/compose'; import * as compose from '../utils/compose';
import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages'; import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages';
@ -41,6 +37,7 @@ import { getBalenaSdk, getChalk } from '../utils/lazy';
* @param {any} opts * @param {any} opts
*/ */
const deployProject = function (docker, logger, composeOpts, opts) { const deployProject = function (docker, logger, composeOpts, opts) {
const Bluebird = require('bluebird');
const _ = require('lodash'); const _ = require('lodash');
const doodles = require('resin-doodles'); const doodles = require('resin-doodles');
const sdk = getBalenaSdk(); const sdk = getBalenaSdk();
@ -49,7 +46,7 @@ const deployProject = function (docker, logger, composeOpts, opts) {
loadProject, loadProject,
} = require('../utils/compose_ts'); } = require('../utils/compose_ts');
return Bluebird.resolve(loadProject(logger, composeOpts, opts.image)) return loadProject(logger, composeOpts, opts.image)
.then(function (project) { .then(function (project) {
if ( if (
project.descriptors.length > 1 && project.descriptors.length > 1 &&
@ -178,8 +175,9 @@ const deployProject = function (docker, logger, composeOpts, opts) {
console.log(doodles.getDoodle()); // Show charlie console.log(doodles.getDoodle()); // Show charlie
console.log(); console.log();
}) })
.tapCatch(() => { .catch((err) => {
logger.logError('Deploy failed'); 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 // compositions with many services trigger misleading warnings
// @ts-ignore editing property that isn't typed but does exist // @ts-ignore editing property that isn't typed but does exist
require('events').defaultMaxListeners = 1000; require('events').defaultMaxListeners = 1000;
@ -268,7 +266,6 @@ Examples:
appName = appName_raw || appName || options.application; appName = appName_raw || appName || options.application;
delete options.application; delete options.application;
return Bluebird.try(function () {
if (appName == null) { if (appName == null) {
throw new ExpectedError( throw new ExpectedError(
'Please specify the name of the application to deploy', 'Please specify the name of the application to deploy',
@ -280,34 +277,35 @@ Examples:
'Build option is not applicable when specifying an image', 'Build option is not applicable when specifying an image',
); );
} }
})
.then(function () {
if (image) { if (image) {
return getRegistrySecrets(sdk, options['registry-secrets']).then( const registrySecrets = await getRegistrySecrets(
(registrySecrets) => { sdk,
options['registry-secrets'] = registrySecrets; options['registry-secrets'],
},
); );
options['registry-secrets'] = registrySecrets;
} else { } else {
return validateProjectDirectory(sdk, { const {
dockerfilePath,
registrySecrets,
} = await validateProjectDirectory(sdk, {
dockerfilePath: options.dockerfile, dockerfilePath: options.dockerfile,
noParentCheck: options['noparent-check'] || false, noParentCheck: options['noparent-check'] || false,
projectPath: options.source || '.', projectPath: options.source || '.',
registrySecretsPath: options['registry-secrets'], registrySecretsPath: options['registry-secrets'],
}).then(function ({ dockerfilePath, registrySecrets }) { });
options.dockerfile = dockerfilePath; options.dockerfile = dockerfilePath;
options['registry-secrets'] = registrySecrets; options['registry-secrets'] = registrySecrets;
});
} }
})
.then(() => helpers.getAppWithArch(appName)) const app = await helpers.getAppWithArch(appName);
.then(function (app) {
return Promise.all([ const [docker, buildOpts, composeOpts] = await Promise.all([
dockerUtils.getDocker(options), dockerUtils.getDocker(options),
dockerUtils.generateBuildOpts(options), dockerUtils.generateBuildOpts(options),
compose.generateOpts(options), compose.generateOpts(options),
]).then(([docker, buildOpts, composeOpts]) => ]);
deployProject(docker, logger, composeOpts, { await deployProject(docker, logger, composeOpts, {
app, app,
appName, // may be prefixed by 'owner/', unlike app.app_name appName, // may be prefixed by 'owner/', unlike app.app_name
image, image,
@ -315,8 +313,6 @@ Examples:
shouldUploadLogs: !options.nologupload, shouldUploadLogs: !options.nologupload,
buildEmulated: !!options.emulated, buildEmulated: !!options.emulated,
buildOpts, buildOpts,
}),
);
}); });
}, },
}; };

View File

@ -81,7 +81,7 @@ Examples:
return Bluebird.using(download(), (tempPath) => return Bluebird.using(download(), (tempPath) =>
runCommand(`device register ${application.app_name}`) runCommand(`device register ${application.app_name}`)
.then(balena.models.device.get) .then(balena.models.device.get)
.tap(function (device) { .then(function (device) {
let configureCommand = `os configure '${tempPath}' --device ${device.uuid}`; let configureCommand = `os configure '${tempPath}' --device ${device.uuid}`;
if (options.config) { if (options.config) {
configureCommand += ` --config '${options.config}'`; configureCommand += ` --config '${options.config}'`;
@ -103,7 +103,8 @@ Examples:
balena.models.device.remove(device.uuid).finally(function () { balena.models.device.remove(device.uuid).finally(function () {
throw error; throw error;
}), }),
); )
.then(() => device);
}), }),
).then(function (device) { ).then(function (device) {
console.log('Done'); console.log('Done');

View File

@ -312,8 +312,7 @@ ${INIT_WARNING_MESSAGE}\
`Going to erase ${answers.drive}.`, `Going to erase ${answers.drive}.`,
true, true,
) )
.return(answers.drive) .then(() => umountAsync(answers.drive));
.then(umountAsync);
}) })
.tap((answers) => .tap((answers) =>
helpers.sudo([ helpers.sudo([

View File

@ -14,11 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import * as Bluebird from 'bluebird';
import * as capitano from 'capitano'; import * as capitano from 'capitano';
import * as actions from './actions'; import * as actions from './actions';
import * as events from './events'; import * as events from './events';
import { promisify } from 'util';
capitano.permission('user', (done) => capitano.permission('user', (done) =>
require('./utils/patterns').checkLoggedIn().then(done, done), require('./utils/patterns').checkLoggedIn().then(done, done),
@ -76,17 +75,17 @@ capitano.command(actions.local.flash);
// ---------- Public utils ---------- // ---------- Public utils ----------
capitano.command(actions.util.availableDrives); capitano.command(actions.util.availableDrives);
//------------ Local build and deploy ------- // ------------ Local build and deploy -------
capitano.command(actions.build); capitano.command(actions.build);
capitano.command(actions.deploy); capitano.command(actions.deploy);
//------------ Push/remote builds ------- // ------------ Push/remote builds -------
capitano.command(actions.push.push); capitano.command(actions.push.push);
export function run(argv) { export function run(argv: string[]) {
const cli = capitano.parse(argv.slice(2)); const cli = capitano.parse(argv.slice(2));
const runCommand = function () { const runCommand = function () {
const capitanoExecuteAsync = Bluebird.promisify(capitano.execute); const capitanoExecuteAsync = promisify(capitano.execute);
if (cli.global?.help) { if (cli.global?.help) {
return capitanoExecuteAsync({ return capitanoExecuteAsync({
command: `help ${cli.command ?? ''}`, command: `help ${cli.command ?? ''}`,
@ -97,9 +96,7 @@ export function run(argv) {
}; };
const trackCommand = function () { const trackCommand = function () {
const getMatchCommandAsync = Bluebird.promisify( const getMatchCommandAsync = promisify(capitano.state.getMatchCommand);
capitano.state.getMatchCommand,
);
return getMatchCommandAsync(cli.command).then(function (command) { return getMatchCommandAsync(cli.command).then(function (command) {
// cmdSignature is literally a string like, for example: // cmdSignature is literally a string like, for example:
// "push <applicationOrDevice>" // "push <applicationOrDevice>"
@ -111,7 +108,7 @@ export function run(argv) {
}); });
}; };
return Bluebird.all([trackCommand(), runCommand()]).catch( return Promise.all([trackCommand(), runCommand()]).catch(
require('./errors').handleError, require('./errors').handleError,
); );
} }

View File

@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import * as Bluebird from 'bluebird';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as Mixpanel from 'mixpanel'; import * as Mixpanel from 'mixpanel';
@ -49,6 +48,7 @@ interface CachedUsername {
* (mainly unexpected/unhandled exceptions -- see also `lib/errors.ts`). * (mainly unexpected/unhandled exceptions -- see also `lib/errors.ts`).
*/ */
export async function trackCommand(commandSignature: string) { export async function trackCommand(commandSignature: string) {
try {
const Sentry = await import('@sentry/node'); const Sentry = await import('@sentry/node');
Sentry.configureScope((scope) => { Sentry.configureScope((scope) => {
scope.setExtra('command', commandSignature); scope.setExtra('command', commandSignature);
@ -56,8 +56,7 @@ export async function trackCommand(commandSignature: string) {
const settings = await import('balena-settings-client'); const settings = await import('balena-settings-client');
const balenaUrl = settings.get('balenaUrl') as string; const balenaUrl = settings.get('balenaUrl') as string;
return Bluebird.props({ const username = await (async () => {
username: (async () => {
const getStorage = await import('balena-settings-storage'); const getStorage = await import('balena-settings-storage');
const dataDirectory = settings.get('dataDirectory') as string; const dataDirectory = settings.get('dataDirectory') as string;
const storage = getStorage({ dataDirectory }); const storage = getStorage({ dataDirectory });
@ -78,26 +77,26 @@ export async function trackCommand(commandSignature: string) {
} }
try { try {
const balena = getBalenaSdk(); const balena = getBalenaSdk();
const username = await balena.auth.whoami(); const $username = await balena.auth.whoami();
storage.set('cachedUsername', { storage.set('cachedUsername', {
token, token,
username, username: $username,
} as CachedUsername); } as CachedUsername);
return username; return $username;
} catch { } catch {
return; return;
} }
})(), })();
mixpanel: getMixpanel(balenaUrl),
}) const mixpanel = getMixpanel(balenaUrl);
.then(({ username, mixpanel }) => {
Sentry.configureScope((scope) => { Sentry.configureScope((scope) => {
scope.setUser({ scope.setUser({
id: username, id: username,
username, username,
}); });
}); });
return mixpanel.track(`[CLI] ${commandSignature}`, { await mixpanel.track(`[CLI] ${commandSignature}`, {
distinct_id: username, distinct_id: username,
version: packageJSON.version, version: packageJSON.version,
node: process.version, node: process.version,
@ -105,7 +104,7 @@ export async function trackCommand(commandSignature: string) {
balenaUrl, // e.g. 'balena-cloud.com' or 'balena-staging.com' balenaUrl, // e.g. 'balena-cloud.com' or 'balena-staging.com'
platform: process.platform, platform: process.platform,
}); });
}) } catch {
.timeout(100) // ignore
.catchReturn(undefined); }
} }

View File

@ -15,7 +15,6 @@
* limitations under the License. * limitations under the License.
*/ */
import * as Bluebird from 'bluebird';
import * as path from 'path'; import * as path from 'path';
import { ExpectedError } from '../errors'; import { ExpectedError } from '../errors';
@ -196,12 +195,12 @@ export function createProject(composePath, composeStr, projectName = null) {
* optionally converting text file line endings (CRLF to LF). * optionally converting text file line endings (CRLF to LF).
* @param {string} dir Source directory * @param {string} dir Source directory
* @param {import('./compose-types').TarDirectoryOptions} param * @param {import('./compose-types').TarDirectoryOptions} param
* @returns {Bluebird<import('stream').Readable>} * @returns {Promise<import('stream').Readable>}
*/ */
export function tarDirectory(dir, param) { export function tarDirectory(dir, param) {
let { nogitignore = false } = param; let { nogitignore = false } = param;
if (nogitignore) { if (nogitignore) {
return Bluebird.resolve(require('./compose_ts').tarDirectory(dir, param)); return require('./compose_ts').tarDirectory(dir, param);
} else { } else {
return originalTarDirectory(dir, param); return originalTarDirectory(dir, param);
} }
@ -213,7 +212,7 @@ export function tarDirectory(dir, param) {
* deleted in CLI v13. * deleted in CLI v13.
* @param {string} dir Source directory * @param {string} dir Source directory
* @param {import('./compose-types').TarDirectoryOptions} param * @param {import('./compose-types').TarDirectoryOptions} param
* @returns {Bluebird<import('stream').Readable>} * @returns {Promise<import('stream').Readable>}
*/ */
function originalTarDirectory(dir, param) { function originalTarDirectory(dir, param) {
let { let {
@ -225,6 +224,7 @@ function originalTarDirectory(dir, param) {
convertEol = false; convertEol = false;
} }
const Bluebird = require('bluebird');
const tar = require('tar-stream'); const tar = require('tar-stream');
const klaw = require('klaw'); const klaw = require('klaw');
const { promises: fs } = require('fs'); const { promises: fs } = require('fs');
@ -323,6 +323,7 @@ export function buildProject(
nogitignore, nogitignore,
multiDockerignore, multiDockerignore,
) { ) {
const Bluebird = require('bluebird');
const _ = require('lodash'); const _ = require('lodash');
const humanize = require('humanize'); const humanize = require('humanize');
const compose = require('resin-compose-parse'); const compose = require('resin-compose-parse');
@ -377,12 +378,14 @@ export function buildProject(
.then(( .then((
needsQemu, // Tar up the directory, ready for the build stream needsQemu, // Tar up the directory, ready for the build stream
) => ) =>
Bluebird.resolve(
tarDirectory(projectPath, { tarDirectory(projectPath, {
composition, composition,
convertEol, convertEol,
multiDockerignore, multiDockerignore,
nogitignore, nogitignore,
}) }),
)
.then((tarStream) => .then((tarStream) =>
makeBuildTasks( makeBuildTasks(
composition, composition,
@ -555,7 +558,7 @@ export function buildProject(
* @param {number} userId * @param {number} userId
* @param {number} appId * @param {number} appId
* @param {import('resin-compose-parse').Composition} composition * @param {import('resin-compose-parse').Composition} composition
* @returns {Bluebird<import('./compose-types').Release>} * @returns {Promise<import('./compose-types').Release>}
*/ */
export const createRelease = function ( export const createRelease = function (
apiEndpoint, apiEndpoint,
@ -638,7 +641,7 @@ export const tagServiceImages = (docker, images, serviceImages) =>
* @param {import('docker-toolbelt')} docker * @param {import('docker-toolbelt')} docker
* @param {import('./logger')} logger * @param {import('./logger')} logger
* @param {number} appID * @param {number} appID
* @returns {Bluebird<string[]>} * @returns {Promise<string[]>}
*/ */
export const getPreviousRepos = (sdk, docker, logger, appID) => export const getPreviousRepos = (sdk, docker, logger, appID) =>
sdk.pine sdk.pine
@ -691,7 +694,7 @@ export const getPreviousRepos = (sdk, docker, logger, appID) =>
* @param {string} registry * @param {string} registry
* @param {string[]} images * @param {string[]} images
* @param {string[]} previousRepos * @param {string[]} previousRepos
* @returns {Bluebird<string>} * @returns {Promise<string>}
*/ */
export const authorizePush = function ( export const authorizePush = function (
sdk, sdk,
@ -734,6 +737,7 @@ export const pushAndUpdateServiceImages = function (
const { DockerProgress } = require('docker-progress'); const { DockerProgress } = require('docker-progress');
const { retry } = require('./helpers'); const { retry } = require('./helpers');
const tty = require('./tty')(process.stdout); const tty = require('./tty')(process.stdout);
const Bluebird = require('bluebird');
const opts = { authconfig: { registrytoken: token } }; const opts = { authconfig: { registrytoken: token } };

View File

@ -1,4 +1,3 @@
import * as Bluebird from 'bluebird';
import * as chokidar from 'chokidar'; import * as chokidar from 'chokidar';
import type * as Dockerode from 'dockerode'; import type * as Dockerode from 'dockerode';
import Livepush, { ContainerNotRunningError } from 'livepush'; import Livepush, { ContainerNotRunningError } from 'livepush';
@ -20,6 +19,7 @@ import {
} from './deploy'; } from './deploy';
import { BuildError } from './errors'; import { BuildError } from './errors';
import { getServiceColourFn } from './logs'; import { getServiceColourFn } from './logs';
import { delay } from '../helpers';
// How often do we want to check the device state // How often do we want to check the device state
// engine has settled (delay in ms) // engine has settled (delay in ms)
@ -251,7 +251,7 @@ export class LivepushManager {
this.logger.logDebug( this.logger.logDebug(
`Device state not settled, retrying in ${DEVICE_STATUS_SETTLE_CHECK_INTERVAL}ms`, `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(); await this.awaitDeviceStateSettle();
} }
@ -308,7 +308,7 @@ export class LivepushManager {
); );
await this.cancelRebuild(serviceName); await this.cancelRebuild(serviceName);
while (this.rebuildsCancelled[serviceName]) { while (this.rebuildsCancelled[serviceName]) {
await Bluebird.delay(1000); await delay(1000);
} }
} }

View File

@ -17,7 +17,6 @@
// Functions to help actions which rely on using docker // Functions to help actions which rely on using docker
import * as Bluebird from 'bluebird';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { ExpectedError } from '../errors'; import { ExpectedError } from '../errors';
@ -109,6 +108,7 @@ Implements the same feature as the "docker build --cache-from" option.`,
const generateConnectOpts = function (opts) { const generateConnectOpts = function (opts) {
const { promises: fs } = require('fs'); const { promises: fs } = require('fs');
const Bluebird = require('bluebird');
return Bluebird.try(function () { return Bluebird.try(function () {
const connectOpts = {}; const connectOpts = {};
@ -213,7 +213,7 @@ export function generateBuildOpts(options) {
* port?: number; * port?: number;
* timeout?: number; * timeout?: number;
* }} options * }} options
* @returns {Bluebird<import('docker-toolbelt')>} * @returns {Promise<import('docker-toolbelt')>}
*/ */
export function getDocker(options) { export function getDocker(options) {
return generateConnectOpts(options) return generateConnectOpts(options)
@ -223,6 +223,7 @@ export function getDocker(options) {
const getDockerToolbelt = _.once(function () { const getDockerToolbelt = _.once(function () {
const Docker = require('docker-toolbelt'); const Docker = require('docker-toolbelt');
const Bluebird = require('bluebird');
Bluebird.promisifyAll(Docker.prototype, { Bluebird.promisifyAll(Docker.prototype, {
filter(name) { filter(name) {
return name === 'run'; return name === 'run';

View File

@ -16,7 +16,6 @@ limitations under the License.
import type { InitializeEmitter, OperationState } from 'balena-device-init'; import type { InitializeEmitter, OperationState } from 'balena-device-init';
import type * as BalenaSdk from 'balena-sdk'; import type * as BalenaSdk from 'balena-sdk';
import * as Bluebird from 'bluebird';
import { spawn, SpawnOptions } from 'child_process'; import { spawn, SpawnOptions } from 'child_process';
import * as _ from 'lodash'; import * as _ from 'lodash';
import * as os from 'os'; import * as os from 'os';
@ -25,6 +24,7 @@ import type * as ShellEscape from 'shell-escape';
import type { Device, PineOptionsFor } from 'balena-sdk'; import type { Device, PineOptionsFor } from 'balena-sdk';
import { ExpectedError } from '../errors'; import { ExpectedError } from '../errors';
import { getBalenaSdk, getChalk, getVisuals } from './lazy'; import { getBalenaSdk, getChalk, getVisuals } from './lazy';
import { promisify } from 'util';
export function getGroupDefaults(group: { export function getGroupDefaults(group: {
options: Array<{ name: string; default?: string }>; options: Array<{ name: string; default?: string }>;
@ -95,9 +95,10 @@ export async function sudo(
await executeWithPrivileges(command, stderr, isCLIcmd); await executeWithPrivileges(command, stderr, isCLIcmd);
} }
export function runCommand<T>(command: string): Bluebird<T> { export function runCommand<T>(command: string): Promise<T> {
const capitano = require('capitano'); const capitano = require('capitano') as typeof import('capitano');
return Bluebird.fromCallback((resolver) => capitano.run(command, resolver)); const capitanoRunAsync = promisify(capitano.run);
return capitanoRunAsync(command);
} }
export async function getManifest( export async function getManifest(
@ -188,6 +189,8 @@ function getApplication(applicationName: string) {
return balena.models.application.get(applicationName, extraOptions); 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 * Call `func`, and if func() throws an error or returns a promise that
* eventually rejects, retry it `times` many times, each time printing a * 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 func: The function to call and, if needed, retry calling
* @param times: How many times to retry calling func() * @param times: How many times to retry calling func()
* @param label: Label to include in the retry log message * @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 backoffScaler: Multiplier to previous wait time
* @param count: Used "internally" for the recursive calls * @param count: Used "internally" for the recursive calls
*/ */
export function retry<T>( export async function retry<T>(
func: () => T, func: () => T,
times: number, times: number,
label: string, label: string,
delayMs = 1000, startingDelayMs = 1000,
backoffScaler = 2, backoffScaler = 2,
count = 0, ): Promise<T> {
): Bluebird<T> { for (let count = 0; count < times - 1; count++) {
let promise = Bluebird.try(func); try {
if (count < times) { return await func();
promise = promise.catch((err: Error) => { } catch (err) {
const delay = backoffScaler ** count * delayMs; const delayMS = backoffScaler ** count * startingDelayMs;
console.log( console.log(
`Retrying "${label}" after ${(delay / 1000).toFixed(2)}s (${ `Retrying "${label}" after ${(delayMS / 1000).toFixed(2)}s (${
count + 1 count + 1
} of ${times}) due to: ${err}`, } of ${times}) due to: ${err}`,
); );
return Bluebird.delay(delay).then(() => await delay(delayMS);
retry(func, times, label, delayMs, backoffScaler, count + 1),
);
});
} }
return promise; }
return await func();
} }
/** /**

View File

@ -15,7 +15,6 @@ limitations under the License.
*/ */
import { BalenaApplicationNotFound } from 'balena-errors'; import { BalenaApplicationNotFound } from 'balena-errors';
import type * as BalenaSdk from 'balena-sdk'; import type * as BalenaSdk from 'balena-sdk';
import Bluebird = require('bluebird');
import _ = require('lodash'); import _ = require('lodash');
import _form = require('resin-cli-form'); import _form = require('resin-cli-form');
@ -27,10 +26,11 @@ import {
} from '../errors'; } from '../errors';
import { getBalenaSdk, getVisuals, stripIndent } from './lazy'; import { getBalenaSdk, getVisuals, stripIndent } from './lazy';
import validation = require('./validation'); import validation = require('./validation');
import { delay } from './helpers';
const getForm = _.once((): typeof _form => require('resin-cli-form')); const getForm = _.once((): typeof _form => require('resin-cli-form'));
export function authenticate(options: {}): Bluebird<void> { export function authenticate(options: {}): Promise<void> {
const balena = getBalenaSdk(); const balena = getBalenaSdk();
return getForm() return getForm()
.run( .run(
@ -134,26 +134,25 @@ export function selectDeviceType() {
}); });
} }
export function confirm( export async function confirm(
yesOption: boolean, yesOption: boolean,
message: string, message: string,
yesMessage?: string, yesMessage?: string,
exitIfDeclined = false, exitIfDeclined = false,
) { ) {
return Bluebird.try(function () {
if (yesOption) { if (yesOption) {
if (yesMessage) { if (yesMessage) {
console.log(yesMessage); console.log(yesMessage);
} }
return true; return;
} }
return getForm().ask<boolean>({ const confirmed = await getForm().ask<boolean>({
message, message,
type: 'confirm', type: 'confirm',
default: false, default: false,
}); });
}).then(function (confirmed) {
if (!confirmed) { if (!confirmed) {
const err = new Error('Aborted'); const err = new Error('Aborted');
if (exitIfDeclined) { if (exitIfDeclined) {
@ -161,7 +160,6 @@ export function confirm(
} }
throw err; throw err;
} }
});
} }
export function selectApplication( export function selectApplication(
@ -234,16 +232,16 @@ export function selectOrCreateApplication() {
}); });
} }
export function awaitDevice(uuid: string) { export async function awaitDevice(uuid: string) {
const balena = getBalenaSdk(); const balena = getBalenaSdk();
return balena.models.device.getName(uuid).then((deviceName) => { const deviceName = await balena.models.device.getName(uuid);
const visuals = getVisuals(); const visuals = getVisuals();
const spinner = new visuals.Spinner( const spinner = new visuals.Spinner(
`Waiting for ${deviceName} to come online`, `Waiting for ${deviceName} to come online`,
); );
const poll = (): Bluebird<void> => { const poll = async (): Promise<void> => {
return balena.models.device.isOnline(uuid).then(function (isOnline) { const isOnline = await balena.models.device.isOnline(uuid);
if (isOnline) { if (isOnline) {
spinner.stop(); spinner.stop();
console.info(`The device **${deviceName}** is online!`); console.info(`The device **${deviceName}** is online!`);
@ -253,31 +251,37 @@ export function awaitDevice(uuid: string) {
// not start again if it was already started // not start again if it was already started
spinner.start(); spinner.start();
return Bluebird.delay(3000).then(poll); await delay(3000);
await poll();
} }
});
}; };
console.info(`Waiting for ${deviceName} to connect to balena...`); console.info(`Waiting for ${deviceName} to connect to balena...`);
return poll().return(uuid); await poll();
}); return uuid;
} }
export function awaitDeviceOsUpdate(uuid: string, targetOsVersion: string) { export async function awaitDeviceOsUpdate(
uuid: string,
targetOsVersion: string,
) {
const balena = getBalenaSdk(); const balena = getBalenaSdk();
return balena.models.device.getName(uuid).then((deviceName) => { const deviceName = await balena.models.device.getName(uuid);
const visuals = getVisuals(); const visuals = getVisuals();
const progressBar = new visuals.Progress( const progressBar = new visuals.Progress(
`Updating the OS of ${deviceName} to v${targetOsVersion}`, `Updating the OS of ${deviceName} to v${targetOsVersion}`,
); );
progressBar.update({ percentage: 0 }); progressBar.update({ percentage: 0 });
const poll = (): Bluebird<void> => { const poll = async (): Promise<void> => {
return Bluebird.all([ const [
osUpdateStatus,
{ overall_progress: osUpdateProgress },
] = await Promise.all([
balena.models.device.getOsUpdateStatus(uuid), balena.models.device.getOsUpdateStatus(uuid),
balena.models.device.get(uuid, { $select: 'overall_progress' }), balena.models.device.get(uuid, { $select: 'overall_progress' }),
]).then(([osUpdateStatus, { overall_progress: osUpdateProgress }]) => { ]);
if (osUpdateStatus.status === 'done') { if (osUpdateStatus.status === 'done') {
console.info( console.info(
`The device ${deviceName} has been updated to v${targetOsVersion} and will restart shortly!`, `The device ${deviceName} has been updated to v${targetOsVersion} and will restart shortly!`,
@ -298,12 +302,12 @@ export function awaitDeviceOsUpdate(uuid: string, targetOsVersion: string) {
progressBar.update({ percentage: osUpdateProgress }); 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) { export function inferOrSelectDevice(preferredUuid: string) {
@ -411,7 +415,7 @@ export async function getOnlineTargetUuid(
export function selectFromList<T>( export function selectFromList<T>(
message: string, message: string,
choices: Array<T & { name: string }>, choices: Array<T & { name: string }>,
): Bluebird<T> { ): Promise<T> {
return getForm().ask<T>({ return getForm().ask<T>({
message, message,
type: 'list', type: 'list',

View File

@ -15,7 +15,6 @@
* limitations under the License. * limitations under the License.
*/ */
import * as Bluebird from 'bluebird';
import type * as Dockerode from 'dockerode'; import type * as Dockerode from 'dockerode';
import { getBalenaSdk, stripIndent } from './lazy'; import { getBalenaSdk, stripIndent } from './lazy';
import Logger = require('./logger'); import Logger = require('./logger');
@ -37,9 +36,14 @@ export function copyQemu(context: string, arch: string) {
const binDir = path.join(context, '.balena'); const binDir = path.join(context, '.balena');
const binPath = path.join(binDir, QEMU_BIN_NAME); const binPath = path.join(binDir, QEMU_BIN_NAME);
return Bluebird.resolve(fs.promises.mkdir(binDir)) return fs.promises
.catch({ code: 'EEXIST' }, function () { .mkdir(binDir)
// noop .catch(function (err) {
if (err.code === 'EEXIST') {
// ignore
return;
}
throw err;
}) })
.then(() => getQemuPath(arch)) .then(() => getQemuPath(arch))
.then( .then(
@ -65,9 +69,14 @@ export const getQemuPath = function (arch: string) {
const { promises: fs } = require('fs') as typeof import('fs'); const { promises: fs } = require('fs') as typeof import('fs');
return balena.settings.get('binDirectory').then((binDir) => return balena.settings.get('binDirectory').then((binDir) =>
Bluebird.resolve(fs.mkdir(binDir)) fs
.catch({ code: 'EEXIST' }, function () { .mkdir(binDir)
// noop .catch(function (err) {
if (err.code === 'EEXIST') {
// ignore
return;
}
throw err;
}) })
.then(() => .then(() =>
path.join(binDir, `${QEMU_BIN_NAME}-${arch}-${QEMU_VERSION}`), path.join(binDir, `${QEMU_BIN_NAME}-${arch}-${QEMU_VERSION}`),

View File

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import type { BalenaSDK } from 'balena-sdk'; import type { BalenaSDK } from 'balena-sdk';
import * as Bluebird from 'bluebird';
import { Socket } from 'net'; import { Socket } from 'net';
import { TypedError } from 'typed-error'; import { TypedError } from 'typed-error';
@ -42,11 +41,11 @@ export const tunnelConnectionToDevice = (
port: number, port: number,
sdk: BalenaSDK, sdk: BalenaSDK,
) => { ) => {
return Bluebird.props({ return Promise.all([
vpnUrl: sdk.settings.get('vpnUrl'), sdk.settings.get('vpnUrl'),
whoami: sdk.auth.whoami(), sdk.auth.whoami(),
token: sdk.auth.getToken(), sdk.auth.getToken(),
}).then(({ vpnUrl, whoami, token }) => { ]).then(([vpnUrl, whoami, token]) => {
const auth = { const auth = {
user: whoami || 'root', user: whoami || 'root',
password: token, password: token,

View File

@ -81,9 +81,13 @@ declare module 'capitano' {
globalOptions: OptionDefinition[]; globalOptions: OptionDefinition[];
}; };
export function run(
command: string,
callback: (err: Error | null, result: any) => void,
): void;
export function execute( export function execute(
args: any, args: any,
callback: (err?: any, result: any) => void, callback: (err?: Error, result: any) => void,
): void; ): void;
export function globalOption(option: OptionDefinition): void; export function globalOption(option: OptionDefinition): void;
export function permission( export function permission(