mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-01-18 10:46:34 +00:00
Merge pull request #2251 from balena-io/1003-config-inject-umount
config inject/read/write: Fix umount errors with OS image files
This commit is contained in:
commit
f6e6d9ce8b
@ -2330,7 +2330,7 @@ device type (Check available types with `balena devices supported`)
|
||||
|
||||
#### -d, --drive DRIVE
|
||||
|
||||
device filesystem or OS image location
|
||||
path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2)
|
||||
|
||||
## config read
|
||||
|
||||
@ -2350,7 +2350,7 @@ device type (Check available types with `balena devices supported`)
|
||||
|
||||
#### -d, --drive DRIVE
|
||||
|
||||
device filesystem or OS image location
|
||||
path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2)
|
||||
|
||||
## config reconfigure
|
||||
|
||||
@ -2370,7 +2370,7 @@ device type (Check available types with `balena devices supported`)
|
||||
|
||||
#### -d, --drive DRIVE
|
||||
|
||||
device filesystem or OS image location
|
||||
path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2)
|
||||
|
||||
#### -v, --advanced
|
||||
|
||||
@ -2405,7 +2405,7 @@ device type (Check available types with `balena devices supported`)
|
||||
|
||||
#### -d, --drive DRIVE
|
||||
|
||||
device filesystem or OS image location
|
||||
path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2)
|
||||
|
||||
# Preload
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
* Copyright 2016-2021 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -54,16 +54,8 @@ export default class ConfigInjectCmd extends Command {
|
||||
public static usage = 'config inject <file>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
type: flags.string({
|
||||
description:
|
||||
'device type (Check available types with `balena devices supported`)',
|
||||
char: 't',
|
||||
required: true,
|
||||
}),
|
||||
drive: flags.string({
|
||||
description: 'device filesystem or OS image location',
|
||||
char: 'd',
|
||||
}),
|
||||
type: cf.deviceType,
|
||||
drive: cf.driveOrImg,
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
@ -76,12 +68,11 @@ export default class ConfigInjectCmd extends Command {
|
||||
ConfigInjectCmd,
|
||||
);
|
||||
|
||||
const { promisify } = await import('util');
|
||||
const umountAsync = promisify((await import('umount')).umount);
|
||||
const { safeUmount } = await import('../../utils/helpers');
|
||||
|
||||
const drive =
|
||||
options.drive || (await getVisuals().drive('Select the device/OS drive'));
|
||||
await umountAsync(drive);
|
||||
await safeUmount(drive);
|
||||
|
||||
const fs = await import('fs');
|
||||
const configJSON = JSON.parse(
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
* Copyright 2016-2021 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -42,16 +42,8 @@ export default class ConfigReadCmd extends Command {
|
||||
public static usage = 'config read';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
type: flags.string({
|
||||
description:
|
||||
'device type (Check available types with `balena devices supported`)',
|
||||
char: 't',
|
||||
required: true,
|
||||
}),
|
||||
drive: flags.string({
|
||||
description: 'device filesystem or OS image location',
|
||||
char: 'd',
|
||||
}),
|
||||
type: cf.deviceType,
|
||||
drive: cf.driveOrImg,
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
@ -62,12 +54,11 @@ export default class ConfigReadCmd extends Command {
|
||||
public async run() {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(ConfigReadCmd);
|
||||
|
||||
const { promisify } = await import('util');
|
||||
const umountAsync = promisify((await import('umount')).umount);
|
||||
const { safeUmount } = await import('../../utils/helpers');
|
||||
|
||||
const drive =
|
||||
options.drive || (await getVisuals().drive('Select the device drive'));
|
||||
await umountAsync(drive);
|
||||
await safeUmount(drive);
|
||||
|
||||
const config = await import('balena-config-json');
|
||||
const configJSON = await config.read(drive, options.type);
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
* Copyright 2016-2021 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -42,16 +42,8 @@ export default class ConfigReconfigureCmd extends Command {
|
||||
public static usage = 'config reconfigure';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
type: flags.string({
|
||||
description:
|
||||
'device type (Check available types with `balena devices supported`)',
|
||||
char: 't',
|
||||
required: true,
|
||||
}),
|
||||
drive: flags.string({
|
||||
description: 'device filesystem or OS image location',
|
||||
char: 'd',
|
||||
}),
|
||||
type: cf.deviceType,
|
||||
drive: cf.driveOrImg,
|
||||
advanced: flags.boolean({
|
||||
description: 'show advanced commands',
|
||||
char: 'v',
|
||||
@ -66,16 +58,15 @@ export default class ConfigReconfigureCmd extends Command {
|
||||
public async run() {
|
||||
const { flags: options } = this.parse<FlagsDef, {}>(ConfigReconfigureCmd);
|
||||
|
||||
const { promisify } = await import('util');
|
||||
const umountAsync = promisify((await import('umount')).umount);
|
||||
const { safeUmount } = await import('../../utils/helpers');
|
||||
|
||||
const drive =
|
||||
options.drive || (await getVisuals().drive('Select the device drive'));
|
||||
await umountAsync(drive);
|
||||
await safeUmount(drive);
|
||||
|
||||
const config = await import('balena-config-json');
|
||||
const { uuid } = await config.read(drive, options.type);
|
||||
await umountAsync(drive);
|
||||
await safeUmount(drive);
|
||||
|
||||
const configureCommand = ['os', 'configure', drive, '--device', uuid];
|
||||
if (options.advanced) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
* Copyright 2016-2021 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -61,16 +61,8 @@ export default class ConfigWriteCmd extends Command {
|
||||
public static usage = 'config write <key> <value>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
type: flags.string({
|
||||
description:
|
||||
'device type (Check available types with `balena devices supported`)',
|
||||
char: 't',
|
||||
required: true,
|
||||
}),
|
||||
drive: flags.string({
|
||||
description: 'device filesystem or OS image location',
|
||||
char: 'd',
|
||||
}),
|
||||
type: cf.deviceType,
|
||||
drive: cf.driveOrImg,
|
||||
help: cf.help,
|
||||
};
|
||||
|
||||
@ -83,12 +75,11 @@ export default class ConfigWriteCmd extends Command {
|
||||
ConfigWriteCmd,
|
||||
);
|
||||
|
||||
const { promisify } = await import('util');
|
||||
const umountAsync = promisify((await import('umount')).umount);
|
||||
const { safeUmount } = await import('../../utils/helpers');
|
||||
|
||||
const drive =
|
||||
options.drive || (await getVisuals().drive('Select the device drive'));
|
||||
await umountAsync(drive);
|
||||
await safeUmount(drive);
|
||||
|
||||
const config = await import('balena-config-json');
|
||||
const configJSON = await config.read(drive, options.type);
|
||||
@ -97,7 +88,7 @@ export default class ConfigWriteCmd extends Command {
|
||||
const _ = await import('lodash');
|
||||
_.set(configJSON, params.key, params.value);
|
||||
|
||||
await umountAsync(drive);
|
||||
await safeUmount(drive);
|
||||
|
||||
await config.write(drive, options.type, configJSON);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
* Copyright 2016-2021 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -61,20 +61,16 @@ export default class LocalConfigureCmd extends Command {
|
||||
const { args: params } = this.parse<FlagsDef, ArgsDef>(LocalConfigureCmd);
|
||||
|
||||
const path = await import('path');
|
||||
const umount = await import('umount');
|
||||
const umountAsync = promisify(umount.umount);
|
||||
const isMountedAsync = promisify(umount.isMounted);
|
||||
const reconfix = await import('reconfix');
|
||||
const denymount = promisify(await import('denymount'));
|
||||
const { safeUmount } = await import('../../utils/helpers');
|
||||
const Logger = await import('../../utils/logger');
|
||||
|
||||
const logger = Logger.getLogger();
|
||||
|
||||
const configurationSchema = await this.prepareConnectionFile(params.target);
|
||||
|
||||
if (await isMountedAsync(params.target)) {
|
||||
await umountAsync(params.target);
|
||||
}
|
||||
await safeUmount(params.target);
|
||||
|
||||
const dmOpts: any = {};
|
||||
if (process.pkg) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2016-2020 Balena Ltd.
|
||||
* Copyright 2016-2021 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -61,12 +61,7 @@ export default class OsInitializeCmd extends Command {
|
||||
public static usage = 'os initialize <image>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
type: flags.string({
|
||||
description:
|
||||
'device type (Check available types with `balena devices supported`)',
|
||||
char: 't',
|
||||
required: true,
|
||||
}),
|
||||
type: cf.deviceType,
|
||||
drive: cf.drive,
|
||||
yes: cf.yes,
|
||||
help: cf.help,
|
||||
@ -79,9 +74,9 @@ export default class OsInitializeCmd extends Command {
|
||||
OsInitializeCmd,
|
||||
);
|
||||
|
||||
const { promisify } = await import('util');
|
||||
const umountAsync = promisify((await import('umount')).umount);
|
||||
const { getManifest, sudo } = await import('../../utils/helpers');
|
||||
const { getManifest, safeUmount, sudo } = await import(
|
||||
'../../utils/helpers'
|
||||
);
|
||||
|
||||
console.info(`Initializing device ${INIT_WARNING_MESSAGE}`);
|
||||
|
||||
@ -101,7 +96,7 @@ export default class OsInitializeCmd extends Command {
|
||||
`Going to erase ${answers.drive}.`,
|
||||
true,
|
||||
);
|
||||
await umountAsync(answers.drive);
|
||||
await safeUmount(answers.drive);
|
||||
}
|
||||
|
||||
await sudo([
|
||||
@ -113,22 +108,7 @@ export default class OsInitializeCmd extends Command {
|
||||
]);
|
||||
|
||||
if (answers.drive != null) {
|
||||
// TODO: balena local makes use of ejectAsync, see below
|
||||
// DO we need this / should we do that here?
|
||||
|
||||
// getDrive = (drive) ->
|
||||
// driveListAsync().then (drives) ->
|
||||
// selectedDrive = _.find(drives, device: drive)
|
||||
|
||||
// if not selectedDrive?
|
||||
// throw new Error("Drive not found: #{drive}")
|
||||
|
||||
// return selectedDrive
|
||||
// if (os.platform() is 'win32') and selectedDrive.mountpoint?
|
||||
// ejectAsync = Promise.promisify(require('removedrive').eject)
|
||||
// return ejectAsync(selectedDrive.mountpoint)
|
||||
|
||||
await umountAsync(answers.drive);
|
||||
await safeUmount(answers.drive);
|
||||
console.info(`You can safely remove ${answers.drive} now`);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2019 Balena Ltd.
|
||||
* Copyright 2019-2021 Balena Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -82,6 +82,19 @@ export const drive = flags.string({
|
||||
`,
|
||||
});
|
||||
|
||||
export const driveOrImg = flags.string({
|
||||
char: 'd',
|
||||
description:
|
||||
'path to OS image file (e.g. balena.img) or block device (e.g. /dev/disk2)',
|
||||
});
|
||||
|
||||
export const deviceType = flags.string({
|
||||
description:
|
||||
'device type (Check available types with `balena devices supported`)',
|
||||
char: 't',
|
||||
required: true,
|
||||
});
|
||||
|
||||
export const json: IBooleanFlag<boolean> = flags.boolean({
|
||||
char: 'j',
|
||||
description: 'produce JSON output instead of tabular output',
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2016-2020 Balena
|
||||
Copyright 2016-2021 Balena Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -16,16 +16,11 @@ limitations under the License.
|
||||
|
||||
import type { InitializeEmitter, OperationState } from 'balena-device-init';
|
||||
import type * as BalenaSdk from 'balena-sdk';
|
||||
import { spawn, SpawnOptions } from 'child_process';
|
||||
import * as _ from 'lodash';
|
||||
import * as os from 'os';
|
||||
import type * as ShellEscape from 'shell-escape';
|
||||
|
||||
import type { Device, PineOptions } from 'balena-sdk';
|
||||
import { ExpectedError, SIGINTError } from '../errors';
|
||||
import { getBalenaSdk, getChalk, getVisuals } from './lazy';
|
||||
import * as _ from 'lodash';
|
||||
import { promisify } from 'util';
|
||||
import { isSubcommand } from '../preparser';
|
||||
|
||||
import { getBalenaSdk, getChalk, getVisuals } from './lazy';
|
||||
|
||||
export function getGroupDefaults(group: {
|
||||
options: Array<{ name: string; default: string | number }>;
|
||||
@ -84,7 +79,7 @@ export async function sudo(
|
||||
) {
|
||||
const { executeWithPrivileges } = await import('./sudo');
|
||||
|
||||
if (os.platform() !== 'win32') {
|
||||
if (process.platform !== 'win32') {
|
||||
console.log(
|
||||
msg ||
|
||||
'Admin privileges required: you may be asked for your computer password to continue.',
|
||||
@ -95,6 +90,9 @@ export async function sudo(
|
||||
}
|
||||
|
||||
export function runCommand<T>(commandArgs: string[]): Promise<T> {
|
||||
const {
|
||||
isSubcommand,
|
||||
} = require('../preparser') as typeof import('../preparser');
|
||||
if (isSubcommand(commandArgs)) {
|
||||
commandArgs = [
|
||||
commandArgs[0] + ':' + commandArgs[1],
|
||||
@ -238,6 +236,7 @@ export async function retry<T>({
|
||||
backoffScaler?: number;
|
||||
maxSingleDelayMs?: number;
|
||||
}): Promise<T> {
|
||||
const { SIGINTError } = await import('../errors');
|
||||
let delayMs = initialDelayMs;
|
||||
for (let count = 0; count < maxAttempts - 1; count++) {
|
||||
const lastAttemptMs = Date.now();
|
||||
@ -348,7 +347,7 @@ export function shellEscape(args: string[], detectShell = false): string[] {
|
||||
if (isCmdExe) {
|
||||
return args.map((v) => windowsCmdExeEscapeArg(v));
|
||||
} else {
|
||||
const shellEscapeFunc: typeof ShellEscape = require('shell-escape');
|
||||
const shellEscapeFunc: typeof import('shell-escape') = require('shell-escape');
|
||||
return args.map((v) => shellEscapeFunc([v]));
|
||||
}
|
||||
}
|
||||
@ -392,6 +391,7 @@ export async function which(
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
if (rejectOnMissing) {
|
||||
const { ExpectedError } = await import('../errors');
|
||||
throw new ExpectedError(
|
||||
`'${program}' program not found. Is it installed?`,
|
||||
);
|
||||
@ -422,9 +422,10 @@ export async function which(
|
||||
export async function whichSpawn(
|
||||
programName: string,
|
||||
args: string[],
|
||||
options: SpawnOptions = { stdio: 'inherit' },
|
||||
options: import('child_process').SpawnOptions = { stdio: 'inherit' },
|
||||
returnExitCodeOrSignal = false,
|
||||
): Promise<[number | undefined, string | undefined]> {
|
||||
const { spawn } = await import('child_process');
|
||||
const program = await which(programName);
|
||||
if (process.env.DEBUG) {
|
||||
console.error(`[debug] [${program}, ${args.join(', ')}]`);
|
||||
@ -510,7 +511,7 @@ export function getProxyConfig(): ProxyConfig | undefined {
|
||||
}
|
||||
}
|
||||
|
||||
export const expandForAppName: PineOptions<Device> = {
|
||||
export const expandForAppName: BalenaSdk.PineOptions<BalenaSdk.Device> = {
|
||||
$expand: {
|
||||
belongs_to__application: { $select: 'app_name' },
|
||||
is_of__device_type: { $select: 'slug' },
|
||||
@ -565,6 +566,9 @@ export async function awaitInterruptibleTask<
|
||||
let sigintHandler: () => void = () => undefined;
|
||||
const sigintPromise = new Promise<T>((_resolve, reject) => {
|
||||
sigintHandler = () => {
|
||||
const {
|
||||
SIGINTError,
|
||||
} = require('../errors') as typeof import('../errors');
|
||||
reject(new SIGINTError('Task aborted on SIGINT signal'));
|
||||
};
|
||||
addSIGINTHandler(sigintHandler);
|
||||
@ -575,3 +579,16 @@ export async function awaitInterruptibleTask<
|
||||
process.removeListener('SIGINT', sigintHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/** Check if `drive` is mounted, and if so umount it. No-op on Windows. */
|
||||
export async function safeUmount(drive: string) {
|
||||
if (!drive) {
|
||||
return;
|
||||
}
|
||||
const { isMounted, umount } = await import('umount');
|
||||
const isMountedAsync = promisify(isMounted);
|
||||
if (await isMountedAsync(drive)) {
|
||||
const umountAsync = promisify(umount);
|
||||
await umountAsync(drive);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user