mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-18 21:27:51 +00:00
device move: Add ability to move multiple devices in one command
Change-type: minor
This commit is contained in:
parent
d419ae9183
commit
edaf76e329
@ -170,7 +170,7 @@ Users are encouraged to regularly update the balena CLI to the latest version.
|
||||
- [device identify <uuid>](#device-identify-uuid)
|
||||
- [device init](#device-init)
|
||||
- [device <uuid>](#device-uuid)
|
||||
- [device move <uuid>](#device-move-uuid)
|
||||
- [device move <uuid(s)>](#device-move-uuid-s)
|
||||
- [device reboot <uuid>](#device-reboot-uuid)
|
||||
- [device register <application>](#device-register-application)
|
||||
- [device rename <uuid> [newname]](#device-rename-uuid-newname)
|
||||
@ -543,9 +543,9 @@ the device uuid
|
||||
|
||||
### Options
|
||||
|
||||
## device move <uuid>
|
||||
## device move <uuid(s)>
|
||||
|
||||
Move a device to another application.
|
||||
Move one or more devices to another application.
|
||||
|
||||
Note, if the application option is omitted it will be prompted
|
||||
for interactively.
|
||||
@ -553,13 +553,14 @@ for interactively.
|
||||
Examples:
|
||||
|
||||
$ balena device move 7cf02a6
|
||||
$ balena device move 7cf02a6,dc39e52
|
||||
$ balena device move 7cf02a6 --application MyNewApp
|
||||
|
||||
### Arguments
|
||||
|
||||
#### UUID
|
||||
|
||||
the uuid of the device to move
|
||||
comma-separated list (no blank spaces) of device UUIDs to be moved
|
||||
|
||||
### Options
|
||||
|
||||
|
@ -23,6 +23,7 @@ import * as cf from '../../utils/common-flags';
|
||||
import { expandForAppName } from '../../utils/helpers';
|
||||
import { getBalenaSdk, stripIndent } from '../../utils/lazy';
|
||||
import { tryAsInteger } from '../../utils/validation';
|
||||
import { ExpectedError } from '../../errors';
|
||||
|
||||
interface ExtendedDevice extends Device {
|
||||
application_name?: string;
|
||||
@ -40,28 +41,30 @@ interface ArgsDef {
|
||||
|
||||
export default class DeviceMoveCmd extends Command {
|
||||
public static description = stripIndent`
|
||||
Move a device to another application.
|
||||
Move one or more devices to another application.
|
||||
|
||||
Move a device to another application.
|
||||
Move one or more devices to another application.
|
||||
|
||||
Note, if the application option is omitted it will be prompted
|
||||
for interactively.
|
||||
`;
|
||||
public static examples = [
|
||||
'$ balena device move 7cf02a6',
|
||||
'$ balena device move 7cf02a6,dc39e52',
|
||||
'$ balena device move 7cf02a6 --application MyNewApp',
|
||||
];
|
||||
|
||||
public static args: Array<IArg<any>> = [
|
||||
{
|
||||
name: 'uuid',
|
||||
description: 'the uuid of the device to move',
|
||||
description:
|
||||
'comma-separated list (no blank spaces) of device UUIDs to be moved',
|
||||
parse: (dev) => tryAsInteger(dev),
|
||||
required: true,
|
||||
},
|
||||
];
|
||||
|
||||
public static usage = 'device move <uuid>';
|
||||
public static usage = 'device move <uuid(s)>';
|
||||
|
||||
public static flags: flags.Input<FlagsDef> = {
|
||||
application: cf.application,
|
||||
@ -82,47 +85,72 @@ export default class DeviceMoveCmd extends Command {
|
||||
options.application = options.application || options.app;
|
||||
delete options.app;
|
||||
|
||||
const device: ExtendedDevice = await balena.models.device.get(
|
||||
params.uuid,
|
||||
expandForAppName,
|
||||
const devices: ExtendedDevice[] = await Promise.all(
|
||||
params.uuid
|
||||
.split(',')
|
||||
.map((uuid) => balena.models.device.get(uuid, expandForAppName)),
|
||||
);
|
||||
|
||||
const belongsToApplication = device.belongs_to__application as Application[];
|
||||
device.application_name = belongsToApplication?.[0]
|
||||
? belongsToApplication[0].app_name
|
||||
: 'N/a';
|
||||
for (const device of devices) {
|
||||
const belongsToApplication = device.belongs_to__application as Application[];
|
||||
device.application_name = belongsToApplication?.[0]
|
||||
? belongsToApplication[0].app_name
|
||||
: 'N/a';
|
||||
}
|
||||
|
||||
// Get destination application
|
||||
let application;
|
||||
if (options.application) {
|
||||
application = options.application;
|
||||
} else {
|
||||
const [deviceDeviceType, deviceTypes] = await Promise.all([
|
||||
balena.models.device.getManifestBySlug(device.device_type),
|
||||
const [deviceDeviceTypes, deviceTypes] = await Promise.all([
|
||||
Promise.all(
|
||||
devices.map((device) =>
|
||||
balena.models.device.getManifestBySlug(device.device_type),
|
||||
),
|
||||
),
|
||||
balena.models.config.getDeviceTypes(),
|
||||
]);
|
||||
|
||||
const compatibleDeviceTypes = deviceTypes.filter(
|
||||
(dt) =>
|
||||
balena.models.os.isArchitectureCompatibleWith(
|
||||
deviceDeviceType.arch,
|
||||
dt.arch,
|
||||
) &&
|
||||
!!dt.isDependent === !!deviceDeviceType.isDependent &&
|
||||
dt.state !== 'DISCONTINUED',
|
||||
const compatibleDeviceTypes = deviceTypes.filter((dt) =>
|
||||
deviceDeviceTypes.every(
|
||||
(deviceDeviceType) =>
|
||||
balena.models.os.isArchitectureCompatibleWith(
|
||||
deviceDeviceType.arch,
|
||||
dt.arch,
|
||||
) &&
|
||||
!!dt.isDependent === !!deviceDeviceType.isDependent &&
|
||||
dt.state !== 'DISCONTINUED',
|
||||
),
|
||||
);
|
||||
|
||||
const patterns = await import('../../utils/patterns');
|
||||
application = await patterns.selectApplication(
|
||||
(app: Application) =>
|
||||
compatibleDeviceTypes.some((dt) => dt.slug === app.device_type) &&
|
||||
// @ts-ignore using the extended device object prop
|
||||
device.application_name !== app.app_name,
|
||||
);
|
||||
try {
|
||||
application = await patterns.selectApplication(
|
||||
(app: Application) =>
|
||||
compatibleDeviceTypes.some((dt) => dt.slug === app.device_type) &&
|
||||
// @ts-ignore using the extended device object prop
|
||||
devices.some((device) => device.application_name !== app.app_name),
|
||||
true,
|
||||
);
|
||||
} catch (err) {
|
||||
if (deviceDeviceTypes.length) {
|
||||
throw new ExpectedError(
|
||||
`${err.message}\nDo all devices have a compatible architecture?`,
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
await balena.models.device.move(params.uuid, tryAsInteger(application));
|
||||
|
||||
console.info(`${params.uuid} was moved to ${application}`);
|
||||
for (const uuid of params.uuid.split(',')) {
|
||||
try {
|
||||
await balena.models.device.move(uuid, tryAsInteger(application));
|
||||
console.info(`${uuid} was moved to ${application}`);
|
||||
} catch (err) {
|
||||
console.info(`${err.message}, uuid: ${uuid}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -147,6 +147,7 @@ const EXPECTED_ERROR_REGEXES = [
|
||||
/^BalenaApplicationNotFound/, // balena-sdk
|
||||
/^BalenaDeviceNotFound/, // balena-sdk
|
||||
/^BalenaExpiredToken/, // balena-sdk
|
||||
/^BalenaInvalidDeviceType/, // balena-sdk
|
||||
/^Missing \w+$/, // Capitano,
|
||||
/^Missing \d+ required arg/, // oclif parser: RequiredArgsError
|
||||
/Missing required flag/, // oclif parser: RequiredFlagError
|
||||
|
@ -160,6 +160,7 @@ export async function confirm(
|
||||
|
||||
export function selectApplication(
|
||||
filter?: (app: BalenaSdk.Application) => boolean,
|
||||
errorOnEmptySelection = false,
|
||||
) {
|
||||
const balena = getBalenaSdk();
|
||||
return balena.models.application
|
||||
@ -173,6 +174,9 @@ export function selectApplication(
|
||||
})
|
||||
.filter(filter || _.constant(true))
|
||||
.then((applications) => {
|
||||
if (errorOnEmptySelection && applications.length === 0) {
|
||||
throw new ExpectedError('No suitable applications found for selection');
|
||||
}
|
||||
return getCliForm().ask({
|
||||
message: 'Select an application',
|
||||
type: 'list',
|
||||
|
@ -20,13 +20,13 @@ import { BalenaAPIMock } from '../../balena-api-mock';
|
||||
import { cleanOutput, runCommand } from '../../helpers';
|
||||
|
||||
const HELP_RESPONSE = `
|
||||
Move a device to another application.
|
||||
Move one or more devices to another application.
|
||||
|
||||
USAGE
|
||||
$ balena device move <uuid>
|
||||
$ balena device move <uuid(s)>
|
||||
|
||||
ARGUMENTS
|
||||
<uuid> the uuid of the device to move
|
||||
<uuid> comma-separated list (no blank spaces) of device UUIDs to be moved
|
||||
|
||||
OPTIONS
|
||||
-a, --application <application> application name
|
||||
@ -34,13 +34,14 @@ OPTIONS
|
||||
--app <app> same as '--application'
|
||||
|
||||
DESCRIPTION
|
||||
Move a device to another application.
|
||||
Move one or more devices to another application.
|
||||
|
||||
Note, if the application option is omitted it will be prompted
|
||||
for interactively.
|
||||
|
||||
EXAMPLES
|
||||
$ balena device move 7cf02a6
|
||||
$ balena device move 7cf02a6,dc39e52
|
||||
$ balena device move 7cf02a6 --application MyNewApp
|
||||
`;
|
||||
|
||||
@ -71,7 +72,9 @@ describe('balena device move', function () {
|
||||
const errLines = cleanOutput(err);
|
||||
|
||||
expect(errLines[0]).to.equal('Missing 1 required argument:');
|
||||
expect(errLines[1]).to.equal('uuid : the uuid of the device to move');
|
||||
expect(errLines[1]).to.equal(
|
||||
'uuid : comma-separated list (no blank spaces) of device UUIDs to be moved',
|
||||
);
|
||||
expect(out).to.eql([]);
|
||||
});
|
||||
|
||||
|
@ -58,7 +58,7 @@ Additional commands:
|
||||
config write <key> <value> write a device configuration
|
||||
device identify <uuid> identify a device
|
||||
device init initialise a device with balenaOS
|
||||
device move <uuid> move a device to another application
|
||||
device move <uuid(s)> move one or more devices to another application
|
||||
device os-update <uuid> start a Host OS update for a device
|
||||
device public-url <uuid> get or manage the public URL for a device
|
||||
device reboot <uuid> restart a device
|
||||
|
Loading…
Reference in New Issue
Block a user