diff --git a/docs/balena-cli.md b/docs/balena-cli.md index 3d5c2f4964..cc02672ab8 100644 --- a/docs/balena-cli.md +++ b/docs/balena-cli.md @@ -3048,6 +3048,7 @@ because we need to access the raw devices directly. Examples: + $ balena os initialize ../path/rpi.img $ balena os initialize ../path/rpi.img --type raspberry-pi ### Arguments diff --git a/src/commands/internal/osinit.ts b/src/commands/internal/osinit.ts index 4a420ae88b..0d98802c04 100644 --- a/src/commands/internal/osinit.ts +++ b/src/commands/internal/osinit.ts @@ -38,9 +38,6 @@ export default class OsinitCmd extends Command { image: Args.string({ required: true, }), - type: Args.string({ - required: true, - }), config: Args.string({ required: true, }), @@ -58,7 +55,7 @@ export default class OsinitCmd extends Command { const { getManifest, osProgressHandler } = await import( '../../utils/helpers' ); - const manifest = await getManifest(params.image, params.type); + const manifest = await getManifest(params.image); const { initialize } = await import('balena-device-init'); const initializeEmitter = await initialize(params.image, manifest, config); diff --git a/src/commands/os/build-config.ts b/src/commands/os/build-config.ts index 7a15613b59..bfa8bd3d09 100644 --- a/src/commands/os/build-config.ts +++ b/src/commands/os/build-config.ts @@ -38,6 +38,7 @@ export default class OsBuildConfigCmd extends Command { description: 'os image', required: true, }), + // TODO: Drop this arg on the next major 'device-type': Args.string({ description: 'device type', required: true, @@ -63,6 +64,12 @@ export default class OsBuildConfigCmd extends Command { const { writeFile } = (await import('fs')).promises; + // the "device-type" arg will be removed on the next major - warn user + if (params['device-type'] != null) { + const { deviceTypeArgDeprecation } = await import('../../utils/messages'); + console.log(deviceTypeArgDeprecation('device-type')); + } + const config = await this.buildConfig( params.image, params['device-type'], @@ -74,7 +81,12 @@ export default class OsBuildConfigCmd extends Command { console.info(`Config file "${options.output}" created successfully.`); } - async buildConfig(image: string, deviceTypeSlug: string, advanced: boolean) { + async buildConfig( + image: string, + // TODO: Drop this parameter on the next major + deviceTypeSlug: string | undefined, + advanced: boolean, + ) { advanced = advanced || false; const { getManifest } = await import('../../utils/helpers'); diff --git a/src/commands/os/configure.ts b/src/commands/os/configure.ts index 3d9640ce06..47919673fe 100644 --- a/src/commands/os/configure.ts +++ b/src/commands/os/configure.ts @@ -197,6 +197,7 @@ export default class OsConfigureCmd extends Command { const deviceTypeManifest = await helpers.getManifest( params.image, + // TODO: Drop this in the next major in favor of calling checkManifestMatchesDeviceType() directly deviceTypeSlug, ); diff --git a/src/commands/os/initialize.ts b/src/commands/os/initialize.ts index c6d4b52aef..f9cf25abe2 100644 --- a/src/commands/os/initialize.ts +++ b/src/commands/os/initialize.ts @@ -37,6 +37,7 @@ export default class OsInitializeCmd extends Command { `; public static examples = [ + '$ balena os initialize ../path/rpi.img', '$ balena os initialize ../path/rpi.img --type raspberry-pi', ]; @@ -63,6 +64,11 @@ export default class OsInitializeCmd extends Command { console.info(`Initializing device ${INIT_WARNING_MESSAGE}`); const manifest = await getManifest(params.image, options.type); + // the "type" arg will be removed on the next major - warn user + if (options.type != null) { + const { deviceTypeArgDeprecation } = await import('../../utils/messages'); + console.log(deviceTypeArgDeprecation('type')); + } const answers = await getCliForm().run(manifest.initialization?.options, { override: { @@ -81,13 +87,7 @@ export default class OsInitializeCmd extends Command { await safeUmount(answers.drive); } - await sudo([ - 'internal', - 'osinit', - params.image, - options.type, - JSON.stringify(answers), - ]); + await sudo(['internal', 'osinit', params.image, JSON.stringify(answers)]); if (answers.drive != null) { const { safeUmount } = await import('../../utils/umount'); diff --git a/src/utils/common-flags.ts b/src/utils/common-flags.ts index ba84db1390..3d5a69dad2 100644 --- a/src/utils/common-flags.ts +++ b/src/utils/common-flags.ts @@ -94,7 +94,6 @@ export const deviceType = Flags.string({ description: 'device type (Check available types with `balena device-type list`)', char: 't', - required: true, }); export const json = Flags.boolean({ diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index f98d08f89a..06c6075400 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -5,7 +5,7 @@ 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 + 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, @@ -103,48 +103,67 @@ export async function runCommand(commandArgs: string[]): Promise { return run(commandArgs) as Promise; } +export async function checkManifestMatchesDeviceType( + manifest: BalenaSdk.DeviceTypeJson.DeviceType, + deviceType: string, +) { + if ( + manifest.slug !== deviceType && + manifest.slug !== + (await getBalenaSdk().models.deviceType.get(deviceType)).slug + ) { + const { ExpectedError } = await import('../errors'); + throw new ExpectedError( + `The device type of the provided OS image ${manifest.slug}, does not match the expected device type ${deviceType}`, + ); + } +} + export async function getManifest( image: string, - deviceType: string, + // TODO: Drop this parameter in the next major + fallbackDeviceType?: string, ): Promise { const init = await import('balena-device-init'); - const sdk = getBalenaSdk(); const manifest = await init.getImageManifest(image); - if (manifest != null) { - const config = manifest.configuration?.config; - if (config?.partition != null) { - const { getBootPartition } = await import('balena-config-json'); - // Find the device-type.json property that holds the boot partition number for - // this device type (config.partition or config.partition.primary) and overwrite it - // with the boot partition number that was found by inspecting the image. - // since it's deprecated & no longer updated for newer releases. - if (typeof config.partition === 'number') { - config.partition = await getBootPartition(image); - } else if (config.partition.primary != null) { - config.partition.primary = await getBootPartition(image); - } - // TODO: Add handling for when we no longer include a `config.partition` at all. + if (manifest == null) { + // TODO: Change this in the next major to throw when not being able to find the manifest + // on the provided image, after confirming that this works for all supported OS versions. + if (fallbackDeviceType != null) { + console.error( + `[warn] Error while finding a device-type.json on the provided image path. Attempting to fetch from the API.`, + ); + const sdk = getBalenaSdk(); + return await sdk.models.config.getDeviceTypeManifestBySlug( + fallbackDeviceType, + ); } - } else { - // TODO: Change this in the next major to throw, after confirming that this works for all supported OS versions. - console.error( - `[warn] Error while finding a device-type.json on the provided image path. Attempting to fetch from the API.`, - ); - } - if ( - manifest != null && - manifest.slug !== deviceType && - manifest.slug !== (await sdk.models.deviceType.get(deviceType)).slug - ) { const { ExpectedError } = await import('../errors'); throw new ExpectedError( - `The device type of the provided OS image ${manifest.slug}, does not match the expected device type ${deviceType}`, + `Could not find the manifest on the provided image path '${image}'. Please double check that this is a valid balenaOS image.`, ); } - return ( - manifest ?? - (await sdk.models.config.getDeviceTypeManifestBySlug(deviceType)) - ); + + const config = manifest.configuration?.config; + if (config?.partition != null) { + const { getBootPartition } = await import('balena-config-json'); + // Find the device-type.json property that holds the boot partition number for + // this device type (config.partition or config.partition.primary) and overwrite it + // with the boot partition number that was found by inspecting the image. + // since it's deprecated & no longer updated for newer releases. + if (typeof config.partition === 'number') { + config.partition = await getBootPartition(image); + } else if (config.partition.primary != null) { + config.partition.primary = await getBootPartition(image); + } + // TODO: Add handling for when we no longer include a `config.partition` at all. + } + // TODO: Drop this in the next major along with the fallbackDeviceType argument + // and move the checkManifestMatchesDeviceType() to the caller side when still necessary. + if (fallbackDeviceType != null) { + await checkManifestMatchesDeviceType(manifest, fallbackDeviceType); + } + return manifest; } export const areDeviceTypesCompatible = async ( diff --git a/src/utils/messages.ts b/src/utils/messages.ts index 88a21c7de0..0c10dc1e56 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -189,6 +189,12 @@ If you have a particular use for buildArg, which is not satisfied by build-time secrets, please contact us via support or the forums: https://forums.balena.io/ \n`; +export const deviceTypeArgDeprecation = (paramName: string) => `\ +WARNING: You have specified a '--${paramName}' option, which is now deprecated, and +may be removed in the future. The balena-cli will now try to auto-detect +the device type of the provided balenaOS image. +\n`; + export function getNodeEngineVersionWarn( version: string, validVersions: string,