From 320b4864d97c7fea234bc25ec755e2e5013b2a18 Mon Sep 17 00:00:00 2001 From: Scott Lowe Date: Wed, 15 Apr 2020 12:39:27 +0200 Subject: [PATCH] device os-update: allow host OS upgrade with development balenaOS images also: fix `device os-update` incorrectly showing 0% progress convert `device os-update` to use async/await Change-type: minor Resolves: #1725 Signed-off-by: Scott Lowe --- lib/actions/device_ts.ts | 114 ++++++++++++++++++----------------- lib/actions/keys.ts | 4 +- lib/utils/device/progress.ts | 2 +- lib/utils/patterns.ts | 12 ++-- npm-shrinkwrap.json | 54 +++++++++++------ package.json | 2 +- 6 files changed, 105 insertions(+), 83 deletions(-) diff --git a/lib/actions/device_ts.ts b/lib/actions/device_ts.ts index 9e04e18a..da30ee8a 100644 --- a/lib/actions/device_ts.ts +++ b/lib/actions/device_ts.ts @@ -1,5 +1,5 @@ /* -Copyright 2016-2017 Balena +Copyright 2016-2020 Balena Ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ limitations under the License. import { Device } from 'balena-sdk'; import { CommandDefinition } from 'capitano'; import { stripIndent } from 'common-tags'; +import { ExpectedError } from '../errors'; import { getBalenaSdk } from '../utils/lazy'; import { normalizeUuidProp } from '../utils/normalization'; import * as commandOptions from './command-options'; @@ -55,62 +56,67 @@ export const osUpdate: CommandDefinition = { const patterns = await import('../utils/patterns'); const form = await import('resin-cli-form'); - return sdk.models.device - .get(params.uuid, { - $select: ['uuid', 'device_type', 'os_version', 'os_variant'], - }) - .then(async ({ uuid, device_type, os_version, os_variant }) => { - const currentOsVersion = sdk.models.device.getOsVersion({ - os_version, - os_variant, - } as Device); - if (!currentOsVersion) { - patterns.exitWithExpectedError( - 'The current os version of the device is not available', - ); - // Just to make TS happy - return; - } + // Get device info + const { + uuid, + device_type, + os_version, + os_variant, + } = await sdk.models.device.get(params.uuid, { + $select: ['uuid', 'device_type', 'os_version', 'os_variant'], + }); - return sdk.models.os - .getSupportedOsUpdateVersions(device_type, currentOsVersion) - .then(hupVersionInfo => { - if (hupVersionInfo.versions.length === 0) { - patterns.exitWithExpectedError( - 'There are no available Host OS update targets for this device', - ); - } + // Get current device OS version + const currentOsVersion = sdk.models.device.getOsVersion({ + os_version, + os_variant, + } as Device); + if (!currentOsVersion) { + throw new ExpectedError( + 'The current os version of the device is not available', + ); + } - if (options.version != null) { - if (!_.includes(hupVersionInfo.versions, options.version)) { - patterns.exitWithExpectedError( - 'The provided version is not in the Host OS update targets for this device', - ); - } - return options.version; - } + // Get supported OS update versions + const hupVersionInfo = await sdk.models.os.getSupportedOsUpdateVersions( + device_type, + currentOsVersion, + ); + if (hupVersionInfo.versions.length === 0) { + throw new ExpectedError( + 'There are no available Host OS update targets for this device', + ); + } - return form.ask({ - message: 'Target OS version', - type: 'list', - choices: hupVersionInfo.versions.map(version => ({ - name: - hupVersionInfo.recommended === version - ? `${version} (recommended)` - : version, - value: version, - })), - }); - }) - .then(version => - patterns - .confirm( - options.yes || false, - 'Host OS updates require a device restart when they complete. Are you sure you want to proceed?', - ) - .then(() => sdk.models.device.startOsUpdate(uuid, version)) - .then(() => patterns.awaitDeviceOsUpdate(uuid, version)), - ); + // Get target OS version + let targetOsVersion = options.version; + if (targetOsVersion != null) { + if (!_.includes(hupVersionInfo.versions, targetOsVersion)) { + throw new ExpectedError( + `The provided version ${targetOsVersion} is not in the Host OS update targets for this device`, + ); + } + } else { + targetOsVersion = await form.ask({ + message: 'Target OS version', + type: 'list', + choices: hupVersionInfo.versions.map(version => ({ + name: + hupVersionInfo.recommended === version + ? `${version} (recommended)` + : version, + value: version, + })), }); + } + + // Confirm and start update + await patterns.confirm( + options.yes || false, + 'Host OS updates require a device restart when they complete. Are you sure you want to proceed?', + ); + + await sdk.models.device.startOsUpdate(uuid, targetOsVersion); + await patterns.awaitDeviceOsUpdate(uuid, targetOsVersion); }, }; diff --git a/lib/actions/keys.ts b/lib/actions/keys.ts index 9b18094e..c6fa2b8a 100644 --- a/lib/actions/keys.ts +++ b/lib/actions/keys.ts @@ -48,7 +48,7 @@ Examples: `, permission: 'user', async action(params) { - const key = await getBalenaSdk().models.key.get(params.id); + const key = await getBalenaSdk().models.key.get(parseInt(params.id, 10)); console.log(getVisuals().table.vertical(key, ['id', 'title'])); @@ -86,7 +86,7 @@ Examples: 'Are you sure you want to delete the key?', ); - await getBalenaSdk().models.key.remove(params.id); + await getBalenaSdk().models.key.remove(parseInt(params.id, 10)); }, }; diff --git a/lib/utils/device/progress.ts b/lib/utils/device/progress.ts index 3c60643c..f98418e2 100644 --- a/lib/utils/device/progress.ts +++ b/lib/utils/device/progress.ts @@ -18,7 +18,7 @@ import { Device } from 'balena-sdk'; export const getDeviceOsProgress = (device: Device) => { if (!device.is_online) { - return 0; + return null; } const status = BalenaDeviceStatus.getStatus(device).key; diff --git a/lib/utils/patterns.ts b/lib/utils/patterns.ts index f5730ea2..27c29abd 100644 --- a/lib/utils/patterns.ts +++ b/lib/utils/patterns.ts @@ -293,10 +293,7 @@ export function awaitDeviceOsUpdate(uuid: string, targetOsVersion: string) { balena.models.device.getOsUpdateStatus(uuid), balena.models.device.get(uuid).then(getDeviceOsProgress), ]).then(([osUpdateStatus, osUpdateProgress]) => { - if ( - osUpdateStatus.status === 'update_done' || - osUpdateStatus.status === 'done' - ) { + if (osUpdateStatus.status === 'done') { console.info( `The device ${deviceName} has been updated to v${targetOsVersion} and will restart shortly!`, ); @@ -311,7 +308,10 @@ export function awaitDeviceOsUpdate(uuid: string, targetOsVersion: string) { return; } - progressBar.update({ percentage: osUpdateProgress }); + if (osUpdateProgress !== null) { + // Avoid resetting to 0% at end of process when device goes offline. + progressBar.update({ percentage: osUpdateProgress }); + } return Bluebird.delay(3000).then(poll); }); @@ -451,6 +451,8 @@ export function printErrorMessage(message: string) { * "expected" errors (say, a JSON parsing error in a file provided by the user) * don't warrant reporting through Sentry.io. For such mundane errors, catch * them and call this function. + * + * DEPRECATED: Use `throw new ExpectedError()` instead. */ export function exitWithExpectedError(message: string | Error): never { if (message instanceof Error) { diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index bdd9f5ac..4c82460f 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -2089,9 +2089,9 @@ } }, "balena-hup-action-utils": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/balena-hup-action-utils/-/balena-hup-action-utils-3.0.2.tgz", - "integrity": "sha512-AwTAFQN4mtjsRLhkzspMKGBykWKvjVlZnzhAu6uyPBn8PGocyB3D30bR2jWgapd6uSdPJf38fcspRDbcpx3CHA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/balena-hup-action-utils/-/balena-hup-action-utils-4.0.0.tgz", + "integrity": "sha512-Qwk4h6QRqBiJNONRyoHPKtxgoRucdCK5VTG0A6LSCAo0qQrBRi4Alhdq76ksHQgZ2t/Gewfa7cuVAgiH78YMfw==", "requires": { "balena-semver": "^2.0.0", "lodash": "^4.17.11" @@ -2124,9 +2124,9 @@ } }, "balena-pine": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/balena-pine/-/balena-pine-10.1.1.tgz", - "integrity": "sha512-Py0Id0w1QwwM20/gmZ4hRgTOf0sbCwmjGwkE0G/AO6fqnlFDe5ehaebzXk3AtWmW3K7WGDPUxcNAHohHG8aRJA==", + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/balena-pine/-/balena-pine-10.1.3.tgz", + "integrity": "sha512-dOjftYx7vf9e3C9ENJyYyD8DNtQl6CxXuWgvNImfvK3Rw+zSRBPYfLw6S4MqbCWkcsQGjaS/zun/VJ9UNjJYhQ==", "requires": { "balena-errors": "^4.2.1", "bluebird": "^3.7.2", @@ -2235,16 +2235,16 @@ }, "dependencies": { "qs": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.1.tgz", - "integrity": "sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==" + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", + "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==" } } }, "balena-sdk": { - "version": "12.29.1", - "resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-12.29.1.tgz", - "integrity": "sha512-e6nIuP16AaDpGQnOCDPR7kptCM7/iWHPp6bdmUeBsw9e5IX4zwI0FwFHi4L9EoRxByLKI5oRZYeYGeU++Sd1Nw==", + "version": "12.33.0", + "resolved": "https://registry.npmjs.org/balena-sdk/-/balena-sdk-12.33.0.tgz", + "integrity": "sha512-riqcJeA8SYMf20bgt1lhgw/eWiWXVwivGHgBer54/dxDIo48RIwys98LvkisIO2sTco1AmTL4Q0rePsMGjEYwQ==", "requires": { "@types/bluebird": "^3.5.30", "@types/lodash": "^4.14.149", @@ -2253,8 +2253,8 @@ "abortcontroller-polyfill": "^1.4.0", "balena-auth": "^3.0.1", "balena-device-status": "^3.2.1", - "balena-errors": "^4.2.1", - "balena-hup-action-utils": "~3.0.0", + "balena-errors": "^4.3.0", + "balena-hup-action-utils": "~4.0.0", "balena-pine": "^10.1.1", "balena-register-device": "^6.0.1", "balena-request": "^10.0.8", @@ -2269,9 +2269,23 @@ }, "dependencies": { "@types/node": { - "version": "8.10.59", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.59.tgz", - "integrity": "sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ==" + "version": "8.10.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.60.tgz", + "integrity": "sha512-YjPbypHFuiOV0bTgeF07HpEEqhmHaZqYNSdCKeBJa+yFoQ/7BC+FpJcwmi34xUIIRVFktnUyP1dPU8U0612GOg==" + }, + "balena-errors": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/balena-errors/-/balena-errors-4.3.2.tgz", + "integrity": "sha512-daa+3jHqvoha8zIIIX4jvxdus1LWhH+2Y5IJOzOuIzxmfLEq51EXeszMbcHrbSLlGlJpfaJDG7dfBmFXtabL6Q==", + "requires": { + "tslib": "^1.11.1", + "typed-error": "^3.0.0" + } + }, + "tslib": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" } } }, @@ -2314,9 +2328,9 @@ }, "dependencies": { "@types/node": { - "version": "8.10.59", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.59.tgz", - "integrity": "sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ==" + "version": "8.10.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.60.tgz", + "integrity": "sha512-YjPbypHFuiOV0bTgeF07HpEEqhmHaZqYNSdCKeBJa+yFoQ/7BC+FpJcwmi34xUIIRVFktnUyP1dPU8U0612GOg==" } } }, diff --git a/package.json b/package.json index 12223766..3b81e28a 100644 --- a/package.json +++ b/package.json @@ -173,7 +173,7 @@ "balena-errors": "^4.2.1", "balena-image-manager": "^6.1.2", "balena-preload": "^8.4.0", - "balena-sdk": "^12.29.1", + "balena-sdk": "^12.33.0", "balena-semver": "^2.2.0", "balena-settings-client": "^4.0.4", "balena-sync": "^10.2.0",