Skip to content

Commit ef0587b

Browse files
committed
Add device hook to update the target hostApp when the reported version is newer
Change-type: minor See: https://balena.fibery.io/Work/Project/1886
1 parent 509e9de commit ef0587b

File tree

2 files changed

+54
-21
lines changed

2 files changed

+54
-21
lines changed

src/features/hostapp/hooks/target-hostapp.ts

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,18 @@ hooks.addPureHook('PATCH', 'resin', 'device', {
7171
args.request.values.os_version != null &&
7272
args.request.values.os_variant != null
7373
) {
74+
const parsedOsVersion = semver.parse(args.request.values.os_version);
75+
// balena-semver is able to parse all OS versions that we support,
76+
// so if it can't parse the given version string, then we can be sure
77+
// that there can't be a hostApp release matching it.
78+
if (parsedOsVersion == null) {
79+
return;
80+
}
7481
const ids = await sbvrUtils.getAffectedIds(args);
7582
await setOSReleaseResource(
7683
args.api,
7784
ids,
78-
args.request.values.os_version,
85+
parsedOsVersion,
7986
args.request.values.os_variant,
8087
);
8188
}
@@ -89,9 +96,16 @@ hooks.addPureHook('POST', 'resin', 'device', {
8996
request.values.os_variant != null &&
9097
request.values.is_of__device_type != null
9198
) {
99+
const parsedOsVersion = semver.parse(request.values.os_version);
100+
// balena-semver is able to parse all OS versions that we support,
101+
// so if it can't parse the given version string, then we can be sure
102+
// that there can't be a hostApp release matching it.
103+
if (parsedOsVersion == null) {
104+
return;
105+
}
92106
const hostappRelease = await getOSReleaseResource(
93107
api,
94-
request.values.os_version,
108+
parsedOsVersion,
95109
request.values.os_variant,
96110
request.values.is_of__device_type,
97111
);
@@ -107,7 +121,7 @@ hooks.addPureHook('POST', 'resin', 'device', {
107121
async function setOSReleaseResource(
108122
api: typeof sbvrUtils.api.resin,
109123
deviceIds: number[],
110-
osVersion: string,
124+
parsedOsVersion: SemVer,
111125
osVariant: string,
112126
) {
113127
if (deviceIds.length === 0) {
@@ -116,21 +130,43 @@ async function setOSReleaseResource(
116130
const devices = await api.get({
117131
resource: 'device',
118132
options: {
119-
// if the device already has an os_version, just bail.
133+
$select: ['id', 'is_of__device_type'],
134+
$expand: {
135+
should_be_operated_by__release: {
136+
$select: ['id', 'semver', 'variant'],
137+
$expand: {
138+
release_tag: {
139+
$select: 'value',
140+
$filter: { tag_key: 'version' },
141+
},
142+
},
143+
},
144+
},
120145
$filter: {
121146
id: { $in: deviceIds },
122-
os_version: null,
123147
},
124-
$select: ['id', 'is_of__device_type'],
125148
},
126149
});
150+
const devicesToUpdate = devices.filter((device) => {
151+
const currentTargetOsRelease = device.should_be_operated_by__release[1];
152+
if (currentTargetOsRelease == null) {
153+
return true;
154+
}
155+
const currentTargetOsVersion = getBaseVersionFromReleaseSemverOrTag(
156+
currentTargetOsRelease,
157+
);
158+
return (
159+
currentTargetOsVersion == null ||
160+
semver.gt(parsedOsVersion.raw, currentTargetOsVersion)
161+
);
162+
});
127163

128-
if (devices.length === 0) {
164+
if (devicesToUpdate.length === 0) {
129165
return;
130166
}
131167

132168
const devicesByDeviceTypeId = groupByMap(
133-
devices,
169+
devicesToUpdate,
134170
(d) => d.is_of__device_type.__id,
135171
);
136172
if (devicesByDeviceTypeId.size === 0) {
@@ -151,7 +187,7 @@ async function setOSReleaseResource(
151187

152188
const osRelease = await getOSReleaseResource(
153189
api,
154-
osVersion,
190+
parsedOsVersion,
155191
osVariant,
156192
deviceTypeId,
157193
);
@@ -178,17 +214,10 @@ async function setOSReleaseResource(
178214

179215
async function getOSReleaseResource(
180216
api: typeof sbvrUtils.api.resin,
181-
osVersion: string,
217+
parsedOsVersion: SemVer,
182218
osVariant: string,
183219
deviceTypeId: number,
184220
): Promise<PickDeferred<Release['Read'], 'id' | 'is_final'> | undefined> {
185-
const parsedOsVersion = semver.parse(osVersion);
186-
// balena-semver is able to parse all OS versions that we support,
187-
// so if it can't parse the given version string, then we can be sure
188-
// that there can't be a hostApp release matching it.
189-
if (parsedOsVersion == null) {
190-
return;
191-
}
192221
const releases = await api.get({
193222
resource: 'release',
194223
options: {
@@ -241,7 +270,7 @@ async function getOSReleaseResource(
241270
// b/c balena-semver normalizes invalid semvers like
242271
// 2022.01.0 to 2022.1.0 and that would no longer
243272
// match the tag value.
244-
value: normalizeOsVersion(osVersion),
273+
value: normalizeOsVersion(parsedOsVersion.raw),
245274
tag_key: 'version',
246275
},
247276
},
@@ -306,7 +335,7 @@ async function getOSReleaseResource(
306335
const [release] = releases;
307336
if (releases.filter((r) => r.is_final).length > 1) {
308337
ThisShouldNeverHappenError(
309-
`Found more than one finalized hostApp release matching version ${osVersion} ${osVariant} for device type ${deviceTypeId} and returned ${release.id}.`,
338+
`Found more than one finalized hostApp release matching version ${parsedOsVersion.raw} ${osVariant} for device type ${deviceTypeId} and returned ${release.id}.`,
310339
);
311340
}
312341

test/15_target-hostapp.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -528,8 +528,10 @@ export default () => {
528528
],
529529
] as const
530530
).forEach(([osTypeTitlePart, initialOsVersion, getHostAppReleaseId]) => {
531+
let invalidatedReleaseDevice: fakeDevice.Device;
532+
531533
it(`should provision with an invalidated ${osTypeTitlePart} hostapp release`, async () => {
532-
const invalidatedReleaseDevice = await fakeDevice.provisionDevice(
534+
invalidatedReleaseDevice = await fakeDevice.provisionDevice(
533535
admin,
534536
applicationId,
535537
);
@@ -552,7 +554,9 @@ export default () => {
552554
},
553555
},
554556
);
557+
});
555558

559+
it(`...should be able to update from an invalidated ${osTypeTitlePart} to a newer release`, async () => {
556560
const supervisorVersion = 'v12.3.5';
557561
const newOsVersion = 'balenaOS 2.88.5+rev1';
558562
await invalidatedReleaseDevice.patchStateV2({
@@ -575,7 +579,7 @@ export default () => {
575579
// Atm the should_be_operated_by__release is only updated when the device provisions.
576580
// We might change this during the scheduled or tri-app HUP.
577581
should_be_operated_by__release: {
578-
__id: initialInvalidatedReleaseId,
582+
__id: unifiedSemverRevHostAppReleaseId,
579583
},
580584
},
581585
);

0 commit comments

Comments
 (0)