From 2bde660ec875f5160449f94670a58442462c63b7 Mon Sep 17 00:00:00 2001 From: Christina Ying Wang Date: Mon, 9 Dec 2024 12:49:18 -0800 Subject: [PATCH] Target hostapp hook: Only patch should_be_operated_by__release if null or older release With Supervisor managed HUP, we'll soon start relying on should_be_operated_by__release to determine the target hostapp release. The Supervisor reports os_version and os_variant on device provision, and the current target-hostapp hook will set the should_be_operated_by field when receiving that initial state report. However, SV manage HUP means this field could be set prior to device provision, so we don't want to overwrite it if it's not null. If the should_be_operated_by field has a release that's older than the reported OS release though, we should update the field to the reported OS release. Change-type: patch Signed-off-by: Christina Ying Wang --- src/features/hostapp/hooks/target-hostapp.ts | 20 +++- test/15_target-hostapp.ts | 103 +++++++++++++++++++ 2 files changed, 119 insertions(+), 4 deletions(-) diff --git a/src/features/hostapp/hooks/target-hostapp.ts b/src/features/hostapp/hooks/target-hostapp.ts index e19c82209..f96614094 100644 --- a/src/features/hostapp/hooks/target-hostapp.ts +++ b/src/features/hostapp/hooks/target-hostapp.ts @@ -63,7 +63,8 @@ hooks.addPureHook('PATCH', 'resin', 'device', { /** * When a device checks in with it's initial OS version, set the corresponding should_be_operated_by__release resource - * using its current reported version. + * using its current reported version only if should_be_operated_by__release is null or older than the current OS version. + * This ensures the target hostapp field isn't overwritten by an older OS release when a device reports OS info on provision. */ hooks.addPureHook('PATCH', 'resin', 'device', { async PRERUN(args) { @@ -121,7 +122,7 @@ async function setOSReleaseResource( id: { $in: deviceIds }, os_version: null, }, - $select: ['id', 'is_of__device_type'], + $select: ['id', 'should_be_operated_by__release', 'is_of__device_type'], }, }); @@ -147,8 +148,6 @@ async function setOSReleaseResource( return Promise.all( Array.from(devicesByDeviceTypeId.entries()).map( async ([deviceTypeId, affectedDevices]) => { - const affectedDeviceIds = affectedDevices.map((d) => d.id); - const osRelease = await getOSReleaseResource( api, osVersion, @@ -160,6 +159,19 @@ async function setOSReleaseResource( return; } + // Only patch should_be_operated_by__release if it's null or older than the reported OS release + const affectedDeviceIds = affectedDevices + .filter( + (d) => + d.should_be_operated_by__release == null || + d.should_be_operated_by__release.__id < osRelease.id, + ) + .map((d) => d.id); + + if (affectedDeviceIds.length === 0) { + return; + } + await rootApi.patch({ resource: 'device', options: { diff --git a/test/15_target-hostapp.ts b/test/15_target-hostapp.ts index 27233202d..eddaed09b 100644 --- a/test/15_target-hostapp.ts +++ b/test/15_target-hostapp.ts @@ -633,6 +633,109 @@ export default () => { .expect(200); expect(dev.d[0].should_be_operated_by__release).to.be.null; }); + + it('should overwrite existing should_be_operated_by__release when device reports OS info if should_be_operated_by__release is null', async () => { + // First set up a device with a specific target release + const device = await fakeDevice.provisionDevice(admin, applicationId); + await supertest(admin) + .patch(`/${version}/device(${device.id})`) + .send({ + should_be_operated_by__release: null, + }) + .expect(200); + + // Verify initial state + await expectResourceToMatch(pineUser, 'device', device.id, { + should_be_operated_by__release: null, + }); + + // Now patch the device with OS info + await device.patchStateV2({ + local: { + os_version: 'balenaOS 2.50.0+rev1', + os_variant: 'prod', + }, + }); + + // Verify the should_be_operated_by__release was updated with OS info + await expectResourceToMatch(pineUser, 'device', device.id, { + should_be_operated_by__release: { + __id: nuc2_50_0_rev1prodId, + }, + os_version: 'balenaOS 2.50.0+rev1', + os_variant: 'prod', + }); + }); + + it('should not overwrite existing should_be_operated_by__release when device reports OS info if should_be_operated_by__release is newer', async () => { + // First set up a device with a specific target release + const device = await fakeDevice.provisionDevice(admin, applicationId); + await supertest(admin) + .patch(`/${version}/device(${device.id})`) + .send({ + should_be_operated_by__release: nuc2_51_0_rev1prodTagAndSemverId, + }) + .expect(200); + + // Verify initial state + await expectResourceToMatch(pineUser, 'device', device.id, { + should_be_operated_by__release: { + __id: nuc2_51_0_rev1prodTagAndSemverId, + }, + }); + + // Now patch the device with OS info that would normally map to a different & older release + await device.patchStateV2({ + local: { + os_version: 'balenaOS 2.50.0+rev1', + os_variant: 'prod', + }, + }); + + // Verify the should_be_operated_by__release was not changed + await expectResourceToMatch(pineUser, 'device', device.id, { + should_be_operated_by__release: { + __id: nuc2_51_0_rev1prodTagAndSemverId, + }, + os_version: 'balenaOS 2.50.0+rev1', + os_variant: 'prod', + }); + }); + + it('should overwrite existing should_be_operated_by__release when device reports OS info if should_be_operated_by__release is older', async () => { + // First set up a device with a specific target release + const device = await fakeDevice.provisionDevice(admin, applicationId); + await supertest(admin) + .patch(`/${version}/device(${device.id})`) + .send({ + should_be_operated_by__release: unifiedSemverOnlyHostAppReleaseId, + }) + .expect(200); + + // Verify initial state + await expectResourceToMatch(pineUser, 'device', device.id, { + should_be_operated_by__release: { + __id: unifiedSemverOnlyHostAppReleaseId, + }, + }); + + // Now patch the device with OS info that would normally map to a different & newer release + await device.patchStateV2({ + local: { + os_version: 'balenaOS 2.88.5+rev1', + os_variant: 'prod', + }, + }); + + // Verify the should_be_operated_by__release was updated with newer release + await expectResourceToMatch(pineUser, 'device', device.id, { + should_be_operated_by__release: { + __id: unifiedSemverRevHostAppReleaseId, + }, + os_version: 'balenaOS 2.88.5+rev1', + os_variant: 'prod', + }); + }); }); }); };