mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-02-12 21:56:07 +00:00
Merge pull request #2901 from balena-io/balena-device-init-v-8-1-0
os configure: Locate the boot partition w/o using the device-type.json's partition field
This commit is contained in:
commit
f0ede6fca2
16
npm-shrinkwrap.json
generated
16
npm-shrinkwrap.json
generated
@ -17,7 +17,7 @@
|
||||
"@oclif/core": "^4.1.0",
|
||||
"@sentry/node": "^6.16.1",
|
||||
"balena-config-json": "^4.2.0",
|
||||
"balena-device-init": "^8.0.0",
|
||||
"balena-device-init": "^8.1.0",
|
||||
"balena-errors": "^4.7.3",
|
||||
"balena-image-fs": "^7.0.6",
|
||||
"balena-preload": "^16.0.0",
|
||||
@ -7063,10 +7063,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/balena-device-init": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balena-device-init/-/balena-device-init-8.0.0.tgz",
|
||||
"integrity": "sha512-Kaitk9LA8oQsM1suwqYbVAeJrbmSRM4BbzsYJbczItulxRS6XadPWZeN3OLYEV5eUW2Es2SPdqQqkFvIBZriKg==",
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/balena-device-init/-/balena-device-init-8.1.0.tgz",
|
||||
"integrity": "sha512-nOtrzTcLHhn8uDvZxRRGRGcH9Ry1IoQA86wFB9qK9xEii29f6sl+bti9fOvnDyPCZ1n3pLFFSStE8yN0fNKJQQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"balena-config-json": "^4.2.0",
|
||||
"balena-image-fs": "^7.0.6",
|
||||
"balena-semver": "^2.2.0",
|
||||
"lodash": "^4.17.15",
|
||||
@ -15985,9 +15987,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/patch-package/node_modules/yaml": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
|
||||
"integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
|
||||
"integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
|
@ -196,7 +196,7 @@
|
||||
"@oclif/core": "^4.1.0",
|
||||
"@sentry/node": "^6.16.1",
|
||||
"balena-config-json": "^4.2.0",
|
||||
"balena-device-init": "^8.0.0",
|
||||
"balena-device-init": "^8.1.0",
|
||||
"balena-errors": "^4.7.3",
|
||||
"balena-image-fs": "^7.0.6",
|
||||
"balena-preload": "^16.0.0",
|
||||
|
@ -110,6 +110,27 @@ export async function getManifest(
|
||||
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.
|
||||
}
|
||||
} 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 &&
|
||||
|
@ -22,6 +22,7 @@ import { runCommand } from '../../helpers';
|
||||
import { promisify } from 'util';
|
||||
import * as tmp from 'tmp';
|
||||
import type * as $imagefs from 'balena-image-fs';
|
||||
import * as stripIndent from 'common-tags/lib/stripIndent';
|
||||
|
||||
tmp.setGracefulCleanup();
|
||||
const tmpNameAsync = promisify(tmp.tmpName);
|
||||
@ -34,6 +35,7 @@ if (process.platform !== 'win32') {
|
||||
let api: BalenaAPIMock;
|
||||
let tmpDummyPath: string;
|
||||
let tmpMatchingDtJsonPartitionPath: string;
|
||||
let tmpNonMatchingDtJsonPartitionPath: string;
|
||||
|
||||
before(async function () {
|
||||
// We conditionally import balena-image-fs, since when imported on top level then unrelated tests on win32 failed with:
|
||||
@ -47,6 +49,46 @@ if (process.platform !== 'win32') {
|
||||
'./tests/test-data/mock-jetson-nano-6.0.13.with-boot-partition-12.img',
|
||||
tmpMatchingDtJsonPartitionPath,
|
||||
);
|
||||
|
||||
tmpNonMatchingDtJsonPartitionPath = (await tmpNameAsync()) as string;
|
||||
// Create an image with a device-type.json that mentions a non matching boot partition.
|
||||
// We copy the pre-existing image and modify it, since including a separate one
|
||||
// would add 18MB more to the repository.
|
||||
await fs.copyFile(
|
||||
'./tests/test-data/mock-jetson-nano-6.0.13.with-boot-partition-12.img',
|
||||
tmpNonMatchingDtJsonPartitionPath,
|
||||
);
|
||||
await imagefs.interact(
|
||||
tmpNonMatchingDtJsonPartitionPath,
|
||||
12,
|
||||
async (_fs) => {
|
||||
const readFileAsync = promisify(_fs.readFile);
|
||||
const writeFileAsync = promisify(_fs.writeFile);
|
||||
|
||||
const dtJson = JSON.parse(
|
||||
await readFileAsync('/device-type.json', { encoding: 'utf8' }),
|
||||
);
|
||||
expect(dtJson).to.have.nested.property(
|
||||
'configuration.config.partition',
|
||||
12,
|
||||
);
|
||||
dtJson.configuration.config.partition = 999;
|
||||
await writeFileAsync('/device-type.json', JSON.stringify(dtJson));
|
||||
|
||||
await writeFileAsync(
|
||||
'/os-release',
|
||||
stripIndent`
|
||||
ID="balena-os"
|
||||
NAME="balenaOS"
|
||||
VERSION="6.1.25"
|
||||
VERSION_ID="6.1.25"
|
||||
PRETTY_NAME="balenaOS 6.1.25"
|
||||
DISTRO_CODENAME="kirkstone"
|
||||
MACHINE="jetson-nano"
|
||||
META_BALENA_VERSION="6.1.25"`,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@ -61,20 +103,20 @@ if (process.platform !== 'win32') {
|
||||
after(async () => {
|
||||
await fs.unlink(tmpDummyPath);
|
||||
await fs.unlink(tmpMatchingDtJsonPartitionPath);
|
||||
await fs.unlink(tmpNonMatchingDtJsonPartitionPath);
|
||||
});
|
||||
|
||||
it('should inject a valid config.json file to an image with partition 12 as boot & matching device-type.json ', async () => {
|
||||
it('should detect the OS version and inject a valid config.json file to a 6.0.13 image with partition 12 as boot & matching device-type.json', async () => {
|
||||
api.expectGetApplication();
|
||||
api.expectGetDeviceTypes();
|
||||
// TODO: this shouldn't be necessary & the CLI should be able to find
|
||||
// It should not reach to /config or /device-types/v1 but instead find
|
||||
// everything required from the device-type.json in the image.
|
||||
api.expectGetConfigDeviceTypes();
|
||||
// api.expectGetConfigDeviceTypes();
|
||||
api.expectDownloadConfig();
|
||||
|
||||
const command: string[] = [
|
||||
`os configure ${tmpMatchingDtJsonPartitionPath}`,
|
||||
'--device-type jetson-nano',
|
||||
'--version 6.0.13',
|
||||
'--fleet testApp',
|
||||
'--config-app-update-poll-interval 10',
|
||||
'--config-network ethernet',
|
||||
@ -111,6 +153,53 @@ if (process.platform !== 'win32') {
|
||||
expect(configObj).to.have.property('initialDeviceName', 'testDeviceName');
|
||||
});
|
||||
|
||||
it('should detect the OS version and inject a valid config.json file to a 6.1.25 image with partition 12 as boot & a non-matching device-type.json', async () => {
|
||||
api.expectGetApplication();
|
||||
api.expectGetDeviceTypes();
|
||||
// It should not reach to /config or /device-types/v1 but instead find
|
||||
// everything required from the device-type.json in the image.
|
||||
// api.expectGetConfigDeviceTypes();
|
||||
api.expectDownloadConfig();
|
||||
|
||||
const command: string[] = [
|
||||
`os configure ${tmpNonMatchingDtJsonPartitionPath}`,
|
||||
'--device-type jetson-nano',
|
||||
'--fleet testApp',
|
||||
'--config-app-update-poll-interval 10',
|
||||
'--config-network ethernet',
|
||||
'--initial-device-name testDeviceName',
|
||||
'--provisioning-key-name testKey',
|
||||
'--provisioning-key-expiry-date 2050-12-12',
|
||||
];
|
||||
|
||||
const { err } = await runCommand(command.join(' '));
|
||||
expect(err.join('')).to.equal('');
|
||||
|
||||
// confirm the image contains a config.json...
|
||||
const config = await imagefs.interact(
|
||||
tmpNonMatchingDtJsonPartitionPath,
|
||||
12,
|
||||
async (_fs) => {
|
||||
const readFileAsync = promisify(_fs.readFile);
|
||||
const dtJson = JSON.parse(
|
||||
await readFileAsync('/device-type.json', { encoding: 'utf8' }),
|
||||
);
|
||||
// confirm that the device-type.json mentions the expected partition
|
||||
expect(dtJson).to.have.nested.property(
|
||||
'configuration.config.partition',
|
||||
999,
|
||||
);
|
||||
return await readFileAsync('/config.json');
|
||||
},
|
||||
);
|
||||
expect(config).to.not.be.empty;
|
||||
|
||||
// confirm the image has the correct config.json values...
|
||||
const configObj = JSON.parse(config.toString('utf8'));
|
||||
expect(configObj).to.have.property('deviceType', 'jetson-nano');
|
||||
expect(configObj).to.have.property('initialDeviceName', 'testDeviceName');
|
||||
});
|
||||
|
||||
// TODO: In the next major consider just failing when we can't find a device-types.json in the image.
|
||||
it('should inject a valid config.json file to a dummy image', async () => {
|
||||
api.expectGetApplication();
|
||||
@ -134,7 +223,20 @@ if (process.platform !== 'win32') {
|
||||
const { err } = await runCommand(command.join(' '));
|
||||
// Once we replace the dummy.img with one that includes a os-release & device-type.json
|
||||
// then we should be able to change this to expect no errors.
|
||||
expect(err.join('')).to.equal('');
|
||||
expect(
|
||||
err.flatMap((line) => line.split('\n')).filter((line) => line !== ''),
|
||||
).to.deep.equal(
|
||||
stripIndent`
|
||||
[warn] "${tmpDummyPath}":
|
||||
[warn] 1 partition(s) found, but none containing file "/device-type.json".
|
||||
[warn] Assuming default boot partition number '1'.
|
||||
[warn] "${tmpDummyPath}":
|
||||
[warn] Could not find a previous "/config.json" file in partition '1'.
|
||||
[warn] Proceeding anyway, but this is unexpected.
|
||||
[warn] Error while finding a device-type.json on the provided image path. Attempting to fetch from the API.`.split(
|
||||
'\n',
|
||||
),
|
||||
);
|
||||
|
||||
// confirm the image contains a config.json...
|
||||
const config = await imagefs.interact(tmpDummyPath, 1, async (_fs) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user