Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Try to find the boot partition by inspecting the image #47

Merged
merged 1 commit into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"wary": "^1.1.1"
},
"dependencies": {
"balena-config-json": "^4.2.0",
"balena-image-fs": "^7.0.6",
"balena-semver": "^2.2.0",
"lodash": "^4.17.15",
Expand Down
136 changes: 76 additions & 60 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import _ from 'lodash';
import path from 'path';
import * as util from 'node:util';
import * as imagefs from 'balena-image-fs';
import { getBootPartition } from 'balena-config-json';

import type {
DeviceTypeConfigurationConfig,
DeviceTypeJson,
Expand All @@ -34,22 +36,28 @@ import type {
* utils.getImageManifest('path/to/image.img', 'raspberry-pi').then (manifest) ->
* console.log(manifest)
*/
export function getImageManifest(
export async function getImageManifest(
image: string,
): Promise<DeviceTypeJson | null> {
// Attempt to read manifest from the first
// partition, but fallback to the API if
// we encounter any errors along the way.
return Promise.resolve(
imagefs.interact(image, 1, function (_fs) {
const readFileAsync = util.promisify(_fs.readFile);
return readFileAsync('/device-type.json', {
encoding: 'utf8',
});
}),
)
.then(JSON.parse)
.catch(() => null);
// Attempt to find the boot partition from the image
// or fallback to the first partition,
// and then try to read the manifest.
try {
const bootPartitionNumber = (await getBootPartition(image)) ?? 1;
const manifestString = await imagefs.interact(
image,
bootPartitionNumber,
function (_fs) {
const readFileAsync = util.promisify(_fs.readFile);
return readFileAsync('/device-type.json', {
encoding: 'utf8',
});
},
);
return JSON.parse(manifestString);
} catch {
return null;
}
}

/**
Expand Down Expand Up @@ -133,60 +141,68 @@ export function definitionForImage<
* utils.getImageOsVersion('path/to/image.img', manifest).then (version) ->
* console.log(version)
*/
export function getImageOsVersion(
export async function getImageOsVersion(
image: string,
manifest: DeviceTypeJson | undefined,
): Promise<string | null> {
// Try to determine the location where os-release is stored. This is always
// stored alongside "config.json" so look into the manifest if given, and
// stored alongside "config.json" so look into the manifest if given,
// or fallback to getting the manifest by inspecting the image, and
// fallback to a sensible default if not. This should be able to handle a
// wide range of regular images with several partitions as well as cases like
// with Edison where "image" points to a folder structure.

const definition = {
...convertFilePathDefinition(
definitionForImage(
image,
manifest?.configuration?.config ?? { partition: 1 },
try {
manifest ??= (await getImageManifest(image)) ?? undefined;

const definition = {
...convertFilePathDefinition(
definitionForImage(
image,
manifest?.configuration?.config ?? {
partition: (await getBootPartition(image)) ?? 1,
},
),
),
),
path: '/os-release',
};

return Promise.resolve(
imagefs.interact(definition.image, definition.partition, function (_fs) {
const readFileAsync = util.promisify(_fs.readFile);
return readFileAsync(definition.path, { encoding: 'utf8' });
}),
)
.then(function (osReleaseString) {
const parsedOsRelease = _(osReleaseString)
.split('\n')
.map(function (line) {
const match = line.match(/(.*)=(.*)/);
if (match) {
return [
match[1],
match[2].replace(/^"(.*)"$/, '$1').replace(/^'(.*)'$/, '$1'),
];
} else {
return false;
}
})
.filter()
.fromPairs()
.value();

if (
parsedOsRelease.NAME !== 'Resin OS' &&
parsedOsRelease.NAME !== 'balenaOS'
) {
return null;
} else {
return parsedOsRelease.VERSION || null;
}
})
.catch(() => null);
path: '/os-release',
};

const osReleaseString = await imagefs.interact(
definition.image,
definition.partition,
function (_fs) {
const readFileAsync = util.promisify(_fs.readFile);
return readFileAsync(definition.path, { encoding: 'utf8' });
},
);

const parsedOsRelease = _(osReleaseString)
.split('\n')
.map(function (line) {
const match = line.match(/(.*)=(.*)/);
if (match) {
return [
match[1],
match[2].replace(/^"(.*)"$/, '$1').replace(/^'(.*)'$/, '$1'),
];
} else {
return false;
}
})
.filter()
.fromPairs()
.value();

if (
parsedOsRelease.NAME !== 'Resin OS' &&
parsedOsRelease.NAME !== 'balenaOS'
) {
return null;
} else {
return parsedOsRelease.VERSION || null;
}
} catch {
return null;
}
}

/**
Expand Down
Loading