os configure, local configure: Fix "Unsupported filesystem" error

When configuring a BalenaOS image with system connections using the CLI,
the function assumed that the boot partition was always 1. This is not
the case for every supported board. Therefore, a new function is added,
which automatically determines the boot partition number and allows
users to configure the image with system connection settings.

This change affects both the `balena local configure` and `balena os configure` commands.

Change-type: patch
This commit is contained in:
Roland Kajatin 2021-05-15 16:18:14 +02:00
parent e2ff561728
commit 501882fd26
4 changed files with 54 additions and 16 deletions

View File

@ -108,10 +108,9 @@ export default class LocalConfigureCmd extends Command {
console.log('Done!');
}
readonly BOOT_PARTITION = 1;
readonly CONNECTIONS_FOLDER = '/system-connections';
getConfigurationSchema(connectionFileName?: string) {
getConfigurationSchema(bootPartition: number, connectionFileName?: string) {
connectionFileName ??= 'resin-wifi';
return {
mapper: [
@ -150,14 +149,14 @@ export default class LocalConfigureCmd extends Command {
path: this.CONNECTIONS_FOLDER.slice(1),
// Reconfix still uses the older resin-image-fs, so still needs an
// object-based partition definition.
partition: this.BOOT_PARTITION,
partition: bootPartition,
},
},
config_json: {
type: 'json',
location: {
path: 'config.json',
partition: this.BOOT_PARTITION,
partition: bootPartition,
},
},
},
@ -250,21 +249,20 @@ export default class LocalConfigureCmd extends Command {
async prepareConnectionFile(target: string) {
const _ = await import('lodash');
const imagefs = await import('balena-image-fs');
const helpers = await import('../../utils/helpers');
const files = await imagefs.interact(
target,
this.BOOT_PARTITION,
async (_fs) => {
return await promisify(_fs.readdir)(this.CONNECTIONS_FOLDER);
},
);
const bootPartition = await helpers.getBootPartition(target);
const files = await imagefs.interact(target, bootPartition, async (_fs) => {
return await promisify(_fs.readdir)(this.CONNECTIONS_FOLDER);
});
let connectionFileName;
if (_.includes(files, 'resin-wifi')) {
// The required file already exists, nothing to do
} else if (_.includes(files, 'resin-sample.ignore')) {
// Fresh image, new mode, accoding to https://github.com/balena-os/meta-balena/pull/770/files
await imagefs.interact(target, this.BOOT_PARTITION, async (_fs) => {
await imagefs.interact(target, bootPartition, async (_fs) => {
const readFileAsync = promisify(_fs.readFile);
const writeFileAsync = promisify(_fs.writeFile);
const contents = await readFileAsync(
@ -287,14 +285,14 @@ export default class LocalConfigureCmd extends Command {
connectionFileName = 'resin-sample';
} else {
// In case there's no file at all (shouldn't happen normally, but the file might have been removed)
await imagefs.interact(target, this.BOOT_PARTITION, async (_fs) => {
await imagefs.interact(target, bootPartition, async (_fs) => {
return await promisify(_fs.writeFile)(
`${this.CONNECTIONS_FOLDER}/resin-wifi`,
this.CONNECTION_FILE,
);
});
}
return await this.getConfigurationSchema(connectionFileName);
return await this.getConfigurationSchema(bootPartition, connectionFileName);
}
async removeHostname(schema: any) {

View File

@ -25,7 +25,6 @@ import * as cf from '../../utils/common-flags';
import { getBalenaSdk, stripIndent, getCliForm } from '../../utils/lazy';
import { applicationIdInfo } from '../../utils/messages';
const BOOT_PARTITION = 1;
const CONNECTIONS_FOLDER = '/system-connections';
interface FlagsDef {
@ -281,10 +280,12 @@ export default class OsConfigureCmd extends Command {
}),
);
const bootPartition = await helpers.getBootPartition(params.image);
const imagefs = await import('balena-image-fs');
for (const { name, content } of files) {
await imagefs.interact(image, BOOT_PARTITION, async (_fs) => {
await imagefs.interact(image, bootPartition, async (_fs) => {
return await promisify(_fs.writeFile)(
path.join(CONNECTIONS_FOLDER, name),
content,

View File

@ -202,6 +202,43 @@ function getApplication(
) as Promise<ApplicationWithDeviceType>;
}
/**
* Returns the boot partition number of the given image.
* @param imagePath Local filesystem path to a balenaOS image file
*/
export async function getBootPartition(imagePath: string): Promise<number> {
const imagefs = await import('balena-image-fs');
const filedisk = await import('file-disk');
const partitioninfo = await import('partitioninfo');
const partitionNumber = await filedisk.withOpenFile(
imagePath,
'r',
async (handle) => {
const disk = new filedisk.FileDisk(handle, true, false, false);
const { partitions } = await partitioninfo.getPartitions(disk, {
includeExtended: false,
getLogical: true,
});
for (const { index } of partitions) {
try {
return await imagefs.interact(disk, index, async (fs) => {
const statAsync = promisify(fs.stat);
const stats = await statAsync('/device-type.json');
if (stats.isFile()) {
return index;
}
});
} catch (error) {
// noop
}
}
},
);
return partitionNumber ?? 1;
}
const second = 1000; // 1000 milliseconds
const minute = 60 * second;
export const delay = promisify(setTimeout);

View File

@ -230,6 +230,7 @@
"express": "^4.13.3",
"fast-boot2": "^1.1.0",
"fast-levenshtein": "^3.0.0",
"file-disk": "^8.0.1",
"get-stdin": "^8.0.0",
"global-agent": "^2.1.12",
"global-tunnel-ng": "^2.1.1",
@ -251,6 +252,7 @@
"node-unzip-2": "^0.2.8",
"oclif": "^1.16.1",
"open": "^7.1.0",
"partitioninfo": "^6.0.2",
"patch-package": "^6.4.7",
"prettyjson": "^1.1.3",
"progress-stream": "^2.0.0",