Convert device init to async await and oclif

Change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
This commit is contained in:
Scott Lowe 2020-07-02 15:58:07 +02:00
parent 402e48c8f8
commit 40f643c07f
6 changed files with 221 additions and 163 deletions

View File

@ -54,8 +54,8 @@ const capitanoDoc = {
{ {
title: 'Device', title: 'Device',
files: [ files: [
'build/actions/device.js',
'build/actions-oclif/device/identify.js', 'build/actions-oclif/device/identify.js',
'build/actions-oclif/device/init.js',
'build/actions-oclif/device/index.js', 'build/actions-oclif/device/index.js',
'build/actions-oclif/device/move.js', 'build/actions-oclif/device/move.js',
'build/actions-oclif/device/reboot.js', 'build/actions-oclif/device/reboot.js',

View File

@ -167,8 +167,8 @@ Users are encouraged to regularly update the balena CLI to the latest version.
- Device - Device
- [device init](#device-init)
- [device identify &#60;uuid&#62;](#device-identify-uuid) - [device identify &#60;uuid&#62;](#device-identify-uuid)
- [device init](#device-init)
- [device &#60;uuid&#62;](#device-uuid) - [device &#60;uuid&#62;](#device-uuid)
- [device move &#60;uuid&#62;](#device-move-uuid) - [device move &#60;uuid&#62;](#device-move-uuid)
- [device reboot &#60;uuid&#62;](#device-reboot-uuid) - [device reboot &#60;uuid&#62;](#device-reboot-uuid)
@ -464,48 +464,6 @@ Examples:
# Device # Device
## device init
Use this command to download the OS image of a certain application and write it to an SD Card.
Notice this command may ask for confirmation interactively.
You can avoid this by passing the `--yes` boolean option.
Examples:
$ balena device init
$ balena device init --application MyApp
### Options
#### --application, -a, --app &#60;application&#62;
application name
#### --yes, -y
confirm non interactively
#### --advanced, -v
show advanced configuration options
#### --os-version &#60;os-version&#62;
exact version number, or a valid semver range,
or 'latest' (includes pre-releases),
or 'default' (excludes pre-releases if at least one stable version is available),
or 'recommended' (excludes pre-releases, will fail if only pre-release versions are available),
or 'menu' (will show the interactive menu)
#### --drive, -d &#60;drive&#62;
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.
#### --config &#60;config&#62;
path to the config JSON file, see `balena os build-config`
## device identify &#60;uuid&#62; ## device identify &#60;uuid&#62;
Identify a device by making the ACT LED blink (Raspberry Pi). Identify a device by making the ACT LED blink (Raspberry Pi).
@ -522,6 +480,53 @@ the uuid of the device to identify
### Options ### Options
## device init
Initialise a device by downloading the OS image of a certain application
and writing it to an SD Card.
Note, if the application option is omitted it will be prompted
for interactively.
Examples:
$ balena device init
$ balena device init --application MyApp
### Options
#### -a, --application APPLICATION
application name
#### --app APP
same as '--application'
#### -y, --yes
answer "yes" to all questions (non interactive use)
#### -v, --advanced
show advanced configuration options
#### --os-version OS-VERSION
exact version number, or a valid semver range,
or 'latest' (includes pre-releases),
or 'default' (excludes pre-releases if at least one stable version is available),
or 'recommended' (excludes pre-releases, will fail if only pre-release versions are available),
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.
#### --config CONFIG
path to the config JSON file, see `balena os build-config`
## device &#60;uuid&#62; ## device &#60;uuid&#62;
Show information about a single device. Show information about a single device.

View File

@ -0,0 +1,172 @@
/**
* @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 { getBalenaSdk, stripIndent } from '../../utils/lazy';
import { runCommand } from '../../utils/helpers';
interface FlagsDef {
application?: string;
app?: string;
yes: boolean;
advanced: boolean;
'os-version'?: string;
drive?: string;
config?: string;
help: void;
}
export default class DeviceInitCmd extends Command {
public static description = stripIndent`
Initialise a device with balenaOS.
Initialise a device by downloading the OS image of a certain application
and writing it to an SD Card.
Note, if the application option is omitted it will be prompted
for interactively.
`;
public static examples = [
'$ balena device init',
'$ balena device init --application MyApp',
];
public static usage = 'device init';
public static flags: flags.Input<FlagsDef> = {
application: cf.application,
app: cf.app,
yes: cf.yes,
advanced: flags.boolean({
char: 'v',
description: 'show advanced configuration options',
}),
'os-version': flags.string({
description: stripIndent`
exact version number, or a valid semver range,
or 'latest' (includes pre-releases),
or 'default' (excludes pre-releases if at least one stable version is available),
or 'recommended' (excludes pre-releases, will fail if only pre-release versions are available),
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.
`,
}),
config: flags.string({
description: 'path to the config JSON file, see `balena os build-config`',
}),
help: cf.help,
};
public static authenticated = true;
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(DeviceInitCmd);
// Imports
const { promisify } = await import('bluebird');
const rimraf = promisify(await import('rimraf'));
const tmp = await import('tmp');
const tmpNameAsync = promisify(tmp.tmpName);
tmp.setGracefulCleanup();
const balena = getBalenaSdk();
const patterns = await import('../../utils/patterns');
const Logger = await import('../../utils/logger');
const logger = Logger.getLogger();
// Consolidate application options
options.application = options.application || options.app;
delete options.app;
// Get application and
const application = await balena.models.application.get(
options['application'] || (await patterns.selectApplication()),
);
// Register new device
const deviceUuid = balena.models.device.generateUniqueKey();
console.info(`Registering to ${application.app_name}: ${deviceUuid}`);
await balena.models.device.register(application.id, deviceUuid);
const device = await balena.models.device.get(deviceUuid);
// Download OS, configure, and flash
const tmpPath = (await tmpNameAsync()) as string;
try {
logger.logDebug(`Downloading OS image...`);
await this.downloadOsImage(tmpPath, application.device_type, options);
logger.logDebug(`Configuring OS image...`);
await this.configureOsImage(tmpPath, device.uuid, options);
logger.logDebug(`Writing OS image...`);
await this.writeOsImage(tmpPath, application.device_type, options);
} catch (e) {
// Remove device in failed cases
try {
logger.logDebug(`Process failed, removing device ${device.uuid}`);
await balena.models.device.remove(device.uuid);
} catch (e) {
// Ignore removal failures, and throw original error
}
throw e;
} finally {
// Remove temp download
logger.logDebug(`Removing temporary OS image download...`);
await rimraf(tmpPath);
}
console.log('Done');
return device.uuid;
}
async downloadOsImage(path: string, deviceType: string, options: FlagsDef) {
const osVersion = options['os-version'] || 'default';
await runCommand(
`os download ${deviceType} --output '${path}' --version ${osVersion}`,
);
}
async configureOsImage(path: string, uuid: string, options: FlagsDef) {
let configureCommand = `os configure ${path} --device ${uuid}`;
if (options.config) {
configureCommand += ` --config ${options.config}`;
} else if (options.advanced) {
configureCommand += ' --advanced';
}
await runCommand(configureCommand);
}
async writeOsImage(path: string, deviceType: string, options: FlagsDef) {
let osInitCommand = `os initialize '${path}' --type ${deviceType}`;
if (options.yes) {
osInitCommand += ' --yes';
}
if (options.drive) {
osInitCommand += ` --drive ${options.drive}`;
}
await runCommand(osInitCommand);
}
}

View File

@ -1,115 +0,0 @@
/*
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 * as commandOptions from './command-options';
import * as _ from 'lodash';
import { getBalenaSdk } from '../utils/lazy';
export const init = {
signature: 'device init',
description: 'initialise a device with balenaOS',
help: `\
Use this command to download the OS image of a certain application and write it to an SD Card.
Notice this command may ask for confirmation interactively.
You can avoid this by passing the \`--yes\` boolean option.
Examples:
$ balena device init
$ balena device init --application MyApp\
`,
options: [
commandOptions.optionalApplication,
commandOptions.yes,
commandOptions.advancedConfig,
_.assign({}, commandOptions.osVersionOrSemver, {
signature: 'os-version',
parameter: 'os-version',
}),
commandOptions.drive,
{
signature: 'config',
description: 'path to the config JSON file, see `balena os build-config`',
parameter: 'config',
},
],
permission: 'user',
action(_params, options) {
const Bluebird = require('bluebird');
const rimraf = Bluebird.promisify(require('rimraf'));
const tmp = require('tmp');
const tmpNameAsync = Bluebird.promisify(tmp.tmpName);
tmp.setGracefulCleanup();
const balena = getBalenaSdk();
const patterns = require('../utils/patterns');
const { runCommand } = require('../utils/helpers');
return Bluebird.try(function () {
if (options.application != null) {
return options.application;
}
return patterns.selectApplication();
})
.then(balena.models.application.get)
.then(function (application) {
const download = () =>
tmpNameAsync()
.then(function (tempPath) {
const osVersion = options['os-version'] || 'default';
return runCommand(
`os download ${application.device_type} --output '${tempPath}' --version ${osVersion}`,
);
})
.disposer((tempPath) => rimraf(tempPath));
return Bluebird.using(download(), (tempPath) =>
runCommand(`device register ${application.app_name}`)
.then(balena.models.device.get)
.then(function (device) {
let configureCommand = `os configure '${tempPath}' --device ${device.uuid}`;
if (options.config) {
configureCommand += ` --config '${options.config}'`;
} else if (options.advanced) {
configureCommand += ' --advanced';
}
return runCommand(configureCommand)
.then(function () {
let osInitCommand = `os initialize '${tempPath}' --type ${application.device_type}`;
if (options.yes) {
osInitCommand += ' --yes';
}
if (options.drive) {
osInitCommand += ` --drive ${options.drive}`;
}
return runCommand(osInitCommand);
})
.catch((error) =>
balena.models.device.remove(device.uuid).finally(function () {
throw error;
}),
)
.then(() => device);
}),
).then(function (device) {
console.log('Done');
return device.uuid;
});
});
},
};

View File

@ -15,7 +15,6 @@ limitations under the License.
*/ */
export * as config from './config'; export * as config from './config';
export * as device from './device';
export * as help from './help'; export * as help from './help';
export * as local from './local'; export * as local from './local';
export * as logs from './logs'; export * as logs from './logs';

View File

@ -46,9 +46,6 @@ capitano.globalOption({
// ---------- Help Module ---------- // ---------- Help Module ----------
capitano.command(actions.help.help); capitano.command(actions.help.help);
// ---------- Device Module ----------
capitano.command(actions.device.init);
// ---------- OS Module ---------- // ---------- OS Module ----------
capitano.command(actions.os.versions); capitano.command(actions.os.versions);
capitano.command(actions.os.download); capitano.command(actions.os.download);