mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-22 15:02:22 +00:00
64a44e7a5f
- Add fleet(s) commands and -f, --fleet flags as aliases to the app(s) commands and -a, --app, --application flags. - Conditionally rename column/row headers and JSON object properties from 'application' to 'fleet', with some variations. - Print warning messages regarding the renaming, provided that stderr is attached to an interactive terminal. Change-type: minor Resolves: #2302
263 lines
8.5 KiB
TypeScript
263 lines
8.5 KiB
TypeScript
/**
|
|
* @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 {
|
|
applicationIdInfo,
|
|
appToFleetFlagMsg,
|
|
warnify,
|
|
} from '../../utils/messages';
|
|
import { isV13 } from '../../utils/version';
|
|
import type { PineDeferred } from 'balena-sdk';
|
|
|
|
interface FlagsDef {
|
|
version: string; // OS version
|
|
application?: string;
|
|
app?: string; // application alias
|
|
fleet?: string;
|
|
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 fleet.
|
|
|
|
The target balenaOS version must be specified with the --version option.
|
|
|
|
To configure an image for a fleet of mixed device types, use the --fleet option
|
|
alongside the --deviceType option to specify the target device type.
|
|
|
|
To avoid interactive questions, specify a command line option for each question that
|
|
would otherwise be asked.
|
|
|
|
${applicationIdInfo.split('\n').join('\n\t\t')}
|
|
`;
|
|
|
|
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 --fleet MyFleet --version 2.12.7',
|
|
'$ balena config generate --fleet myorg/myfleet --version 2.12.7',
|
|
'$ balena config generate --fleet MyFleet --version 2.12.7 --deviceType fincm3',
|
|
'$ balena config generate --fleet MyFleet --version 2.12.7 --output config.json',
|
|
'$ balena config generate --fleet MyFleet --version 2.12.7 --network wifi --wifiSsid mySsid --wifiKey abcdefgh --appUpdatePollInterval 15',
|
|
];
|
|
|
|
public static usage = 'config generate';
|
|
|
|
public static flags: flags.Input<FlagsDef> = {
|
|
version: flags.string({
|
|
description: 'a balenaOS version',
|
|
required: true,
|
|
}),
|
|
...(isV13()
|
|
? {}
|
|
: {
|
|
application: {
|
|
...cf.application,
|
|
exclusive: ['app', 'fleet', 'device'],
|
|
},
|
|
app: { ...cf.app, exclusive: ['application', 'fleet', 'device'] },
|
|
appUpdatePollInterval: flags.string({
|
|
description: 'DEPRECATED alias for --updatePollInterval',
|
|
}),
|
|
}),
|
|
fleet: { ...cf.fleet, exclusive: ['application', 'app', 'device'] },
|
|
device: { ...cf.device, exclusive: ['application', 'app', 'fleet'] },
|
|
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 (run 'balena devices supported' for possible values)",
|
|
}),
|
|
'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:
|
|
'supervisor cloud polling interval in minutes (e.g. for variable updates)',
|
|
}),
|
|
help: cf.help,
|
|
};
|
|
|
|
public static authenticated = true;
|
|
|
|
public async run() {
|
|
const { flags: options } = this.parse<FlagsDef, {}>(ConfigGenerateCmd);
|
|
|
|
const { getApplication } = await import('../../utils/sdk');
|
|
|
|
const balena = getBalenaSdk();
|
|
|
|
await this.validateOptions(options);
|
|
|
|
let resourceDeviceType: string;
|
|
let application: ApplicationWithDeviceType | null = null;
|
|
let device:
|
|
| (DeviceWithDeviceType & { belongs_to__application: PineDeferred })
|
|
| null = null;
|
|
if (options.device != null) {
|
|
const { tryAsInteger } = await import('../../utils/validation');
|
|
const rawDevice = await balena.models.device.get(
|
|
tryAsInteger(options.device),
|
|
{ $expand: { is_of__device_type: { $select: 'slug' } } },
|
|
);
|
|
if (!rawDevice.belongs_to__application) {
|
|
const { ExpectedError } = await import('../../errors');
|
|
throw new ExpectedError(stripIndent`
|
|
Device ${options.device} does not appear to belong to an accessible fleet.
|
|
Try with a different device, or use '--fleet' instead of '--device'.`);
|
|
}
|
|
device = rawDevice as DeviceWithDeviceType & {
|
|
belongs_to__application: PineDeferred;
|
|
};
|
|
resourceDeviceType = device.is_of__device_type[0].slug;
|
|
} else {
|
|
// Disambiguate application (if is a number, it could either be an ID or a numerical name)
|
|
application = (await getApplication(balena, options.application!, {
|
|
$expand: {
|
|
is_for__device_type: { $select: 'slug' },
|
|
},
|
|
})) as ApplicationWithDeviceType;
|
|
resourceDeviceType = application.is_for__device_type[0].slug;
|
|
}
|
|
|
|
const deviceType = options.deviceType || resourceDeviceType;
|
|
|
|
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(
|
|
resourceDeviceType,
|
|
);
|
|
|
|
const helpers = await import('../../utils/helpers');
|
|
if (
|
|
!helpers.areDeviceTypesCompatible(appDeviceManifest, deviceManifest)
|
|
) {
|
|
throw new balena.errors.BalenaInvalidDeviceType(
|
|
`Device type ${options.deviceType} is incompatible with fleet ${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 (device) {
|
|
config = await generateDeviceConfig(
|
|
device,
|
|
options.deviceApiKey || options['generate-device-api-key'] || undefined,
|
|
answers,
|
|
);
|
|
} else if (application) {
|
|
answers.deviceType = deviceType;
|
|
config = await generateApplicationConfig(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 a fleet must be specified.
|
|
|
|
See the help page for examples:
|
|
|
|
$ balena help config generate
|
|
`;
|
|
|
|
protected readonly deviceTypeNotAllowedMessage =
|
|
'The --deviceType option can only be used alongside the --fleet option';
|
|
|
|
protected async validateOptions(options: FlagsDef) {
|
|
const { ExpectedError } = await import('../../errors');
|
|
|
|
if ((options.application || options.app) && process.stderr.isTTY) {
|
|
console.error(warnify(appToFleetFlagMsg));
|
|
}
|
|
options.application ||= options.app || options.fleet;
|
|
// Prefer options.application over 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);
|
|
}
|
|
}
|
|
}
|