Convert config commands to typescript, oclif

Change-type: patch
Signed-off-by: Scott Lowe <scott@balena.io>
This commit is contained in:
Scott Lowe 2020-07-28 14:40:47 +02:00
parent fd4c740d29
commit 56cc70cd50
13 changed files with 769 additions and 521 deletions

View File

@ -127,7 +127,13 @@ const capitanoDoc = {
},
{
title: 'Config',
files: ['build/actions/config.js'],
files: [
'build/actions-oclif/config/generate.js',
'build/actions-oclif/config/inject.js',
'build/actions-oclif/config/read.js',
'build/actions-oclif/config/reconfigure.js',
'build/actions-oclif/config/write.js',
],
},
{
title: 'Preload',

View File

@ -230,11 +230,11 @@ Users are encouraged to regularly update the balena CLI to the latest version.
- Config
- [config read](#config-read)
- [config write &#60;key&#62; &#60;value&#62;](#config-write-key-value)
- [config inject &#60;file&#62;](#config-inject-file)
- [config reconfigure](#config-reconfigure)
- [config generate](#config-generate)
- [config inject &#60;file&#62;](#config-inject-file)
- [config read](#config-read)
- [config reconfigure](#config-reconfigure)
- [config write &#60;key&#62; &#60;value&#62;](#config-write-key-value)
- Preload
@ -1752,96 +1752,13 @@ answer "yes" to all questions (non interactive use)
# Config
## config read
Use this command to read the config.json file from the mounted filesystem (e.g. SD card) of a provisioned device"
Examples:
$ balena config read --type raspberry-pi
$ balena config read --type raspberry-pi --drive /dev/disk2
### Options
#### --type, -t &#60;type&#62;
device type (Check available types with `balena devices supported`)
#### --drive, -d &#60;drive&#62;
drive
## config write &#60;key&#62; &#60;value&#62;
Use this command to write the config.json file to the mounted filesystem (e.g. SD card) of a provisioned device
Examples:
$ balena config write --type raspberry-pi username johndoe
$ balena config write --type raspberry-pi --drive /dev/disk2 username johndoe
$ balena config write --type raspberry-pi files.network/settings "..."
### Options
#### --type, -t &#60;type&#62;
device type (Check available types with `balena devices supported`)
#### --drive, -d &#60;drive&#62;
drive
## config inject &#60;file&#62;
Use this command to inject a config.json file to the mounted filesystem
(e.g. SD card or mounted balenaOS image) of a provisioned device"
Examples:
$ balena config inject my/config.json --type raspberry-pi
$ balena config inject my/config.json --type raspberry-pi --drive /dev/disk2
### Options
#### --type, -t &#60;type&#62;
device type (Check available types with `balena devices supported`)
#### --drive, -d &#60;drive&#62;
drive
## config reconfigure
Use this command to reconfigure a provisioned device
Examples:
$ balena config reconfigure --type raspberry-pi
$ balena config reconfigure --type raspberry-pi --advanced
$ balena config reconfigure --type raspberry-pi --drive /dev/disk2
### Options
#### --type, -t &#60;type&#62;
device type (Check available types with `balena devices supported`)
#### --drive, -d &#60;drive&#62;
drive
#### --advanced, -v
show advanced commands
## config generate
Use this command to generate a config.json for a device or application.
Generate a config.json file for a device or application.
Calling this command with the exact version number of the targeted image is required.
This is interactive by default, but you can do this automatically without interactivity
This command is interactive by default, but you can do this automatically without interactivity
by specifying an option for each question on the command line, if you know the questions
that will be asked for the relevant device type.
@ -1861,23 +1778,27 @@ Examples:
### Options
#### --version &#60;version&#62;
#### --version VERSION
a balenaOS version
#### --application, -a, --app &#60;application&#62;
#### -a, --application APPLICATION
application name
#### --device, -d &#60;device&#62;
#### --app APP
same as '--application'
#### -d, --device DEVICE
device uuid
#### --deviceApiKey, -k &#60;device-api-key&#62;
#### -k, --deviceApiKey DEVICEAPIKEY
custom device key - note that this is only supported on balenaOS 2.0.3+
#### --deviceType &#60;device-type&#62;
#### --deviceType DEVICETYPE
device type slug
@ -1885,26 +1806,127 @@ device type slug
generate a fresh device key for the device
#### --output, -o &#60;output&#62;
#### -o, --output OUTPUT
output
path of output file
#### --network &#60;network&#62;
#### --network NETWORK
the network type to use: ethernet or wifi
#### --wifiSsid &#60;wifiSsid&#62;
#### --wifiSsid WIFISSID
the wifi ssid to use (used only if --network is set to wifi)
#### --wifiKey &#60;wifiKey&#62;
#### --wifiKey WIFIKEY
the wifi key to use (used only if --network is set to wifi)
#### --appUpdatePollInterval &#60;appUpdatePollInterval&#62;
#### --appUpdatePollInterval APPUPDATEPOLLINTERVAL
how frequently (in minutes) to poll for application updates
## config inject &#60;file&#62;
Inject a config.json file to the mounted filesystem,
e.g. the SD card of a provisioned device or balenaOS image.
Examples:
$ balena config inject my/config.json --type raspberrypi3
$ balena config inject my/config.json --type raspberrypi3 --drive /dev/disk2
### Arguments
#### FILE
the path to the config.json file to inject
### Options
#### -t, --type TYPE
device type (Check available types with `balena devices supported`)
#### -d, --drive DRIVE
device filesystem or OS image location
## config read
Read the config.json file from the mounted filesystem,
e.g. the SD card of a provisioned device or balenaOS image.
Examples:
$ balena config read --type raspberrypi3
$ balena config read --type raspberrypi3 --drive /dev/disk2
### Options
#### -t, --type TYPE
device type (Check available types with `balena devices supported`)
#### -d, --drive DRIVE
device filesystem or OS image location
## config reconfigure
Interactively reconfigure a provisioned device or OS image.
Examples:
$ balena config reconfigure --type raspberrypi3
$ balena config reconfigure --type raspberrypi3 --advanced
$ balena config reconfigure --type raspberrypi3 --drive /dev/disk2
### Options
#### -t, --type TYPE
device type (Check available types with `balena devices supported`)
#### -d, --drive DRIVE
device filesystem or OS image location
#### -v, --advanced
show advanced commands
## config write &#60;key&#62; &#60;value&#62;
Write a key-value pair to the config.json file on the mounted filesystem,
e.g. the SD card of a provisioned device or balenaOS image.
Examples:
$ balena config write --type raspberrypi3 username johndoe
$ balena config write --type raspberrypi3 --drive /dev/disk2 username johndoe
$ balena config write --type raspberrypi3 files.network/settings "..."
### Arguments
#### KEY
the key of the config parameter to write
#### VALUE
the value of the config parameter to write
### Options
#### -t, --type TYPE
device type (Check available types with `balena devices supported`)
#### -d, --drive DRIVE
device filesystem or OS image location
# Preload
## preload &#60;image&#62;

View File

@ -0,0 +1,239 @@
/**
* @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, getCliForm, stripIndent } from '../../utils/lazy';
import type { Device, Application, PineDeferred } from 'balena-sdk';
interface FlagsDef {
version: string; // OS version
application?: string;
app?: string; // application alias
device?: string;
deviceApiKey?: string;
deviceType?: string;
'generate-device-api-key': boolean;
output?: string;
// Options for non-interactive configuration
network?: string;
wifiSsid?: string;
wifiKey?: string;
appUpdatePollInterval?: string;
help: void;
}
export default class ConfigGenerateCmd extends Command {
public static description = stripIndent`
Generate a config.json file.
Generate a config.json file for a device or application.
Calling this command with the exact version number of the targeted image is required.
This command is interactive by default, but you can do this automatically without interactivity
by specifying an option for each question on the command line, if you know the questions
that will be asked for the relevant device type.
In case that you want to configure an image for an application with mixed device types,
you can pass the --device-type argument along with --app to specify the target device type.
`;
public static examples = [
'$ balena config generate --device 7cf02a6 --version 2.12.7',
'$ balena config generate --device 7cf02a6 --version 2.12.7 --generate-device-api-key',
'$ balena config generate --device 7cf02a6 --version 2.12.7 --device-api-key <existingDeviceKey>',
'$ balena config generate --device 7cf02a6 --version 2.12.7 --output config.json',
'$ balena config generate --app MyApp --version 2.12.7',
'$ balena config generate --app MyApp --version 2.12.7 --device-type fincm3',
'$ balena config generate --app MyApp --version 2.12.7 --output config.json',
'$ balena config generate --app MyApp --version 2.12.7 --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 1',
];
public static usage = 'config generate';
public static flags: flags.Input<FlagsDef> = {
version: flags.string({
description: 'a balenaOS version',
required: true,
}),
application: flags.string({
description: 'application name',
char: 'a',
exclusive: ['app', 'device'],
}),
app: flags.string({
description: "same as '--application'",
exclusive: ['application', 'device'],
}),
device: flags.string({
description: 'device uuid',
char: 'd',
exclusive: ['application', 'app'],
}),
deviceApiKey: flags.string({
description:
'custom device key - note that this is only supported on balenaOS 2.0.3+',
char: 'k',
}),
deviceType: flags.string({
description: 'device type slug',
}),
'generate-device-api-key': flags.boolean({
description: 'generate a fresh device key for the device',
}),
output: flags.string({
description: 'path of output file',
char: 'o',
}),
// Options for non-interactive configuration
network: flags.string({
description: 'the network type to use: ethernet or wifi',
options: ['ethernet', 'wifi'],
}),
wifiSsid: flags.string({
description:
'the wifi ssid to use (used only if --network is set to wifi)',
}),
wifiKey: flags.string({
description:
'the wifi key to use (used only if --network is set to wifi)',
}),
appUpdatePollInterval: flags.string({
description:
'how frequently (in minutes) to poll for application updates',
}),
help: cf.help,
};
public static authenticated = true;
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(ConfigGenerateCmd);
const balena = getBalenaSdk();
await this.validateOptions(options);
// Get device | application
let resource;
if (options.device != null) {
const { tryAsInteger } = await import('../../utils/validation');
resource = (await balena.models.device.get(
tryAsInteger(options.device),
)) as Device & { belongs_to__application: PineDeferred };
} else {
resource = await balena.models.application.get(options.application!);
}
const deviceType = options.deviceType || resource.device_type;
const deviceManifest = await balena.models.device.getManifestBySlug(
deviceType,
);
// Check compatibility if application and deviceType provided
if (options.application && options.deviceType) {
const appDeviceManifest = await balena.models.device.getManifestBySlug(
resource.device_type,
);
const helpers = await import('../../utils/helpers');
if (
!helpers.areDeviceTypesCompatible(appDeviceManifest, deviceManifest)
) {
throw new balena.errors.BalenaInvalidDeviceType(
`Device type ${options.deviceType} is incompatible with application ${options.application}`,
);
}
}
// Prompt for values
// Pass params as an override: if there is any param with exactly the same name as a
// required option, that value is used (and the corresponding question is not asked)
const answers = await getCliForm().run(deviceManifest.options, {
override: options,
});
answers.version = options.version;
// Generate config
const { generateDeviceConfig, generateApplicationConfig } = await import(
'../../utils/config'
);
let config;
if ('uuid' in resource && resource.uuid != null) {
config = await generateDeviceConfig(
resource,
options.deviceApiKey || options['generate-device-api-key'] || undefined,
answers,
);
} else {
answers.deviceType = deviceType;
config = await generateApplicationConfig(
resource as Application,
answers,
);
}
// Output
if (options.output != null) {
const fs = await import('fs');
await fs.promises.writeFile(options.output, JSON.stringify(config));
}
const prettyjson = await import('prettyjson');
console.log(prettyjson.render(config));
}
protected readonly missingDeviceOrAppMessage = stripIndent`
Either a device or an application must be specified.
See the help page for examples:
$ balena help config generate
`;
protected readonly deviceTypeNotAllowedMessage = stripIndent`
Specifying a different device type is only supported when
generating a config for an application:
* An application, with --app <appname>
* A specific device type, with --device-type <deviceTypeSlug>
See the help page for examples:
$ balena help config generate
`;
protected async validateOptions(options: FlagsDef) {
const { ExpectedError } = await import('../../errors');
// Prefer options.application over options.app
options.application = options.application || options.app;
delete options.app;
if (options.device == null && options.application == null) {
throw new ExpectedError(this.missingDeviceOrAppMessage);
}
if (!options.application && options.deviceType) {
throw new ExpectedError(this.deviceTypeNotAllowedMessage);
}
}
}

View File

@ -0,0 +1,96 @@
/**
* @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 { getVisuals, stripIndent } from '../../utils/lazy';
interface FlagsDef {
type: string;
drive?: string;
help: void;
}
interface ArgsDef {
file: string;
}
export default class ConfigInjectCmd extends Command {
public static description = stripIndent`
Inject a configuration file into a device or OS image.
Inject a config.json file to the mounted filesystem,
e.g. the SD card of a provisioned device or balenaOS image.
`;
public static examples = [
'$ balena config inject my/config.json --type raspberrypi3',
'$ balena config inject my/config.json --type raspberrypi3 --drive /dev/disk2',
];
public static args = [
{
name: 'file',
description: 'the path to the config.json file to inject',
required: true,
},
];
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',
}),
help: cf.help,
};
public static authenticated = true;
public static root = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
ConfigInjectCmd,
);
const { promisify } = await import('util');
const umountAsync = promisify((await import('umount')).umount);
const drive =
options.drive || (await getVisuals().drive('Select the device/OS drive'));
await umountAsync(drive);
const fs = await import('fs');
const configJSON = JSON.parse(
await fs.promises.readFile(params.file, 'utf8'),
);
const config = await import('balena-config-json');
await config.write(drive, options.type, configJSON);
console.info('Done');
}
}

View File

@ -0,0 +1,78 @@
/**
* @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 { getVisuals, stripIndent } from '../../utils/lazy';
interface FlagsDef {
type: string;
drive?: string;
help: void;
}
export default class ConfigReadCmd extends Command {
public static description = stripIndent`
Read the configuration of a device or OS image.
Read the config.json file from the mounted filesystem,
e.g. the SD card of a provisioned device or balenaOS image.
`;
public static examples = [
'$ balena config read --type raspberrypi3',
'$ balena config read --type raspberrypi3 --drive /dev/disk2',
];
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',
}),
help: cf.help,
};
public static authenticated = true;
public static root = true;
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(ConfigReadCmd);
const { promisify } = await import('util');
const umountAsync = promisify((await import('umount')).umount);
const drive =
options.drive || (await getVisuals().drive('Select the device drive'));
await umountAsync(drive);
const config = await import('balena-config-json');
const configJSON = await config.read(drive, options.type);
const prettyjson = await import('prettyjson');
console.info(prettyjson.render(configJSON));
}
}

View File

@ -0,0 +1,90 @@
/**
* @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 { getVisuals, stripIndent } from '../../utils/lazy';
interface FlagsDef {
type: string;
drive?: string;
advanced: boolean;
help: void;
}
export default class ConfigReconfigureCmd extends Command {
public static description = stripIndent`
Interactively reconfigure a device or OS image.
Interactively reconfigure a provisioned device or OS image.
`;
public static examples = [
'$ balena config reconfigure --type raspberrypi3',
'$ balena config reconfigure --type raspberrypi3 --advanced',
'$ balena config reconfigure --type raspberrypi3 --drive /dev/disk2',
];
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',
}),
advanced: flags.boolean({
description: 'show advanced commands',
char: 'v',
}),
help: cf.help,
};
public static authenticated = true;
public static root = true;
public async run() {
const { flags: options } = this.parse<FlagsDef, {}>(ConfigReconfigureCmd);
const { promisify } = await import('util');
const umountAsync = promisify((await import('umount')).umount);
const drive =
options.drive || (await getVisuals().drive('Select the device drive'));
await umountAsync(drive);
const config = await import('balena-config-json');
const { uuid } = await config.read(drive, options.type);
await umountAsync(drive);
const configureCommand = ['os', 'configure', drive, '--device', uuid];
if (options.advanced) {
configureCommand.push('--advanced');
}
const { runCommand } = await import('../../utils/helpers');
await runCommand(configureCommand);
console.info('Done');
}
}

View File

@ -0,0 +1,106 @@
/**
* @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 { getVisuals, stripIndent } from '../../utils/lazy';
interface FlagsDef {
type: string;
drive?: string;
help: void;
}
interface ArgsDef {
key: string;
value: string;
}
export default class ConfigWriteCmd extends Command {
public static description = stripIndent`
Write a key-value pair to configuration of a device or OS image.
Write a key-value pair to the config.json file on the mounted filesystem,
e.g. the SD card of a provisioned device or balenaOS image.
`;
public static examples = [
'$ balena config write --type raspberrypi3 username johndoe',
'$ balena config write --type raspberrypi3 --drive /dev/disk2 username johndoe',
'$ balena config write --type raspberrypi3 files.network/settings "..."',
];
public static args = [
{
name: 'key',
description: 'the key of the config parameter to write',
required: true,
},
{
name: 'value',
description: 'the value of the config parameter to write',
required: true,
},
];
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',
}),
help: cf.help,
};
public static authenticated = true;
public static root = true;
public async run() {
const { args: params, flags: options } = this.parse<FlagsDef, ArgsDef>(
ConfigWriteCmd,
);
const { promisify } = await import('util');
const umountAsync = promisify((await import('umount')).umount);
const drive =
options.drive || (await getVisuals().drive('Select the device drive'));
await umountAsync(drive);
const config = await import('balena-config-json');
const configJSON = await config.read(drive, options.type);
console.info(`Setting ${params.key} to ${params.value}`);
const _ = await import('lodash');
_.set(configJSON, params.key, params.value);
await umountAsync(drive);
await config.write(drive, options.type, configJSON);
console.info('Done');
}
}

View File

@ -1,408 +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 { normalizeUuidProp } from '../utils/normalization';
import { getBalenaSdk, getVisuals, getCliForm } from '../utils/lazy';
import * as _ from 'lodash';
const getUmountAsync = async () => {
const { promisify } = await import('bluebird');
const { umount } = await import('umount');
return promisify(umount);
};
export const read = {
signature: 'config read',
description: 'read a device configuration',
help: `\
Use this command to read the config.json file from the mounted filesystem (e.g. SD card) of a provisioned device"
Examples:
$ balena config read --type raspberry-pi
$ balena config read --type raspberry-pi --drive /dev/disk2\
`,
options: [
{
signature: 'type',
description:
'device type (Check available types with `balena devices supported`)',
parameter: 'type',
alias: 't',
required: 'You have to specify a device type',
},
{
signature: 'drive',
description: 'drive',
parameter: 'drive',
alias: 'd',
},
],
permission: 'user',
root: true,
async action(_params, options) {
const umountAsync = await getUmountAsync();
const drive =
options.drive || (await getVisuals().drive('Select the device drive'));
await umountAsync(drive);
const config = await import('balena-config-json');
const configJSON = await config.read(drive, options.type);
const prettyjson = await import('prettyjson');
console.info(prettyjson.render(configJSON));
},
};
export const write = {
signature: 'config write <key> <value>',
description: 'write a device configuration',
help: `\
Use this command to write the config.json file to the mounted filesystem (e.g. SD card) of a provisioned device
Examples:
$ balena config write --type raspberry-pi username johndoe
$ balena config write --type raspberry-pi --drive /dev/disk2 username johndoe
$ balena config write --type raspberry-pi files.network/settings "..."\
`,
options: [
{
signature: 'type',
description:
'device type (Check available types with `balena devices supported`)',
parameter: 'type',
alias: 't',
required: 'You have to specify a device type',
},
{
signature: 'drive',
description: 'drive',
parameter: 'drive',
alias: 'd',
},
],
permission: 'user',
root: true,
async action(params, options) {
const umountAsync = await getUmountAsync();
const drive =
options.drive || (await getVisuals().drive('Select the device drive'));
await umountAsync(drive);
const config = await import('balena-config-json');
const configJSON = await config.read(drive, options.type);
console.info(`Setting ${params.key} to ${params.value}`);
_.set(configJSON, params.key, params.value);
await umountAsync(drive);
await config.write(drive, options.type, configJSON);
console.info('Done');
},
};
export const inject = {
signature: 'config inject <file>',
description: 'inject a device configuration file',
help: `\
Use this command to inject a config.json file to the mounted filesystem
(e.g. SD card or mounted balenaOS image) of a provisioned device"
Examples:
$ balena config inject my/config.json --type raspberry-pi
$ balena config inject my/config.json --type raspberry-pi --drive /dev/disk2\
`,
options: [
{
signature: 'type',
description:
'device type (Check available types with `balena devices supported`)',
parameter: 'type',
alias: 't',
required: 'You have to specify a device type',
},
{
signature: 'drive',
description: 'drive',
parameter: 'drive',
alias: 'd',
},
],
permission: 'user',
root: true,
async action(params, options) {
const umountAsync = await getUmountAsync();
const drive =
options.drive || (await getVisuals().drive('Select the device drive'));
await umountAsync(drive);
const fs = await import('fs');
const configJSON = JSON.parse(
await fs.promises.readFile(params.file, 'utf8'),
);
const config = await import('balena-config-json');
await config.write(drive, options.type, configJSON);
console.info('Done');
},
};
export const reconfigure = {
signature: 'config reconfigure',
description: 'reconfigure a provisioned device',
help: `\
Use this command to reconfigure a provisioned device
Examples:
$ balena config reconfigure --type raspberry-pi
$ balena config reconfigure --type raspberry-pi --advanced
$ balena config reconfigure --type raspberry-pi --drive /dev/disk2\
`,
options: [
{
signature: 'type',
description:
'device type (Check available types with `balena devices supported`)',
parameter: 'type',
alias: 't',
required: 'You have to specify a device type',
},
{
signature: 'drive',
description: 'drive',
parameter: 'drive',
alias: 'd',
},
{
signature: 'advanced',
description: 'show advanced commands',
boolean: true,
alias: 'v',
},
],
permission: 'user',
root: true,
async action(_params, options) {
const umountAsync = await getUmountAsync();
const drive =
options.drive || (await getVisuals().drive('Select the device drive'));
await umountAsync(drive);
const config = await import('balena-config-json');
const { uuid } = await config.read(drive, options.type);
await umountAsync(drive);
let configureCommand = ['os', 'configure', drive, '--device', uuid];
if (options.advanced) {
configureCommand.push('--advanced');
}
const { runCommand } = await import('../utils/helpers');
await runCommand(configureCommand);
console.info('Done');
},
};
export const generate = {
signature: 'config generate',
description: 'generate a config.json file',
help: `\
Use this command to generate a config.json for a device or application.
Calling this command with the exact version number of the targeted image is required.
This is interactive by default, but you can do this automatically without interactivity
by specifying an option for each question on the command line, if you know the questions
that will be asked for the relevant device type.
In case that you want to configure an image for an application with mixed device types,
you can pass the --device-type argument along with --app to specify the target device type.
Examples:
$ balena config generate --device 7cf02a6 --version 2.12.7
$ balena config generate --device 7cf02a6 --version 2.12.7 --generate-device-api-key
$ balena config generate --device 7cf02a6 --version 2.12.7 --device-api-key <existingDeviceKey>
$ balena config generate --device 7cf02a6 --version 2.12.7 --output config.json
$ balena config generate --app MyApp --version 2.12.7
$ balena config generate --app MyApp --version 2.12.7 --device-type fincm3
$ balena config generate --app MyApp --version 2.12.7 --output config.json
$ balena config generate --app MyApp --version 2.12.7 \
--network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 1\
`,
options: [
commandOptions.osVersion,
commandOptions.optionalApplication,
commandOptions.optionalDevice,
commandOptions.optionalDeviceApiKey,
commandOptions.optionalDeviceType,
{
signature: 'generate-device-api-key',
description: 'generate a fresh device key for the device',
boolean: true,
},
{
signature: 'output',
description: 'output',
parameter: 'output',
alias: 'o',
},
// Options for non-interactive configuration
{
signature: 'network',
description: 'the network type to use: ethernet or wifi',
parameter: 'network',
},
{
signature: 'wifiSsid',
description:
'the wifi ssid to use (used only if --network is set to wifi)',
parameter: 'wifiSsid',
},
{
signature: 'wifiKey',
description:
'the wifi key to use (used only if --network is set to wifi)',
parameter: 'wifiKey',
},
{
signature: 'appUpdatePollInterval',
description:
'how frequently (in minutes) to poll for application updates',
parameter: 'appUpdatePollInterval',
},
],
permission: 'user',
action(_params, options) {
normalizeUuidProp(options, 'device');
const Bluebird = require('bluebird');
const balena = getBalenaSdk();
const prettyjson = require('prettyjson');
const {
generateDeviceConfig,
generateApplicationConfig,
} = require('../utils/config');
const helpers = require('../utils/helpers');
const { exitWithExpectedError } = require('../errors');
if (options.device == null && options.application == null) {
exitWithExpectedError(`\
You have to pass either a device or an application.
See the help page for examples:
$ balena help config generate\
`);
}
if (!options.application && options.deviceType) {
exitWithExpectedError(`\
Specifying a different device type is only supported when
generating a config for an application:
* An application, with --app <appname>
* A specific device type, with --device-type <deviceTypeSlug>
See the help page for examples:
$ balena help config generate\
`);
}
return Bluebird.try(
/** @returns {Promise<any>} */ function () {
if (options.device != null) {
return balena.models.device.get(options.device);
}
return balena.models.application.get(options.application);
},
)
.then(function (resource) {
const deviceType = options.deviceType || resource.device_type;
let manifestPromise = balena.models.device.getManifestBySlug(
deviceType,
);
if (options.application && options.deviceType) {
const app = resource;
const appManifestPromise = balena.models.device.getManifestBySlug(
app.device_type,
);
manifestPromise = manifestPromise.tap((paramDeviceType) =>
appManifestPromise.then(function (appDeviceType) {
if (
!helpers.areDeviceTypesCompatible(
appDeviceType,
paramDeviceType,
)
) {
throw new balena.errors.BalenaInvalidDeviceType(
`Device type ${options.deviceType} is incompatible with application ${options.application}`,
);
}
}),
);
}
return manifestPromise
.get('options')
.then((
formOptions, // Pass params as an override: if there is any param with exactly the same name as a
) =>
// required option, that value is used (and the corresponding question is not asked)
getCliForm().run(formOptions, { override: options }),
)
.then(function (answers) {
answers.version = options.version;
if (resource.uuid != null) {
return generateDeviceConfig(
resource,
options.deviceApiKey || options['generate-device-api-key'],
answers,
);
} else {
answers.deviceType = deviceType;
return generateApplicationConfig(resource, answers);
}
});
})
.then(function (config) {
if (options.output != null) {
return require('fs').promises.writeFile(
options.output,
JSON.stringify(config),
);
}
console.log(prettyjson.render(config));
});
},
};

View File

@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
export * as config from './config';
export * as help from './help';
export * as push from './push';

View File

@ -46,13 +46,6 @@ capitano.globalOption({
// ---------- Help Module ----------
capitano.command(actions.help.help);
// ---------- Config Module ----------
capitano.command(actions.config.read);
capitano.command(actions.config.write);
capitano.command(actions.config.inject);
capitano.command(actions.config.reconfigure);
capitano.command(actions.config.generate);
// ---------- Preload Module ----------
capitano.command(actions.preload);

View File

@ -148,6 +148,11 @@ export const convertedCommands = [
'app:rm',
'apps',
'api-key:generate',
'config:generate',
'config:inject',
'config:read',
'config:reconfigure',
'config:write',
'device',
'device:identify',
'device:init',

View File

@ -52,10 +52,10 @@ Additional commands:
app restart <name> restart an application
app rm <name> remove an application
config generate generate a config.json file
config inject <file> inject a device configuration file
config read read a device configuration
config reconfigure reconfigure a provisioned device
config write <key> <value> write a device configuration
config inject <file> inject a configuration file into a device or OS image
config read read the configuration of a device or OS image
config reconfigure interactively reconfigure a device or OS image
config write <key> <value> write a key-value pair to configuration of a device or OS image
device identify <uuid> identify a device
device init initialise a device with balenaOS
device move <uuid(s)> move one or more devices to another application

22
typings/balena-config-json/index.d.ts vendored Normal file
View File

@ -0,0 +1,22 @@
/**
* @license
* Copyright 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.
*/
declare module 'balena-config-json' {
export function read(image: string, type: string): Promise<any>;
export function write(image: string, type: string, config: any): Promise;
}