From 5af0136afea73fd5add3eb68776404291690ea66 Mon Sep 17 00:00:00 2001 From: Christina Ying Wang Date: Wed, 20 Nov 2024 02:11:16 -0800 Subject: [PATCH] Pin to target OS release in startOsUpdate and skip action-based HUP if OS >=vTODO Change-type: minor Signed-off-by: Christina Ying Wang --- src/models/device.ts | 118 +++++++++++++++------ src/util/device-actions/os-update/index.ts | 4 +- 2 files changed, 88 insertions(+), 34 deletions(-) diff --git a/src/models/device.ts b/src/models/device.ts index 2da50a1cd..c9d0b5f56 100644 --- a/src/models/device.ts +++ b/src/models/device.ts @@ -107,6 +107,7 @@ export type DeviceMetrics = Pick< >; const DEFAULT_DAYS_OF_REQUESTED_HISTORY = 7; +const SUPERVISOR_MANAGED_HUP_MIN_OS_VERSION = 'vTODO'; const getDeviceModel = function ( deps: InjectedDependenciesParam, @@ -392,9 +393,13 @@ const getDeviceModel = function ( groupByNavigationPoperty: 'is_of__device_type', fn: async (devices, deviceTypeId) => { const dt = await getDeviceType(deviceTypeId); + const toPinOSRelease = new Map(); for (const device of devices) { - // this will throw an error if the action isn't available - exports._checkOsUpdateTarget( + // Validate current device type, OS version, and OS variant. + // Ensure target OS version is newer than current OS version. + // Also ensure device is online if we're using the actions + // proxy which requires a connection to the device. + const currentOsVersion = exports._checkOsUpdateTarget( { ...device, is_of__device_type: [dt], @@ -402,27 +407,58 @@ const getDeviceModel = function ( targetOsVersion, ); + // Validate target OS version is available for the device type. const osVersions = await getAvailableOsVersions(dt.slug, isDraft); - if ( - !osVersions.some( - (v) => bSemver.compare(v.raw_version, targetOsVersion) === 0, - ) - ) { + // Find target OS release + const targetRelease = osVersions.find( + (v) => bSemver.compare(v.raw_version, targetOsVersion) === 0, + ); + + if (!targetRelease) { throw new errors.BalenaInvalidParameterError( 'targetOsVersion', targetOsVersion, ); } + + // Mark device to skip action proxy based HUP + // if current OS version >= SUPERVISOR_MANAGED_HUP_MIN_OS_VERSION + if ( + bSemver.compare( + currentOsVersion, + SUPERVISOR_MANAGED_HUP_MIN_OS_VERSION, + ) >= 0 + ) { + toPinOSRelease.set(device.uuid, targetRelease.id); + } } - // use the v2 device actions api for detached updates + // Trigger OS update via actions proxy, or pin device to target OS release + // if current OS version >= SUPERVISOR_MANAGED_HUP_MIN_OS_VERSION await limitedMap(devices, async (device) => { - results[device.uuid] = await osUpdateHelper.startOsUpdate( - device.uuid, - targetOsVersion, - options.runDetached === true ? 'v2' : 'v1', - ); + const targetReleaseId = toPinOSRelease.get(device.uuid); + if (targetReleaseId) { + // Pin device to target OS release + await sdkInstance.pine.patch({ + resource: 'device', + id: device.id, + body: { + should_be_operated_by__release: targetReleaseId, + }, + }); + + results[device.uuid] = { + status: 'pinned', + }; + } else { + results[device.uuid] = await osUpdateHelper.startOsUpdate( + device.uuid, + targetOsVersion, + // use the v2 device actions api for detached updates + options.runDetached === true ? 'v2' : 'v1', + ); + } }); }, }); @@ -2199,7 +2235,7 @@ const getDeviceModel = function ( * @param {Object} device - A device object * @param {String} targetOsVersion - semver-compatible version for the target device * @throws Exception if update isn't supported - * @returns {void} + * @returns {String} current OS version in semver format */ _checkOsUpdateTarget( { @@ -2212,32 +2248,28 @@ const getDeviceModel = function ( is_of__device_type: [Pick]; }, targetOsVersion: string, - ) { + ): string { if (!uuid) { throw new Error('The uuid of the device is not available'); } - if (!is_online) { - throw new Error(`The device is offline: ${uuid}`); - } - if (!os_version) { throw new Error( `The current os version of the device is not available: ${uuid}`, ); } - const deviceType = is_of__device_type?.[0]?.slug; - if (!deviceType) { + // Error if the property is missing + if (os_variant === undefined) { throw new Error( - `The device type of the device is not available: ${uuid}`, + `The os variant of the device is not available: ${uuid}`, ); } - // error the property is missing - if (os_variant === undefined) { + const deviceType = is_of__device_type?.[0]?.slug; + if (!deviceType) { throw new Error( - `The os variant of the device is not available: ${uuid}`, + `The device type of the device is not available: ${uuid}`, ); } @@ -2247,15 +2279,35 @@ const getDeviceModel = function ( os_variant, }) ?? os_version; - // if the os_version couldn't be parsed - // rely on getHUPActionType to throw an error + // Validate target OS version is newer than current OS version + if (bSemver.compare(currentOsVersion, targetOsVersion) >= 0) { + throw new Error('OS downgrades are not allowed'); + } - // this will throw an error if the action isn't available - hupActionHelper().getHUPActionType( - deviceType, - currentOsVersion, - targetOsVersion, - ); + // If the os_version couldn't be parsed, rely on getHUPActionType to + // throw an error if = vTODO which supports Supervisor managed HUP. + | 'pinned'; parameters?: { target_version: string; };