Merge pull request #1047 from balena-io/1013-os-configure-device-type

Add explicit device type option to `os configure` & `config generate`
This commit is contained in:
Thodoris Greasidis 2018-12-14 17:06:05 +02:00 committed by GitHub
commit b9b4343fd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 137 additions and 27 deletions

View File

@ -1072,12 +1072,16 @@ This command still supports the *deprecated* format where the UUID and optionall
are passed directly on the command line, but the recommended way is to pass either an --app or
--device argument. The deprecated format will be remove in a future release.
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 os configure ../path/rpi.img --device 7cf02a6
$ balena os configure ../path/rpi.img --device 7cf02a6 --device-api-key <existingDeviceKey>
$ balena os configure ../path/rpi.img --app MyApp
$ balena os configure ../path/rpi.img --app MyApp --version 2.12.7
$ balena os configure ../path/rpi3.img --device 7cf02a6
$ balena os configure ../path/rpi3.img --device 7cf02a6 --device-api-key <existingDeviceKey>
$ balena os configure ../path/rpi3.img --app MyApp
$ balena os configure ../path/rpi3.img --app MyApp --version 2.12.7
$ balena os configure ../path/rpi3.img --app MyFinApp --device-type raspberrypi3
### Options
@ -1097,6 +1101,10 @@ device uuid
custom device key - note that this is only supported on balenaOS 2.0.3+
#### --deviceType &#60;device-type&#62;
device type slug
#### --version &#60;version&#62;
a balenaOS version
@ -1225,6 +1233,9 @@ This is interactive by default, but you can do this automatically without intera
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
@ -1232,6 +1243,7 @@ Examples:
$ 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
@ -1253,6 +1265,10 @@ device uuid
custom device key - note that this is only supported on balenaOS 2.0.3+
#### --deviceType &#60;device-type&#62;
device type slug
#### --generate-device-api-key
generate a fresh device key for the device

View File

@ -57,6 +57,12 @@ export const optionalDeviceApiKey = {
alias: 'k',
};
export const optionalDeviceType = {
signature: 'deviceType',
description: 'device type slug',
parameter: 'device-type',
};
export const optionalOsVersion = {
signature: 'version',
description: 'a balenaOS version',

View File

@ -229,6 +229,9 @@ exports.generate =
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
@ -236,6 +239,7 @@ exports.generate =
$ 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
@ -245,6 +249,7 @@ exports.generate =
commandOptions.optionalApplication
commandOptions.optionalDevice
commandOptions.optionalDeviceApiKey
commandOptions.optionalDeviceType
{
signature: 'generate-device-api-key'
description: 'generate a fresh device key for the device'
@ -288,6 +293,7 @@ exports.generate =
prettyjson = require('prettyjson')
{ generateDeviceConfig, generateApplicationConfig } = require('../utils/config')
helpers = require('../utils/helpers')
{ exitWithExpectedError } = require('../utils/patterns')
if not options.device? and not options.application?
@ -299,13 +305,38 @@ exports.generate =
$ balena help config generate
'''
if !options.application and options.deviceType
patterns.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
'''
Promise.try ->
if options.device?
return balena.models.device.get(options.device)
return balena.models.application.get(options.application)
.then (resource) ->
balena.models.device.getManifestBySlug(resource.device_type)
.get('options')
deviceType = options.deviceType || resource.device_type
manifestPromise = balena.models.device.getManifestBySlug(deviceType)
if options.application && options.deviceType
app = resource
appManifestPromise = balena.models.device.getManifestBySlug(app.device_type)
manifestPromise = manifestPromise.tap (paramDeviceType) ->
appManifestPromise.then (appDeviceType) ->
if not helpers.areDeviceTypesCompatible(appDeviceType, paramDeviceType)
throw new balena.errors.BalenaInvalidDeviceType(
"Device type #{options.deviceType} is incompatible with application #{options.application}"
)
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)
@ -316,6 +347,7 @@ exports.generate =
if resource.uuid?
generateDeviceConfig(resource, options.deviceApiKey || options['generate-device-api-key'], answers)
else
answers.deviceType = deviceType
generateApplicationConfig(resource, answers)
.then (config) ->
if options.output?

View File

@ -147,23 +147,28 @@ exports.download =
console.info('The image was downloaded successfully')
.nodeify(done)
buildConfig = (image, deviceType, advanced = false) ->
Promise = require('bluebird')
buildConfigForDeviceType = (deviceType, advanced = false) ->
form = require('resin-cli-form')
helpers = require('../utils/helpers')
Promise.resolve(helpers.getManifest(image, deviceType))
.get('options')
.then (questions) ->
if not advanced
advancedGroup = _.find questions,
name: 'advanced'
isGroup: true
questions = deviceType.options
if not advanced
advancedGroup = _.find questions,
name: 'advanced'
isGroup: true
if advancedGroup?
override = helpers.getGroupDefaults(advancedGroup)
if advancedGroup?
override = helpers.getGroupDefaults(advancedGroup)
return form.run(questions, { override })
return form.run(questions, { override })
buildConfig = (image, deviceTypeSlug, advanced = false) ->
Promise = require('bluebird')
helpers = require('../utils/helpers')
Promise.resolve(helpers.getManifest(image, deviceTypeSlug))
.then (deviceTypeManifest) ->
buildConfigForDeviceType(deviceTypeManifest, advanced)
exports.buildConfig =
signature: 'os build-config <image> <device-type>'
@ -214,12 +219,16 @@ exports.configure =
are passed directly on the command line, but the recommended way is to pass either an --app or
--device argument. The deprecated format will be remove in a future release.
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 os configure ../path/rpi.img --device 7cf02a6
$ balena os configure ../path/rpi.img --device 7cf02a6 --device-api-key <existingDeviceKey>
$ balena os configure ../path/rpi.img --app MyApp
$ balena os configure ../path/rpi.img --app MyApp --version 2.12.7
$ balena os configure ../path/rpi3.img --device 7cf02a6
$ balena os configure ../path/rpi3.img --device 7cf02a6 --device-api-key <existingDeviceKey>
$ balena os configure ../path/rpi3.img --app MyApp
$ balena os configure ../path/rpi3.img --app MyApp --version 2.12.7
$ balena os configure ../path/rpi3.img --app MyFinApp --device-type raspberrypi3
'''
permission: 'user'
options: [
@ -227,6 +236,7 @@ exports.configure =
commandOptions.optionalApplication
commandOptions.optionalDevice
commandOptions.optionalDeviceApiKey
commandOptions.optionalDeviceType
commandOptions.optionalOsVersion
{
signature: 'config'
@ -260,6 +270,19 @@ exports.configure =
$ balena help os configure
'''
if !options.application and options.deviceType
patterns.exitWithExpectedError '''
Specifying a different device type is only supported when
configuring an image using an application as a parameter:
* An application, with --app <appname>
* A specific device type, with --device-type <deviceTypeSlug>
See the help page for examples:
$ balena help os configure
'''
uuid = options.device
deviceApiKey = options.deviceApiKey
@ -269,15 +292,33 @@ exports.configure =
balena.models[configurationResourceType].get(uuid || options.application)
.then (appOrDevice) ->
manifestPromise = helpers.getManifest(params.image, appOrDevice.device_type)
deviceType = options.deviceType || appOrDevice.device_type
manifestPromise = helpers.getManifest(params.image, deviceType)
if options.application && options.deviceType
app = appOrDevice
appManifestPromise = balena.models.device.getManifestBySlug(app.device_type)
paramManifestPromise = balena.models.device.getManifestBySlug(options.deviceType)
manifestPromise = Promise.resolve(manifestPromise).tap ->
Promise.join appManifestPromise, paramManifestPromise, (appDeviceType, paramDeviceType) ->
if not helpers.areDeviceTypesCompatible(appDeviceType, paramDeviceType)
throw new balena.errors.BalenaInvalidDeviceType(
"Device type #{options.deviceType} is incompatible with application #{options.application}"
)
answersPromise = Promise.try ->
if options.config
return readFileAsync(options.config, 'utf8')
.then(JSON.parse)
return buildConfig(params.image, appOrDevice.device_type, options.advanced)
return manifestPromise.then (deviceTypeManifest) ->
buildConfigForDeviceType(deviceTypeManifest, options.advanced)
Promise.join answersPromise, manifestPromise, (answers, manifest) ->
answers.version = options.version
if configurationResourceType == 'application'
answers.deviceType = deviceType
if not answers.version?
answers.version = Promise.resolve(helpers.getOsVersion(params.image, manifest)).tap (version) ->
if not version?

View File

@ -50,7 +50,11 @@ type ImgConfig = {
export function generateBaseConfig(
application: BalenaSdk.Application,
options: { version: string; appUpdatePollInterval?: number },
options: {
version: string;
appUpdatePollInterval?: number;
deviceType?: string;
},
): Promise<ImgConfig> {
options = {
...options,
@ -69,7 +73,7 @@ export function generateBaseConfig(
export function generateApplicationConfig(
application: BalenaSdk.Application,
options: { version: string },
options: { version: string; deviceType?: string },
) {
return generateBaseConfig(application, options).tap(config => {
if (semver.satisfies(options.version, '<2.7.8')) {
@ -89,7 +93,11 @@ export function generateDeviceConfig(
return balena.models.application
.get(device.belongs_to__application.__id)
.then(application => {
return generateBaseConfig(application, options).tap(config => {
const baseConfigOpts = {
...options,
deviceType: device.device_type,
};
return generateBaseConfig(application, baseConfigOpts).tap(config => {
if (
deviceApiKey == null &&
semver.satisfies(options.version, '<2.0.3')

View File

@ -92,6 +92,13 @@ export async function getManifest(
return balena.models.device.getManifestBySlug(deviceType);
}
export const areDeviceTypesCompatible = (
deviceTypeA: BalenaSdk.DeviceType,
deviceTypeB: BalenaSdk.DeviceType,
) =>
deviceTypeA.arch === deviceTypeB.arch &&
!!deviceTypeA.isDependent === !!deviceTypeB.isDependent;
export async function getOsVersion(
image: string,
manifest: BalenaSdk.DeviceType,