mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-02-07 11:31:15 +00:00
Merge pull request #2312 from balena-io/remove-exitWithExpectedError
build, deploy: Extend CTRL-C coverage on Windows (PowerShell, cmd.exe)
This commit is contained in:
commit
8db36ccec9
@ -101,7 +101,7 @@ async function printMarkdown() {
|
|||||||
console.log(await renderMarkdown());
|
console.log(await renderMarkdown());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
process.exit(1);
|
process.exitCode = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,17 +41,25 @@ function checkNpmVersion() {
|
|||||||
// the reason is that it would unnecessarily prevent end users from
|
// the reason is that it would unnecessarily prevent end users from
|
||||||
// using npm v6.4.1 that ships with Node 8. (It is OK for the
|
// using npm v6.4.1 that ships with Node 8. (It is OK for the
|
||||||
// shrinkwrap file to get damaged if it is not going to be reused.)
|
// shrinkwrap file to get damaged if it is not going to be reused.)
|
||||||
console.error(`\
|
throw new Error(`\
|
||||||
-------------------------------------------------------------------------------
|
-----------------------------------------------------------------------------
|
||||||
Error: npm version '${npmVersion}' detected. Please upgrade to npm v${requiredVersion} or later
|
Error: npm version '${npmVersion}' detected. Please upgrade to npm v${requiredVersion} or later
|
||||||
because of a bug that causes the 'npm-shrinkwrap.json' file to be damaged.
|
because of a bug that causes the 'npm-shrinkwrap.json' file to be damaged.
|
||||||
At this point, however, your 'npm-shrinkwrap.json' file has already been
|
At this point, however, your 'npm-shrinkwrap.json' file has already been
|
||||||
damaged. Please revert it to the master branch state with a command such as:
|
damaged. Please revert it to the master branch state with a command such as:
|
||||||
"git checkout master -- npm-shrinkwrap.json"
|
"git checkout master -- npm-shrinkwrap.json"
|
||||||
Then re-run "npm install" using npm version ${requiredVersion} or later.
|
Then re-run "npm install" using npm version ${requiredVersion} or later.
|
||||||
-------------------------------------------------------------------------------`);
|
-----------------------------------------------------------------------------`);
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkNpmVersion();
|
function main() {
|
||||||
|
try {
|
||||||
|
checkNpmVersion();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e.message || e);
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
@ -54,9 +54,7 @@ export async function release() {
|
|||||||
try {
|
try {
|
||||||
await createGitHubRelease();
|
await createGitHubRelease();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Release failed');
|
throw new Error(`Error creating GitHub release:\n${err}`);
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,11 +35,6 @@ process.env.DEBUG = ['0', 'no', 'false', '', undefined].includes(
|
|||||||
? ''
|
? ''
|
||||||
: '1';
|
: '1';
|
||||||
|
|
||||||
function exitWithError(error: Error | string): never {
|
|
||||||
console.error(`Error: ${error}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trivial command-line parser. Check whether the command-line argument is one
|
* Trivial command-line parser. Check whether the command-line argument is one
|
||||||
* of the following strings, then call the appropriate functions:
|
* of the following strings, then call the appropriate functions:
|
||||||
@ -49,12 +44,12 @@ function exitWithError(error: Error | string): never {
|
|||||||
*
|
*
|
||||||
* @param args Arguments to parse (default is process.argv.slice(2))
|
* @param args Arguments to parse (default is process.argv.slice(2))
|
||||||
*/
|
*/
|
||||||
export async function run(args?: string[]) {
|
async function parse(args?: string[]) {
|
||||||
args = args || process.argv.slice(2);
|
args = args || process.argv.slice(2);
|
||||||
console.log(`automation/run.ts process.argv=[${process.argv}]\n`);
|
console.error(`[debug] automation/run.ts process.argv=[${process.argv}]`);
|
||||||
console.log(`automation/run.ts args=[${args}]`);
|
console.error(`[debug] automation/run.ts args=[${args}]`);
|
||||||
if (_.isEmpty(args)) {
|
if (_.isEmpty(args)) {
|
||||||
return exitWithError('missing command-line arguments');
|
throw new Error('missing command-line arguments');
|
||||||
}
|
}
|
||||||
const commands: { [cmd: string]: () => void | Promise<void> } = {
|
const commands: { [cmd: string]: () => void | Promise<void> } = {
|
||||||
'build:installer': buildOclifInstaller,
|
'build:installer': buildOclifInstaller,
|
||||||
@ -66,7 +61,7 @@ export async function run(args?: string[]) {
|
|||||||
};
|
};
|
||||||
for (const arg of args) {
|
for (const arg of args) {
|
||||||
if (!commands.hasOwnProperty(arg)) {
|
if (!commands.hasOwnProperty(arg)) {
|
||||||
return exitWithError(`command unknown: ${arg}`);
|
throw new Error(`command unknown: ${arg}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,9 +85,22 @@ export async function run(args?: string[]) {
|
|||||||
const cmdFunc = commands[arg];
|
const cmdFunc = commands[arg];
|
||||||
await cmdFunc();
|
await cmdFunc();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return exitWithError(`"${arg}": ${err}`);
|
if (typeof err === 'object') {
|
||||||
|
err.message = `"${arg}": ${err.message}`;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** See jsdoc for parse() function above */
|
||||||
|
export async function run(args?: string[]) {
|
||||||
|
try {
|
||||||
|
await parse(args);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e.message ? `Error: ${e.message}` : e);
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
run();
|
run();
|
||||||
|
@ -11,8 +11,7 @@ const validateChangeType = (maybeChangeType: string = 'minor') => {
|
|||||||
case 'major':
|
case 'major':
|
||||||
return maybeChangeType;
|
return maybeChangeType;
|
||||||
default:
|
default:
|
||||||
console.error(`Invalid change type: '${maybeChangeType}'`);
|
throw new Error(`Invalid change type: '${maybeChangeType}'`);
|
||||||
return process.exit(1);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -65,24 +64,17 @@ const getUpstreams = async () => {
|
|||||||
return upstream;
|
return upstream;
|
||||||
};
|
};
|
||||||
|
|
||||||
const printUsage = (upstreams: Upstream[], upstreamName: string) => {
|
const getUsage = (upstreams: Upstream[], upstreamName: string) => `
|
||||||
console.error(
|
|
||||||
`
|
|
||||||
Usage: npm run update ${upstreamName} $version [$changeType=minor]
|
Usage: npm run update ${upstreamName} $version [$changeType=minor]
|
||||||
|
|
||||||
Upstream names: ${upstreams.map(({ repo }) => repo).join(', ')}
|
Upstream names: ${upstreams.map(({ repo }) => repo).join(', ')}
|
||||||
`,
|
`;
|
||||||
);
|
|
||||||
return process.exit(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Drop the wrapper function once we move to TS 3.8,
|
async function $main() {
|
||||||
// which will support top level await.
|
|
||||||
async function main() {
|
|
||||||
const upstreams = await getUpstreams();
|
const upstreams = await getUpstreams();
|
||||||
|
|
||||||
if (process.argv.length < 3) {
|
if (process.argv.length < 3) {
|
||||||
return printUsage(upstreams, '$upstreamName');
|
throw new Error(getUsage(upstreams, '$upstreamName'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const upstreamName = process.argv[2];
|
const upstreamName = process.argv[2];
|
||||||
@ -90,16 +82,15 @@ async function main() {
|
|||||||
const upstream = upstreams.find((v) => v.repo === upstreamName);
|
const upstream = upstreams.find((v) => v.repo === upstreamName);
|
||||||
|
|
||||||
if (!upstream) {
|
if (!upstream) {
|
||||||
console.error(
|
throw new Error(
|
||||||
`Invalid upstream name '${upstreamName}', valid options: ${upstreams
|
`Invalid upstream name '${upstreamName}', valid options: ${upstreams
|
||||||
.map(({ repo }) => repo)
|
.map(({ repo }) => repo)
|
||||||
.join(', ')}`,
|
.join(', ')}`,
|
||||||
);
|
);
|
||||||
return process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.argv.length < 4) {
|
if (process.argv.length < 4) {
|
||||||
printUsage(upstreams, upstreamName);
|
throw new Error(getUsage(upstreams, upstreamName));
|
||||||
}
|
}
|
||||||
|
|
||||||
const packageName = upstream.module || upstream.repo;
|
const packageName = upstream.module || upstream.repo;
|
||||||
@ -108,8 +99,7 @@ async function main() {
|
|||||||
await run(`npm install ${packageName}@${process.argv[3]}`);
|
await run(`npm install ${packageName}@${process.argv[3]}`);
|
||||||
const newVersion = await getVersion(packageName);
|
const newVersion = await getVersion(packageName);
|
||||||
if (newVersion === oldVersion) {
|
if (newVersion === oldVersion) {
|
||||||
console.error(`Already on version '${newVersion}'`);
|
throw new Error(`Already on version '${newVersion}'`);
|
||||||
return process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Updated ${upstreamName} from ${oldVersion} to ${newVersion}`);
|
console.log(`Updated ${upstreamName} from ${oldVersion} to ${newVersion}`);
|
||||||
@ -137,4 +127,13 @@ async function main() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
await $main();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
process.exitCode = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
2
lib/commands/env/rm.ts
vendored
2
lib/commands/env/rm.ts
vendored
@ -91,8 +91,6 @@ export default class EnvRmCmd extends Command {
|
|||||||
await confirm(
|
await confirm(
|
||||||
opt.yes || false,
|
opt.yes || false,
|
||||||
'Are you sure you want to delete the environment variable?',
|
'Are you sure you want to delete the environment variable?',
|
||||||
undefined,
|
|
||||||
true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
|
@ -20,12 +20,7 @@ import type { BlockDevice } from 'etcher-sdk/build/source-destination';
|
|||||||
import Command from '../../command';
|
import Command from '../../command';
|
||||||
import { ExpectedError } from '../../errors';
|
import { ExpectedError } from '../../errors';
|
||||||
import * as cf from '../../utils/common-flags';
|
import * as cf from '../../utils/common-flags';
|
||||||
import {
|
import { getChalk, getVisuals, stripIndent } from '../../utils/lazy';
|
||||||
getChalk,
|
|
||||||
getCliForm,
|
|
||||||
getVisuals,
|
|
||||||
stripIndent,
|
|
||||||
} from '../../utils/lazy';
|
|
||||||
|
|
||||||
interface FlagsDef {
|
interface FlagsDef {
|
||||||
yes: boolean;
|
yes: boolean;
|
||||||
@ -93,24 +88,15 @@ export default class LocalFlashCmd extends Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { sourceDestination, multiWrite } = await import('etcher-sdk');
|
|
||||||
|
|
||||||
const drive = await this.getDrive(options);
|
const drive = await this.getDrive(options);
|
||||||
|
|
||||||
const yes =
|
const { confirm } = await import('../../utils/patterns');
|
||||||
options.yes ||
|
await confirm(
|
||||||
(await getCliForm().ask({
|
options.yes,
|
||||||
message: 'This will erase the selected drive. Are you sure?',
|
'This will erase the selected drive. Are you sure?',
|
||||||
type: 'confirm',
|
);
|
||||||
name: 'yes',
|
|
||||||
default: false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!yes) {
|
|
||||||
console.log(getChalk().red.bold('Aborted image flash'));
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const { sourceDestination, multiWrite } = await import('etcher-sdk');
|
||||||
const file = new sourceDestination.File({
|
const file = new sourceDestination.File({
|
||||||
path: params.image,
|
path: params.image,
|
||||||
});
|
});
|
||||||
|
@ -92,7 +92,6 @@ export default class OsInitializeCmd extends Command {
|
|||||||
options.yes,
|
options.yes,
|
||||||
`This will erase ${answers.drive}. Are you sure?`,
|
`This will erase ${answers.drive}. Are you sure?`,
|
||||||
`Going to erase ${answers.drive}.`,
|
`Going to erase ${answers.drive}.`,
|
||||||
true,
|
|
||||||
);
|
);
|
||||||
const { safeUmount } = await import('../../utils/umount');
|
const { safeUmount } = await import('../../utils/umount');
|
||||||
await safeUmount(answers.drive);
|
await safeUmount(answers.drive);
|
||||||
|
@ -286,24 +286,3 @@ export const printErrorMessage = function (message: string) {
|
|||||||
export const printExpectedErrorMessage = function (message: string) {
|
export const printExpectedErrorMessage = function (message: string) {
|
||||||
console.error(`${message}\n`);
|
console.error(`${message}\n`);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Print a friendly error message and exit the CLI with an error code, BYPASSING
|
|
||||||
* error reporting through Sentry.io's platform (raven.Raven.captureException).
|
|
||||||
* Note that lib/errors.ts provides top-level error handling code to catch any
|
|
||||||
* otherwise uncaught errors, AND to report them through Sentry.io. But many
|
|
||||||
* "expected" errors (say, a JSON parsing error in a file provided by the user)
|
|
||||||
* don't warrant reporting through Sentry.io. For such mundane errors, catch
|
|
||||||
* them and call this function.
|
|
||||||
*
|
|
||||||
* DEPRECATED: Use `throw new ExpectedError(<message>)` instead.
|
|
||||||
* If a specific process exit code x must be set, use process.exitCode = x
|
|
||||||
*/
|
|
||||||
export function exitWithExpectedError(message: string | Error): never {
|
|
||||||
if (message instanceof Error) {
|
|
||||||
({ message } = message);
|
|
||||||
}
|
|
||||||
|
|
||||||
printErrorMessage(message);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
@ -447,7 +447,6 @@ var pushProgressRenderer = function (tty, prefix) {
|
|||||||
export class BuildProgressUI {
|
export class BuildProgressUI {
|
||||||
constructor(tty, descriptors) {
|
constructor(tty, descriptors) {
|
||||||
this._handleEvent = this._handleEvent.bind(this);
|
this._handleEvent = this._handleEvent.bind(this);
|
||||||
this._handleInterrupt = this._handleInterrupt.bind(this);
|
|
||||||
this.start = this.start.bind(this);
|
this.start = this.start.bind(this);
|
||||||
this.end = this.end.bind(this);
|
this.end = this.end.bind(this);
|
||||||
this._display = this._display.bind(this);
|
this._display = this._display.bind(this);
|
||||||
@ -499,14 +498,7 @@ export class BuildProgressUI {
|
|||||||
this._serviceToDataMap[service] = event;
|
this._serviceToDataMap[service] = event;
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleInterrupt() {
|
|
||||||
this._cancelled = true;
|
|
||||||
this.end();
|
|
||||||
return process.exit(130); // 128 + SIGINT
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
process.on('SIGINT', this._handleInterrupt);
|
|
||||||
this._tty.hideCursor();
|
this._tty.hideCursor();
|
||||||
this._services.forEach((service) => {
|
this._services.forEach((service) => {
|
||||||
this.streams[service].write({ status: 'Preparing...' });
|
this.streams[service].write({ status: 'Preparing...' });
|
||||||
@ -520,7 +512,6 @@ export class BuildProgressUI {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._ended = true;
|
this._ended = true;
|
||||||
process.removeListener('SIGINT', this._handleInterrupt);
|
|
||||||
this._runloop?.end();
|
this._runloop?.end();
|
||||||
this._runloop = null;
|
this._runloop = null;
|
||||||
|
|
||||||
|
@ -237,7 +237,7 @@ interface Renderer {
|
|||||||
streams: Dictionary<NodeJS.ReadWriteStream>;
|
streams: Dictionary<NodeJS.ReadWriteStream>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function buildProject(opts: {
|
export interface BuildProjectOpts {
|
||||||
docker: Dockerode;
|
docker: Dockerode;
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
projectPath: string;
|
projectPath: string;
|
||||||
@ -252,82 +252,99 @@ export async function buildProject(opts: {
|
|||||||
dockerfilePath?: string;
|
dockerfilePath?: string;
|
||||||
nogitignore: boolean;
|
nogitignore: boolean;
|
||||||
multiDockerignore: boolean;
|
multiDockerignore: boolean;
|
||||||
}): Promise<BuiltImage[]> {
|
}
|
||||||
const { logger, projectName } = opts;
|
|
||||||
logger.logInfo(`Building for ${opts.arch}/${opts.deviceType}`);
|
|
||||||
|
|
||||||
let buildSummaryByService: Dictionary<string> | undefined;
|
export async function buildProject(
|
||||||
|
opts: BuildProjectOpts,
|
||||||
|
): Promise<BuiltImage[]> {
|
||||||
|
await checkBuildSecretsRequirements(opts.docker, opts.projectPath);
|
||||||
const compose = await import('resin-compose-parse');
|
const compose = await import('resin-compose-parse');
|
||||||
const imageDescriptors = compose.parse(opts.composition);
|
const imageDescriptors = compose.parse(opts.composition);
|
||||||
const imageDescriptorsByServiceName = _.keyBy(
|
|
||||||
imageDescriptors,
|
|
||||||
'serviceName',
|
|
||||||
);
|
|
||||||
const renderer = await startRenderer({ imageDescriptors, ...opts });
|
const renderer = await startRenderer({ imageDescriptors, ...opts });
|
||||||
|
let buildSummaryByService: Dictionary<string> | undefined;
|
||||||
try {
|
try {
|
||||||
await checkBuildSecretsRequirements(opts.docker, opts.projectPath);
|
const { awaitInterruptibleTask } = await import('./helpers');
|
||||||
|
const [images, summaryMsgByService] = await awaitInterruptibleTask(
|
||||||
const needsQemu = await installQemuIfNeeded({ ...opts, imageDescriptors });
|
$buildProject,
|
||||||
|
imageDescriptors,
|
||||||
const tarStream = await tarDirectory(opts.projectPath, opts);
|
renderer,
|
||||||
|
|
||||||
const tasks: BuildTaskPlus[] = await makeBuildTasks(
|
|
||||||
opts.composition,
|
|
||||||
tarStream,
|
|
||||||
opts,
|
opts,
|
||||||
logger,
|
|
||||||
projectName,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
setTaskAttributes({ tasks, imageDescriptorsByServiceName, ...opts });
|
|
||||||
|
|
||||||
const transposeOptArray: Array<TransposeOptions | undefined> =
|
|
||||||
await Promise.all(
|
|
||||||
tasks.map((task) => {
|
|
||||||
// Setup emulation if needed
|
|
||||||
if (needsQemu && !task.external) {
|
|
||||||
return qemuTransposeBuildStream({ task, ...opts });
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
// transposeOptions may be undefined. That's OK.
|
|
||||||
transposeOptArray.map((transposeOptions, index) =>
|
|
||||||
setTaskProgressHooks({
|
|
||||||
task: tasks[index],
|
|
||||||
renderer,
|
|
||||||
transposeOptions,
|
|
||||||
...opts,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.logDebug('Prepared tasks; building...');
|
|
||||||
|
|
||||||
const { BALENA_ENGINE_TMP_PATH } = await import('../config');
|
|
||||||
const builder = await import('resin-multibuild');
|
|
||||||
|
|
||||||
const builtImages = await builder.performBuilds(
|
|
||||||
tasks,
|
|
||||||
opts.docker,
|
|
||||||
BALENA_ENGINE_TMP_PATH,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [images, summaryMsgByService] = await inspectBuiltImages({
|
|
||||||
builtImages,
|
|
||||||
imageDescriptorsByServiceName,
|
|
||||||
tasks,
|
|
||||||
...opts,
|
|
||||||
});
|
|
||||||
buildSummaryByService = summaryMsgByService;
|
buildSummaryByService = summaryMsgByService;
|
||||||
|
|
||||||
return images;
|
return images;
|
||||||
} finally {
|
} finally {
|
||||||
renderer.end(buildSummaryByService);
|
renderer.end(buildSummaryByService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function $buildProject(
|
||||||
|
imageDescriptors: ImageDescriptor[],
|
||||||
|
renderer: Renderer,
|
||||||
|
opts: BuildProjectOpts,
|
||||||
|
): Promise<[BuiltImage[], Dictionary<string>]> {
|
||||||
|
const { logger, projectName } = opts;
|
||||||
|
logger.logInfo(`Building for ${opts.arch}/${opts.deviceType}`);
|
||||||
|
|
||||||
|
const needsQemu = await installQemuIfNeeded({ ...opts, imageDescriptors });
|
||||||
|
|
||||||
|
const tarStream = await tarDirectory(opts.projectPath, opts);
|
||||||
|
|
||||||
|
const tasks: BuildTaskPlus[] = await makeBuildTasks(
|
||||||
|
opts.composition,
|
||||||
|
tarStream,
|
||||||
|
opts,
|
||||||
|
logger,
|
||||||
|
projectName,
|
||||||
|
);
|
||||||
|
|
||||||
|
const imageDescriptorsByServiceName = _.keyBy(
|
||||||
|
imageDescriptors,
|
||||||
|
'serviceName',
|
||||||
|
);
|
||||||
|
|
||||||
|
setTaskAttributes({ tasks, imageDescriptorsByServiceName, ...opts });
|
||||||
|
|
||||||
|
const transposeOptArray: Array<TransposeOptions | undefined> =
|
||||||
|
await Promise.all(
|
||||||
|
tasks.map((task) => {
|
||||||
|
// Setup emulation if needed
|
||||||
|
if (needsQemu && !task.external) {
|
||||||
|
return qemuTransposeBuildStream({ task, ...opts });
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
// transposeOptions may be undefined. That's OK.
|
||||||
|
transposeOptArray.map((transposeOptions, index) =>
|
||||||
|
setTaskProgressHooks({
|
||||||
|
task: tasks[index],
|
||||||
|
renderer,
|
||||||
|
transposeOptions,
|
||||||
|
...opts,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.logDebug('Prepared tasks; building...');
|
||||||
|
|
||||||
|
const { BALENA_ENGINE_TMP_PATH } = await import('../config');
|
||||||
|
const builder = await import('resin-multibuild');
|
||||||
|
|
||||||
|
const builtImages = await builder.performBuilds(
|
||||||
|
tasks,
|
||||||
|
opts.docker,
|
||||||
|
BALENA_ENGINE_TMP_PATH,
|
||||||
|
);
|
||||||
|
|
||||||
|
return await inspectBuiltImages({
|
||||||
|
builtImages,
|
||||||
|
imageDescriptorsByServiceName,
|
||||||
|
tasks,
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function startRenderer({
|
async function startRenderer({
|
||||||
imageDescriptors,
|
imageDescriptors,
|
||||||
inlineLogs,
|
inlineLogs,
|
||||||
@ -1344,20 +1361,25 @@ export async function deployProject(
|
|||||||
logger.logDebug('Tagging images...');
|
logger.logDebug('Tagging images...');
|
||||||
const taggedImages = await tagServiceImages(docker, images, serviceImages);
|
const taggedImages = await tagServiceImages(docker, images, serviceImages);
|
||||||
try {
|
try {
|
||||||
const token = await getTokenForPreviousRepos(
|
const { awaitInterruptibleTask } = await import('./helpers');
|
||||||
logger,
|
// awaitInterruptibleTask throws SIGINTError on CTRL-C,
|
||||||
appId,
|
// causing the release status to be set to 'failed'
|
||||||
apiEndpoint,
|
await awaitInterruptibleTask(async () => {
|
||||||
taggedImages,
|
const token = await getTokenForPreviousRepos(
|
||||||
);
|
logger,
|
||||||
await pushServiceImages(
|
appId,
|
||||||
docker,
|
apiEndpoint,
|
||||||
logger,
|
taggedImages,
|
||||||
pineClient,
|
);
|
||||||
taggedImages,
|
await pushServiceImages(
|
||||||
token,
|
docker,
|
||||||
skipLogUpload,
|
logger,
|
||||||
);
|
pineClient,
|
||||||
|
taggedImages,
|
||||||
|
token,
|
||||||
|
skipLogUpload,
|
||||||
|
);
|
||||||
|
});
|
||||||
release.status = 'success';
|
release.status = 'success';
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
release.status = 'failed';
|
release.status = 'failed';
|
||||||
|
@ -16,18 +16,12 @@ limitations under the License.
|
|||||||
import type * as BalenaSdk from 'balena-sdk';
|
import type * as BalenaSdk from 'balena-sdk';
|
||||||
import _ = require('lodash');
|
import _ = require('lodash');
|
||||||
|
|
||||||
import {
|
import { instanceOf, NotLoggedInError, ExpectedError } from '../errors';
|
||||||
exitWithExpectedError,
|
|
||||||
instanceOf,
|
|
||||||
NotLoggedInError,
|
|
||||||
ExpectedError,
|
|
||||||
} from '../errors';
|
|
||||||
import { getBalenaSdk, getVisuals, stripIndent, getCliForm } from './lazy';
|
import { getBalenaSdk, getVisuals, stripIndent, getCliForm } from './lazy';
|
||||||
import validation = require('./validation');
|
import validation = require('./validation');
|
||||||
import { delay } from './helpers';
|
import { delay } from './helpers';
|
||||||
import { isV13 } from './version';
|
import { isV13 } from './version';
|
||||||
import type { Application, Device, Organization } from 'balena-sdk';
|
import type { Application, Device, Organization } from 'balena-sdk';
|
||||||
import { getApplication } from './sdk';
|
|
||||||
|
|
||||||
export function authenticate(options: {}): Promise<void> {
|
export function authenticate(options: {}): Promise<void> {
|
||||||
const balena = getBalenaSdk();
|
const balena = getBalenaSdk();
|
||||||
@ -135,18 +129,16 @@ export function selectDeviceType() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Display interactive confirmation prompt.
|
* Display interactive confirmation prompt.
|
||||||
* If the user declines, then either an error will be thrown,
|
* Throw ExpectedError if the user declines.
|
||||||
* or `exitWithExpectedError` will be called (if exitIfDeclined true).
|
|
||||||
* @param yesOption - automatically confirm if true
|
* @param yesOption - automatically confirm if true
|
||||||
* @param message - message to display with prompt
|
* @param message - message to display with prompt
|
||||||
* @param yesMessage - message to display if automatically confirming
|
* @param yesMessage - message to display if automatically confirming
|
||||||
* @param exitIfDeclined - exitWithExpectedError when decline if true
|
|
||||||
*/
|
*/
|
||||||
export async function confirm(
|
export async function confirm(
|
||||||
yesOption: boolean,
|
yesOption: boolean,
|
||||||
message: string,
|
message: string,
|
||||||
yesMessage?: string,
|
yesMessage?: string,
|
||||||
exitIfDeclined = false,
|
defaultValue = false,
|
||||||
) {
|
) {
|
||||||
if (yesOption) {
|
if (yesOption) {
|
||||||
if (yesMessage) {
|
if (yesMessage) {
|
||||||
@ -162,16 +154,11 @@ export async function confirm(
|
|||||||
const confirmed = await getCliForm().ask<boolean>({
|
const confirmed = await getCliForm().ask<boolean>({
|
||||||
message,
|
message,
|
||||||
type: 'confirm',
|
type: 'confirm',
|
||||||
default: false,
|
default: defaultValue,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!confirmed) {
|
if (!confirmed) {
|
||||||
const err = new ExpectedError('Aborted');
|
throw new ExpectedError('Aborted');
|
||||||
// TODO remove this deprecated function (exitWithExpectedError)
|
|
||||||
if (exitIfDeclined) {
|
|
||||||
exitWithExpectedError(err);
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,11 +268,9 @@ export async function awaitDeviceOsUpdate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (osUpdateStatus.error) {
|
if (osUpdateStatus.error) {
|
||||||
console.error(
|
throw new ExpectedError(
|
||||||
`Failed to complete Host OS update on device ${deviceName}!`,
|
`Failed to complete Host OS update on device ${deviceName}\n${osUpdateStatus.error}`,
|
||||||
);
|
);
|
||||||
exitWithExpectedError(osUpdateStatus.error);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (osUpdateProgress !== null) {
|
if (osUpdateProgress !== null) {
|
||||||
@ -379,6 +364,7 @@ export async function getOnlineTargetDeviceUuid(
|
|||||||
let app: Application;
|
let app: Application;
|
||||||
try {
|
try {
|
||||||
logger.logDebug(`Fetching fleet ${applicationOrDevice}`);
|
logger.logDebug(`Fetching fleet ${applicationOrDevice}`);
|
||||||
|
const { getApplication } = await import('./sdk');
|
||||||
app = await getApplication(sdk, applicationOrDevice);
|
app = await getApplication(sdk, applicationOrDevice);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const { BalenaApplicationNotFound } = await import('balena-errors');
|
const { BalenaApplicationNotFound } = await import('balena-errors');
|
||||||
|
@ -19,6 +19,7 @@ import type * as BalenaSdk from 'balena-sdk';
|
|||||||
import { ExpectedError, printErrorMessage } from '../errors';
|
import { ExpectedError, printErrorMessage } from '../errors';
|
||||||
import { getVisuals, stripIndent, getCliForm } from './lazy';
|
import { getVisuals, stripIndent, getCliForm } from './lazy';
|
||||||
import Logger = require('./logger');
|
import Logger = require('./logger');
|
||||||
|
import { confirm } from './patterns';
|
||||||
import { exec, execBuffered, getDeviceOsRelease } from './ssh';
|
import { exec, execBuffered, getDeviceOsRelease } from './ssh';
|
||||||
|
|
||||||
const MIN_BALENAOS_VERSION = 'v2.14.0';
|
const MIN_BALENAOS_VERSION = 'v2.14.0';
|
||||||
@ -211,7 +212,7 @@ async function getOrSelectApplication(
|
|||||||
.value();
|
.value();
|
||||||
|
|
||||||
if (!appName) {
|
if (!appName) {
|
||||||
return createOrSelectAppOrExit(sdk, compatibleDeviceTypes, deviceType);
|
return createOrSelectApp(sdk, compatibleDeviceTypes, deviceType);
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: BalenaSdk.PineOptions<BalenaSdk.Application> = {
|
const options: BalenaSdk.PineOptions<BalenaSdk.Application> = {
|
||||||
@ -239,17 +240,14 @@ async function getOrSelectApplication(
|
|||||||
)) as ApplicationWithDeviceType[];
|
)) as ApplicationWithDeviceType[];
|
||||||
|
|
||||||
if (applications.length === 0) {
|
if (applications.length === 0) {
|
||||||
const shouldCreateApp = await getCliForm().ask({
|
await confirm(
|
||||||
message:
|
false,
|
||||||
`No fleet found with name "${appName}".\n` +
|
`No fleet found with name "${appName}".\n` +
|
||||||
'Would you like to create it now?',
|
'Would you like to create it now?',
|
||||||
type: 'confirm',
|
undefined,
|
||||||
default: true,
|
true,
|
||||||
});
|
);
|
||||||
if (shouldCreateApp) {
|
return await createApplication(sdk, deviceType, name);
|
||||||
return createApplication(sdk, deviceType, name);
|
|
||||||
}
|
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We've found at least one fleet with the given name.
|
// We've found at least one fleet with the given name.
|
||||||
@ -269,10 +267,7 @@ async function getOrSelectApplication(
|
|||||||
return selectAppFromList(applications);
|
return selectAppFromList(applications);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: revisit this function's purpose. It was refactored out of
|
async function createOrSelectApp(
|
||||||
// `getOrSelectApplication` above in order to satisfy some resin-lint v3
|
|
||||||
// rules, but it looks like there's a fair amount of duplicate logic.
|
|
||||||
async function createOrSelectAppOrExit(
|
|
||||||
sdk: BalenaSdk.BalenaSDK,
|
sdk: BalenaSdk.BalenaSDK,
|
||||||
compatibleDeviceTypes: string[],
|
compatibleDeviceTypes: string[],
|
||||||
deviceType: string,
|
deviceType: string,
|
||||||
@ -291,17 +286,14 @@ async function createOrSelectAppOrExit(
|
|||||||
})) as ApplicationWithDeviceType[];
|
})) as ApplicationWithDeviceType[];
|
||||||
|
|
||||||
if (applications.length === 0) {
|
if (applications.length === 0) {
|
||||||
const shouldCreateApp = await getCliForm().ask({
|
await confirm(
|
||||||
message:
|
false,
|
||||||
'You have no fleets this device can join.\n' +
|
'You have no fleets this device can join.\n' +
|
||||||
'Would you like to create one now?',
|
'Would you like to create one now?',
|
||||||
type: 'confirm',
|
undefined,
|
||||||
default: true,
|
true,
|
||||||
});
|
);
|
||||||
if (shouldCreateApp) {
|
return await createApplication(sdk, deviceType);
|
||||||
return createApplication(sdk, deviceType);
|
|
||||||
}
|
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectAppFromList(applications);
|
return selectAppFromList(applications);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user