Skip to content

Commit

Permalink
Merge pull request #1398 from balena-io/draft-hup
Browse files Browse the repository at this point in the history
Enable OS Updates to pre-release versions of higher base semver
  • Loading branch information
thgreasi authored Jan 23, 2024
2 parents 7c9d78e + 3fd5f7d commit 5318a7b
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 139 deletions.
74 changes: 57 additions & 17 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,15 +359,15 @@ const sdk = fromSharedOptions();
* [.get(handleOrId, [options])](#balena.models.organization.get) ⇒ <code>Promise</code>
* [.remove(handleOrId)](#balena.models.organization.remove) ⇒ <code>Promise</code>
* [.os](#balena.models.os) : <code>object</code>
* [.getAvailableOsVersions(deviceTypes)](#balena.models.os.getAvailableOsVersions) ⇒ <code>Promise</code>
* [.getAvailableOsVersions(deviceTypes, [options])](#balena.models.os.getAvailableOsVersions) ⇒ <code>Promise</code>
* [.getAllOsVersions(deviceTypes, [options])](#balena.models.os.getAllOsVersions) ⇒ <code>Promise</code>
* [.getDownloadSize(deviceType, [version])](#balena.models.os.getDownloadSize) ⇒ <code>Promise</code>
* [.getMaxSatisfyingVersion(deviceType, versionOrRange, [osType])](#balena.models.os.getMaxSatisfyingVersion) ⇒ <code>Promise</code>
* [.getLastModified(deviceType, [version])](#balena.models.os.getLastModified) ⇒ <code>Promise</code>
* [.download(options)](#balena.models.os.download) ⇒ <code>Promise</code>
* [.getConfig(slugOrUuidOrId, options)](#balena.models.os.getConfig) ⇒ <code>Promise</code>
* [.isSupportedOsUpdate(deviceType, currentVersion, targetVersion)](#balena.models.os.isSupportedOsUpdate) ⇒ <code>Promise</code>
* [.getSupportedOsUpdateVersions(deviceType, currentVersion)](#balena.models.os.getSupportedOsUpdateVersions) ⇒ <code>Promise</code>
* [.getSupportedOsUpdateVersions(deviceType, currentVersion, [options])](#balena.models.os.getSupportedOsUpdateVersions) ⇒ <code>Promise</code>
* [.isArchitectureCompatibleWith(osArchitecture, applicationArchitecture)](#balena.models.os.isArchitectureCompatibleWith) ⇒ <code>Boolean</code>
* [.getSupervisorImageForDeviceType(deviceTypeId, version)](#balena.models.os.getSupervisorImageForDeviceType) ⇒ <code>Promise.&lt;String&gt;</code>
* [.config](#balena.models.config) : <code>object</code>
Expand Down Expand Up @@ -490,8 +490,7 @@ rejects with an error.
<a name="balena.utils"></a>

### balena.utils : <code>Object</code>
The utils instance used internally. This should not be necessary
in normal usage, but can be useful to handle some specific cases.
The utils instance offers some convenient features for clients.

**Kind**: static property of [<code>balena</code>](#balena)
**Summary**: Balena utils instance
Expand All @@ -503,6 +502,14 @@ balena.utils.mergePineOptions(
{ $expand: { device: { $select: ['name'] } } },
);
```
**Example**
```js
// Creating a new WebResourceFile in case 'File' API is not available.
new balena.utils.BalenaWebResourceFile(
[fs.readFileSync('./file.tgz')],
'file.tgz'
);
```
<a name="balena.request"></a>

### balena.request : <code>Object</code>
Expand Down Expand Up @@ -759,15 +766,15 @@ balena.models.device.get(123).catch(function (error) {
* [.get(handleOrId, [options])](#balena.models.organization.get) ⇒ <code>Promise</code>
* [.remove(handleOrId)](#balena.models.organization.remove) ⇒ <code>Promise</code>
* [.os](#balena.models.os) : <code>object</code>
* [.getAvailableOsVersions(deviceTypes)](#balena.models.os.getAvailableOsVersions) ⇒ <code>Promise</code>
* [.getAvailableOsVersions(deviceTypes, [options])](#balena.models.os.getAvailableOsVersions) ⇒ <code>Promise</code>
* [.getAllOsVersions(deviceTypes, [options])](#balena.models.os.getAllOsVersions) ⇒ <code>Promise</code>
* [.getDownloadSize(deviceType, [version])](#balena.models.os.getDownloadSize) ⇒ <code>Promise</code>
* [.getMaxSatisfyingVersion(deviceType, versionOrRange, [osType])](#balena.models.os.getMaxSatisfyingVersion) ⇒ <code>Promise</code>
* [.getLastModified(deviceType, [version])](#balena.models.os.getLastModified) ⇒ <code>Promise</code>
* [.download(options)](#balena.models.os.download) ⇒ <code>Promise</code>
* [.getConfig(slugOrUuidOrId, options)](#balena.models.os.getConfig) ⇒ <code>Promise</code>
* [.isSupportedOsUpdate(deviceType, currentVersion, targetVersion)](#balena.models.os.isSupportedOsUpdate) ⇒ <code>Promise</code>
* [.getSupportedOsUpdateVersions(deviceType, currentVersion)](#balena.models.os.getSupportedOsUpdateVersions) ⇒ <code>Promise</code>
* [.getSupportedOsUpdateVersions(deviceType, currentVersion, [options])](#balena.models.os.getSupportedOsUpdateVersions) ⇒ <code>Promise</code>
* [.isArchitectureCompatibleWith(osArchitecture, applicationArchitecture)](#balena.models.os.isArchitectureCompatibleWith) ⇒ <code>Boolean</code>
* [.getSupervisorImageForDeviceType(deviceTypeId, version)](#balena.models.os.getSupervisorImageForDeviceType) ⇒ <code>Promise.&lt;String&gt;</code>
* [.config](#balena.models.config) : <code>object</code>
Expand Down Expand Up @@ -5155,6 +5162,33 @@ balena.models.organization.create({ name:'MyOrganization' }).then(function(organ
console.log(organization);
});
```
**Example**
```js
balena.models.organization.create({
name:'MyOrganization',
logo_image: new balena.utils.BalenaWebResourceFile(
[fs.readFileSync('./img.jpeg')],
'img.jpeg'
);
})
.then(function(organization) {
console.log(organization);
});
```
**Example**
```js
balena.models.organization.create({
name:'MyOrganization',
// Only in case File API is avaialable (most browsers and Node 20+)
logo_image: new File(
imageContent,
'img.jpeg'
);
})
.then(function(organization) {
console.log(organization);
});
```
<a name="balena.models.organization.getAll"></a>

##### organization.getAll([options]) ⇒ <code>Promise</code>
Expand Down Expand Up @@ -5219,30 +5253,32 @@ balena.models.organization.remove(123);
**Kind**: static namespace of [<code>models</code>](#balena.models)

* [.os](#balena.models.os) : <code>object</code>
* [.getAvailableOsVersions(deviceTypes)](#balena.models.os.getAvailableOsVersions) ⇒ <code>Promise</code>
* [.getAvailableOsVersions(deviceTypes, [options])](#balena.models.os.getAvailableOsVersions) ⇒ <code>Promise</code>
* [.getAllOsVersions(deviceTypes, [options])](#balena.models.os.getAllOsVersions) ⇒ <code>Promise</code>
* [.getDownloadSize(deviceType, [version])](#balena.models.os.getDownloadSize) ⇒ <code>Promise</code>
* [.getMaxSatisfyingVersion(deviceType, versionOrRange, [osType])](#balena.models.os.getMaxSatisfyingVersion) ⇒ <code>Promise</code>
* [.getLastModified(deviceType, [version])](#balena.models.os.getLastModified) ⇒ <code>Promise</code>
* [.download(options)](#balena.models.os.download) ⇒ <code>Promise</code>
* [.getConfig(slugOrUuidOrId, options)](#balena.models.os.getConfig) ⇒ <code>Promise</code>
* [.isSupportedOsUpdate(deviceType, currentVersion, targetVersion)](#balena.models.os.isSupportedOsUpdate) ⇒ <code>Promise</code>
* [.getSupportedOsUpdateVersions(deviceType, currentVersion)](#balena.models.os.getSupportedOsUpdateVersions) ⇒ <code>Promise</code>
* [.getSupportedOsUpdateVersions(deviceType, currentVersion, [options])](#balena.models.os.getSupportedOsUpdateVersions) ⇒ <code>Promise</code>
* [.isArchitectureCompatibleWith(osArchitecture, applicationArchitecture)](#balena.models.os.isArchitectureCompatibleWith) ⇒ <code>Boolean</code>
* [.getSupervisorImageForDeviceType(deviceTypeId, version)](#balena.models.os.getSupervisorImageForDeviceType) ⇒ <code>Promise.&lt;String&gt;</code>

<a name="balena.models.os.getAvailableOsVersions"></a>

##### os.getAvailableOsVersions(deviceTypes) ⇒ <code>Promise</code>
##### os.getAvailableOsVersions(deviceTypes, [options]) ⇒ <code>Promise</code>
**Kind**: static method of [<code>os</code>](#balena.models.os)
**Summary**: Get the supported OS versions for the provided device type(s)
**Access**: public
**Fulfil**: <code>Object[]\|Object</code> - An array of OsVersion objects when a single device type slug is provided,
or a dictionary of OsVersion objects by device type slug when an array of device type slugs is provided.

| Param | Type | Description |
| --- | --- | --- |
| deviceTypes | <code>String</code> \| <code>Array.&lt;String&gt;</code> | device type slug or array of slugs |
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| deviceTypes | <code>String</code> \| <code>Array.&lt;String&gt;</code> | | device type slug or array of slugs |
| [options] | <code>Object</code> | | Extra options to filter the OS releases by |
| [options.includeDraft] | <code>Boolean</code> | <code>false</code> | Whether pre-releases should be included in the results |

**Example**
```js
Expand Down Expand Up @@ -5432,21 +5468,25 @@ balena.models.os.isSupportedOsUpgrade('raspberry-pi', '2.9.6+rev2.prod', '2.29.2
```
<a name="balena.models.os.getSupportedOsUpdateVersions"></a>

##### os.getSupportedOsUpdateVersions(deviceType, currentVersion) ⇒ <code>Promise</code>
##### os.getSupportedOsUpdateVersions(deviceType, currentVersion, [options]) ⇒ <code>Promise</code>
**Kind**: static method of [<code>os</code>](#balena.models.os)
**Summary**: Returns the supported OS update targets for the provided device type
**Access**: public
**Fulfil**: <code>Object[]\|Object</code> - An array of OsVersion objects when a single device type slug is provided,
or a dictionary of OsVersion objects by device type slug when an array of device type slugs is provided.
**Fulfil**: <code>Object</code> - the versions information, of the following structure:
* versions - an array of strings,
containing exact version numbers that OS update is supported
* recommended - the recommended version, i.e. the most recent version
that is _not_ pre-release, can be `null`
* current - the provided current version after normalization

| Param | Type | Description |
| --- | --- | --- |
| deviceType | <code>String</code> | device type slug |
| currentVersion | <code>String</code> | semver-compatible version for the starting OS version |
| Param | Type | Default | Description |
| --- | --- | --- | --- |
| deviceType | <code>String</code> | | device type slug |
| currentVersion | <code>String</code> | | semver-compatible version for the starting OS version |
| [options] | <code>Object</code> | | Extra options to filter the OS releases by |
| [options.includeDraft] | <code>Boolean</code> | <code>false</code> | Whether pre-releases should be included in the results |

**Example**
```js
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
"abortcontroller-polyfill": "^1.7.1",
"balena-auth": "^5.1.0",
"balena-errors": "^4.9.0",
"balena-hup-action-utils": "~5.0.0",
"balena-hup-action-utils": "~6.1.0",
"balena-register-device": "^9.0.1",
"balena-request": "^13.2.0",
"balena-semver": "^2.3.0",
Expand Down
11 changes: 8 additions & 3 deletions src/models/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@ const getDeviceModel = function (
);
}

const isDraft =
(bSemver.parse(targetOsVersion)?.prerelease.length ?? 0) > 0;

const getDeviceType = memoizee(
async (deviceTypeId: number) =>
await sdkInstance.models.deviceType.get(deviceTypeId, {
Expand All @@ -358,8 +361,10 @@ const getDeviceModel = function (
{ primitive: true, promise: true },
);
const getAvailableOsVersions = memoizee(
async (slug: string) =>
await sdkInstance.models.os.getAvailableOsVersions(slug),
async (slug: string, includeDraft: boolean) =>
await sdkInstance.models.os.getAvailableOsVersions(slug, {
includeDraft,
}),
{ primitive: true, promise: true },
);

Expand Down Expand Up @@ -392,7 +397,7 @@ const getDeviceModel = function (
targetOsVersion,
);

const osVersions = await getAvailableOsVersions(dt.slug);
const osVersions = await getAvailableOsVersions(dt.slug, isDraft);

if (
!osVersions.some(
Expand Down
31 changes: 22 additions & 9 deletions src/models/os.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,27 +362,32 @@ const getOsModel = function (
};

const _memoizedGetAllOsVersions = authDependentMemoizer(
async (deviceTypes: string[], listedByDefault: boolean | null) => {
async (
deviceTypes: string[],
filterOptions: 'supported' | 'include_draft' | 'all',
) => {
return await _getAllOsVersions(
deviceTypes,
listedByDefault
? {
filterOptions === 'all'
? undefined
: {
$filter: {
is_final: true,
...(filterOptions === 'supported' && { is_final: true }),
is_invalidated: false,
status: 'success',
},
}
: undefined,
},
);
},
);

async function getAvailableOsVersions(
deviceType: string,
options?: { includeDraft?: boolean },
): Promise<OsVersion[]>;
async function getAvailableOsVersions(
deviceTypes: string[],
options?: { includeDraft?: boolean },
): Promise<Dictionary<OsVersion[]>>;
/**
* @summary Get the supported OS versions for the provided device type(s)
Expand All @@ -392,6 +397,8 @@ const getOsModel = function (
* @memberof balena.models.os
*
* @param {String|String[]} deviceTypes - device type slug or array of slugs
* @param {Object} [options] - Extra options to filter the OS releases by
* @param {Boolean} [options.includeDraft=false] - Whether pre-releases should be included in the results
* @fulfil {Object[]|Object} - An array of OsVersion objects when a single device type slug is provided,
* or a dictionary of OsVersion objects by device type slug when an array of device type slugs is provided.
* @returns {Promise}
Expand All @@ -404,13 +411,14 @@ const getOsModel = function (
*/
async function getAvailableOsVersions(
deviceTypes: string[] | string,
options?: { includeDraft?: boolean },
): Promise<TypeOrDictionary<OsVersion[]>> {
const singleDeviceTypeArg =
typeof deviceTypes === 'string' ? deviceTypes : false;
deviceTypes = Array.isArray(deviceTypes) ? deviceTypes : [deviceTypes];
const versionsByDt = await _memoizedGetAllOsVersions(
deviceTypes.slice().sort(),
true,
options?.includeDraft === true ? 'include_draft' : 'supported',
);
return singleDeviceTypeArg
? versionsByDt[singleDeviceTypeArg] ?? []
Expand Down Expand Up @@ -460,7 +468,7 @@ const getOsModel = function (
deviceTypes = Array.isArray(deviceTypes) ? deviceTypes : [deviceTypes];
const versionsByDt = (
options == null
? await _memoizedGetAllOsVersions(deviceTypes.slice().sort(), null)
? await _memoizedGetAllOsVersions(deviceTypes.slice().sort(), 'all')
: await _getAllOsVersions(deviceTypes, options)
) as Dictionary<Array<ExtendedPineTypedResult<Release, OsVersion, TP>>>;
return singleDeviceTypeArg
Expand Down Expand Up @@ -869,6 +877,10 @@ const getOsModel = function (
*
* @param {String} deviceType - device type slug
* @param {String} currentVersion - semver-compatible version for the starting OS version
* @param {Object} [options] - Extra options to filter the OS releases by
* @param {Boolean} [options.includeDraft=false] - Whether pre-releases should be included in the results
* @fulfil {Object[]|Object} - An array of OsVersion objects when a single device type slug is provided,
* or a dictionary of OsVersion objects by device type slug when an array of device type slugs is provided.
* @fulfil {Object} - the versions information, of the following structure:
* * versions - an array of strings,
* containing exact version numbers that OS update is supported
Expand All @@ -885,9 +897,10 @@ const getOsModel = function (
const getSupportedOsUpdateVersions = async (
deviceType: string,
currentVersion: string,
options?: { includeDraft?: boolean },
): Promise<OsUpdateVersions> => {
deviceType = await _getNormalizedDeviceTypeSlug(deviceType);
const allVersions = (await getAvailableOsVersions(deviceType))
const allVersions = (await getAvailableOsVersions(deviceType, options))
.filter((v) => v.osType === OsTypes.DEFAULT)
.map((v) => v.raw_version);
// use bSemver.compare to find the current version in the OS list
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/models/device-type.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ describe('Device Type model', function () {
[
RADXA_ZERO_DEVICE_TYPE_SLUG,
[
"Use the <a href=https://wiki.radxa.com/Zero/dev/maskrom#Enable_maskrom>maskrom mode</a> instructions provided by the vendor and make sure the board's USB2 port is used for provisioning.",
'Use the <a href="https://wiki.radxa.com/Zero/dev/maskrom#Enable_maskrom">maskrom mode</a> instructions provided by the vendor and make sure the board\'s USB2 port is used for provisioning.',
'Install on your PC the <a href=https://wiki.radxa.com/Zero/dev/maskrom#Install_required_tools>tools</a> required for flashing.',
'Clear eMMC and set it in UMS mode. Make sure to use <a href=https://dl.radxa.com/zero/images/loader/radxa-zero-erase-emmc.bin>this loader</a> when following the <a href=https://wiki.radxa.com/Zero/dev/maskrom#Side_loading_binaries>sideloading instructions</a>.',
'Write the OS to the internal eMMC storage device. We recommend using <a href=http://www.etcher.io/>Etcher</a>.',
'Write the OS to the internal eMMC storage device. We recommend using <a href="http://www.etcher.io">Etcher</a>.',
'Once the OS has been written to the eMMC you need to repower your board.',
],
],
Expand Down
Loading

0 comments on commit 5318a7b

Please sign in to comment.