Convert os initialize to oclif, typescript

Change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
This commit is contained in:
Scott Lowe 2020-07-15 21:13:18 +02:00
parent 7b78a777ac
commit b7c74598b2
12 changed files with 186 additions and 125 deletions

View File

@ -122,6 +122,7 @@ const capitanoDoc = {
'build/actions-oclif/os/configure.js',
'build/actions-oclif/os/versions.js',
'build/actions-oclif/os/download.js',
'build/actions-oclif/os/initialize.js',
],
},
{

View File

@ -521,7 +521,9 @@ or 'menu' (will show the interactive menu)
#### -d, --drive DRIVE
the drive to write the image to, eg. `/dev/sdb` or `/dev/mmcblk0`. Careful with this as you can erase your hard drive. Check `balena util available-drives` for available options.
the drive to write the image to, eg. `/dev/sdb` or `/dev/mmcblk0`.
Careful with this as you can erase your hard drive.
Check `balena util available-drives` for available options.
#### --config CONFIG
@ -1704,28 +1706,38 @@ paths to local files to place into the 'system-connections' directory
## os initialize &#60;image&#62;
Use this command to initialize a device with previously configured operating system image.
Initialize an os image for a device with a previously
configured operating system image.
Note: Initializing the device may ask for administrative permissions
because we need to access the raw devices directly.
Examples:
$ balena os initialize ../path/rpi.img --type 'raspberry-pi'
$ balena os initialize ../path/rpi.img --type raspberry-pi
### Arguments
#### IMAGE
path to OS image
### Options
#### --yes, -y
confirm non interactively
#### --type, -t &#60;type&#62;
#### -t, --type TYPE
device type (Check available types with `balena devices supported`)
#### --drive, -d &#60;drive&#62;
#### -d, --drive DRIVE
the drive to write the image to, like `/dev/sdb` or `/dev/mmcblk0`. Careful with this as you can erase your hard drive. Check `balena util available-drives` for available options.
the drive to write the image to, eg. `/dev/sdb` or `/dev/mmcblk0`.
Careful with this as you can erase your hard drive.
Check `balena util available-drives` for available options.
#### -y, --yes
answer "yes" to all questions (non interactive use)
# Config
@ -2217,7 +2229,9 @@ path to OS image
#### -d, --drive DRIVE
drive to flash
the drive to write the image to, eg. `/dev/sdb` or `/dev/mmcblk0`.
Careful with this as you can erase your hard drive.
Check `balena util available-drives` for available options.
#### -y, --yes

View File

@ -66,14 +66,7 @@ export default class DeviceInitCmd extends Command {
or 'menu' (will show the interactive menu)
`,
}),
drive: flags.string({
char: 'd',
description: stripIndent`
the drive to write the image to, eg. \`/dev/sdb\` or \`/dev/mmcblk0\`. \
Careful with this as you can erase your hard drive. \
Check \`balena util available-drives\` for available options.
`,
}),
drive: cf.drive,
config: flags.string({
description: 'path to the config JSON file, see `balena os build-config`',
}),

View File

@ -65,10 +65,7 @@ export default class LocalFlashCmd extends Command {
public static usage = 'local flash <image>';
public static flags: flags.Input<FlagsDef> = {
drive: flags.string({
description: 'drive to flash',
char: 'd',
}),
drive: cf.drive,
yes: cf.yes,
help: cf.help,
};

View File

@ -0,0 +1,135 @@
/**
* @license
* Copyright 2016-2020 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { flags } from '@oclif/command';
import Command from '../../command';
import * as cf from '../../utils/common-flags';
import { getCliForm, stripIndent } from '../../utils/lazy';
interface FlagsDef {
type: string;
drive?: string;
yes: boolean;
help: void;
}
interface ArgsDef {
image: string;
}
const INIT_WARNING_MESSAGE = `
Note: Initializing the device may ask for administrative permissions
because we need to access the raw devices directly.\
`;
export default class OsInitializeCmd extends Command {
public static description = stripIndent`
Initialize an os image for a device.
Initialize an os image for a device with a previously
configured operating system image.
${INIT_WARNING_MESSAGE}
`;
public static examples = [
'$ balena os initialize ../path/rpi.img --type raspberry-pi',
];
public static args = [
{
name: 'image',
description: 'path to OS image',
required: true,
},
];
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,
}),
drive: cf.drive,
yes: cf.yes,
help: cf.help,
};
public static authenticated = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
OsInitializeCmd,
);
const { promisify } = await import('util');
const umountAsync = promisify((await import('umount')).umount);
const { getManifest, sudo } = await import('../../utils/helpers');
console.info(`Initializing device ${INIT_WARNING_MESSAGE}`);
const manifest = await getManifest(params.image, options.type);
const answers = await getCliForm().run(manifest.initialization?.options, {
override: {
drive: options.drive,
},
});
if (answers.drive != null) {
const { confirm } = await import('../../utils/patterns');
await confirm(
options.yes,
`This will erase ${answers.drive}. Are you sure?`,
`Going to erase ${answers.drive}.`,
true,
);
await umountAsync(answers.drive);
}
await sudo([
'internal',
'osinit',
params.image,
options.type,
JSON.stringify(answers),
]);
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);
console.info(`You can safely remove ${answers.drive} now`);
}
}
}

View File

@ -89,101 +89,3 @@ Example:
);
},
};
const INIT_WARNING_MESSAGE = `\
Note: Initializing the device may ask for administrative permissions
because we need to access the raw devices directly.\
`;
export const initialize = {
signature: 'os initialize <image>',
description: 'initialize an os image',
help: `\
Use this command to initialize a device with previously configured operating system image.
${INIT_WARNING_MESSAGE}
Examples:
$ balena os initialize ../path/rpi.img --type 'raspberry-pi'\
`,
permission: 'user',
options: [
commandOptions.yes,
{
signature: 'type',
description:
'device type (Check available types with `balena devices supported`)',
parameter: 'type',
alias: 't',
required: 'You have to specify a device type',
},
commandOptions.drive,
],
action(params, options) {
const Bluebird = require('bluebird');
const umountAsync = Bluebird.promisify(require('umount').umount);
const patterns = require('../utils/patterns');
const helpers = require('../utils/helpers');
console.info(`\
Initializing device
${INIT_WARNING_MESSAGE}\
`);
return Bluebird.resolve(helpers.getManifest(params.image, options.type))
.then((manifest) =>
getCliForm().run(manifest.initialization?.options, {
override: {
drive: options.drive,
},
}),
)
.tap(function (answers) {
if (answers.drive == null) {
return;
}
return patterns
.confirm(
options.yes,
`This will erase ${answers.drive}. Are you sure?`,
`Going to erase ${answers.drive}.`,
true,
)
.then(() => umountAsync(answers.drive));
})
.tap((answers) =>
helpers.sudo([
'internal',
'osinit',
params.image,
options.type,
JSON.stringify(answers),
]),
)
.then(function (answers) {
if (answers.drive == null) {
return;
}
// 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)
return umountAsync(answers.drive).tap(() => {
console.info(`You can safely remove ${answers.drive} now`);
});
});
},
};

View File

@ -48,7 +48,6 @@ capitano.command(actions.help.help);
// ---------- OS Module ----------
capitano.command(actions.os.buildConfig);
capitano.command(actions.os.initialize);
// ---------- Config Module ----------
capitano.command(actions.config.read);

View File

@ -182,6 +182,7 @@ export const convertedCommands = [
'os:configure',
'os:versions',
'os:download',
'os:initialize',
'scan',
'settings',
'ssh',

View File

@ -18,6 +18,7 @@
import { flags } from '@oclif/command';
import type { IBooleanFlag } from '@oclif/parser/lib/flags';
import { stripIndent } from './lazy';
export const application = flags.string({
char: 'a',
@ -65,3 +66,12 @@ export const force: IBooleanFlag<boolean> = flags.boolean({
char: 'f',
description: 'force action if the update lock is set',
});
export const drive = flags.string({
char: 'd',
description: stripIndent`
the drive to write the image to, eg. \`/dev/sdb\` or \`/dev/mmcblk0\`.
Careful with this as you can erase your hard drive.
Check \`balena util available-drives\` for available options.
`,
});

View File

@ -130,6 +130,15 @@ export function selectDeviceType() {
});
}
/**
* Display interactive confirmation prompt.
* If the user declines, then either an error will be thrown,
* or `exitWithExpectedError` will be called (if exitIfDeclined true).
* @param yesOption - automatically confirm if true
* @param message - message to display with prompt
* @param yesMessage - message to display if automatically confirming
* @param exitIfDeclined - exitWithExpectedError when decline if true
*/
export async function confirm(
yesOption: boolean,
message: string,

View File

@ -82,7 +82,7 @@ Additional commands:
os build-config <image> <device-type> build the OS config and save it to the JSON file
os configure <image> configure a previously downloaded balenaOS image
os download <type> download an unconfigured OS image
os initialize <image> initialize an os image
os initialize <image> initialize an os image for a device
os versions <type> show available balenaOS versions for the given device type
settings print current settings
tag rm <tagkey> remove a tag from an application, device or release

View File

@ -18,10 +18,10 @@
declare module 'umount' {
export const umount: (
device: string,
callback: (err?: any, stdout?: any, stderr?: any) => void,
callback: (err?: Error, stdout?: any, stderr?: any) => void,
) => void;
export const isMounted: (
device: string,
callback: (err?: any, isMounted?: boolean) => void,
callback: (err?: Error, isMounted?: boolean) => void,
) => void;
}