-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Split metrics from device
resource
#1371
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,9 +23,11 @@ export { | |
serviceInstallFromImage, | ||
} from './state-get-utils'; | ||
export { | ||
metricsPatchFields, | ||
validDeviceMetricsRecordPatchFields, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NIT: Since this field is also exported from index.ts w/ |
||
v2ValidPatchFields, | ||
v2ValidDevicePatchFields, | ||
v3ValidPatchFields, | ||
v3ValidDevicePatchFields, | ||
} from './state-patch-utils'; | ||
|
||
const gracefulGet = resolveOrDenyDevicesWithStatus(304); | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -10,11 +10,12 @@ import { getIP } from '../../../lib/utils'; | |||||||||||||||||||||||||||||
import type { ImageInstall, PickDeferred } from '../../../balena-model'; | ||||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||||
shouldUpdateMetrics, | ||||||||||||||||||||||||||||||
metricsPatchFields, | ||||||||||||||||||||||||||||||
v2ValidPatchFields, | ||||||||||||||||||||||||||||||
upsertImageInstall, | ||||||||||||||||||||||||||||||
deleteOldImageInstalls, | ||||||||||||||||||||||||||||||
truncateShortTextFields, | ||||||||||||||||||||||||||||||
StatePatchDeviceMetricsRecordBody, | ||||||||||||||||||||||||||||||
validDeviceMetricsRecordPatchFields, | ||||||||||||||||||||||||||||||
v2ValidDevicePatchFields, | ||||||||||||||||||||||||||||||
} from '../state-patch-utils'; | ||||||||||||||||||||||||||||||
import type { ResolveDeviceInfoCustomObject } from '../middleware'; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
@@ -26,7 +27,7 @@ type LocalBody = NonNullable<StatePatchV2Body['local']>; | |||||||||||||||||||||||||||||
* These typings should be used as a guide to what should be sent, but cannot be trusted as what actually *is* sent. | ||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||
export type StatePatchV2Body = { | ||||||||||||||||||||||||||||||
local?: { | ||||||||||||||||||||||||||||||
local?: StatePatchDeviceMetricsRecordBody & { | ||||||||||||||||||||||||||||||
should_be_running__release?: number; | ||||||||||||||||||||||||||||||
name?: string; | ||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||
|
@@ -46,15 +47,6 @@ export type StatePatchV2Body = { | |||||||||||||||||||||||||||||
download_progress?: number | null; | ||||||||||||||||||||||||||||||
api_port?: number; | ||||||||||||||||||||||||||||||
api_secret?: string; | ||||||||||||||||||||||||||||||
memory_usage?: number; | ||||||||||||||||||||||||||||||
memory_total?: number; | ||||||||||||||||||||||||||||||
storage_block_device?: string; | ||||||||||||||||||||||||||||||
storage_usage?: number; | ||||||||||||||||||||||||||||||
storage_total?: number; | ||||||||||||||||||||||||||||||
cpu_temp?: number; | ||||||||||||||||||||||||||||||
cpu_usage?: number; | ||||||||||||||||||||||||||||||
cpu_id?: string; | ||||||||||||||||||||||||||||||
is_undervolted?: boolean; | ||||||||||||||||||||||||||||||
is_on__commit?: string | null; | ||||||||||||||||||||||||||||||
apps?: Array<{ | ||||||||||||||||||||||||||||||
services?: { | ||||||||||||||||||||||||||||||
|
@@ -139,20 +131,13 @@ export const statePatchV2: RequestHandler = async (req, res) => { | |||||||||||||||||||||||||||||
const { apps } = local; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
let deviceBody: | ||||||||||||||||||||||||||||||
| Pick<LocalBody, (typeof v2ValidPatchFields)[number]> & { | ||||||||||||||||||||||||||||||
| Pick<LocalBody, (typeof v2ValidDevicePatchFields)[number]> & { | ||||||||||||||||||||||||||||||
is_running__release?: number | null; | ||||||||||||||||||||||||||||||
} = _.pick(local, v2ValidPatchFields); | ||||||||||||||||||||||||||||||
let metricsBody: Pick<LocalBody, (typeof metricsPatchFields)[number]> = | ||||||||||||||||||||||||||||||
_.pick(local, metricsPatchFields); | ||||||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||||||
Object.keys(metricsBody).length > 0 && | ||||||||||||||||||||||||||||||
(await shouldUpdateMetrics(uuid)) | ||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||
// If we should force a metrics update then merge the two together and clear `metricsBody` so | ||||||||||||||||||||||||||||||
// that we don't try to merge it again later | ||||||||||||||||||||||||||||||
deviceBody = { ...deviceBody, ...metricsBody }; | ||||||||||||||||||||||||||||||
metricsBody = {}; | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
} = _.pick(local, v2ValidDevicePatchFields); | ||||||||||||||||||||||||||||||
const metricsBody: Pick< | ||||||||||||||||||||||||||||||
LocalBody, | ||||||||||||||||||||||||||||||
(typeof validDeviceMetricsRecordPatchFields)[number] | ||||||||||||||||||||||||||||||
> = _.pick(local, validDeviceMetricsRecordPatchFields); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
if (local.name != null) { | ||||||||||||||||||||||||||||||
deviceBody.device_name = local.name; | ||||||||||||||||||||||||||||||
|
@@ -191,15 +176,13 @@ export const statePatchV2: RequestHandler = async (req, res) => { | |||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
updateFns.push(async (resinApiTx) => { | ||||||||||||||||||||||||||||||
if (Object.keys(deviceBody).length > 0) { | ||||||||||||||||||||||||||||||
if (Object.keys(deviceBody).length > 0) { | ||||||||||||||||||||||||||||||
updateFns.push(async (resinApiTx) => { | ||||||||||||||||||||||||||||||
// truncate for resilient legacy compatible device state patch so that supervisors don't fail | ||||||||||||||||||||||||||||||
// to update b/c of length violation of 255 (SBVR SHORT TEXT type) for ip and mac address. | ||||||||||||||||||||||||||||||
// sbvr-types does not export SHORT TEXT VARCHAR length 255 to import. | ||||||||||||||||||||||||||||||
deviceBody = truncateShortTextFields(deviceBody); | ||||||||||||||||||||||||||||||
// If we're updating anyway then ensure the metrics data is included | ||||||||||||||||||||||||||||||
deviceBody = { ...deviceBody, ...metricsBody }; | ||||||||||||||||||||||||||||||
await resinApiTx.patch({ | ||||||||||||||||||||||||||||||
resource: 'device', | ||||||||||||||||||||||||||||||
id: deviceId, | ||||||||||||||||||||||||||||||
|
@@ -208,6 +191,32 @@ export const statePatchV2: RequestHandler = async (req, res) => { | |||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||
body: deviceBody, | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||||||
Object.keys(metricsBody).length > 0 && | ||||||||||||||||||||||||||||||
(await shouldUpdateMetrics(uuid)) | ||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||
updateFns.push(async (resinApiTx) => { | ||||||||||||||||||||||||||||||
const latestDeviceMetricsRecord = await resinApiTx.get({ | ||||||||||||||||||||||||||||||
resource: 'device_metrics_record', | ||||||||||||||||||||||||||||||
id: { is_reported_by__device: deviceId }, | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
Comment on lines
+202
to
+205
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
if (latestDeviceMetricsRecord == null) { | ||||||||||||||||||||||||||||||
await resinApiTx.post({ | ||||||||||||||||||||||||||||||
resource: 'device_metrics_record', | ||||||||||||||||||||||||||||||
id: { is_reported_by__device: deviceId }, | ||||||||||||||||||||||||||||||
body: { | ||||||||||||||||||||||||||||||
...metricsBody, | ||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
Comment on lines
+207
to
+213
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does that work? Shouldn't it be v ?
Suggested change
|
||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||
await resinApiTx.patch({ | ||||||||||||||||||||||||||||||
resource: 'device_metrics_record', | ||||||||||||||||||||||||||||||
id: { is_reported_by__device: deviceId }, | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||
body: metricsBody, | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -10,18 +10,21 @@ import { getIP } from '../../../lib/utils'; | |||||||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||||||||
Application, | ||||||||||||||||||||||||||||||||||
Device, | ||||||||||||||||||||||||||||||||||
DeviceMetricsRecord, | ||||||||||||||||||||||||||||||||||
Image, | ||||||||||||||||||||||||||||||||||
ImageInstall, | ||||||||||||||||||||||||||||||||||
PickDeferred, | ||||||||||||||||||||||||||||||||||
Release, | ||||||||||||||||||||||||||||||||||
} from '../../../balena-model'; | ||||||||||||||||||||||||||||||||||
import type { Filter } from 'pinejs-client-core'; | ||||||||||||||||||||||||||||||||||
import { metricsPatchFields, v3ValidPatchFields } from '..'; | ||||||||||||||||||||||||||||||||||
import { v3ValidDevicePatchFields } from '..'; | ||||||||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||||||||
deleteOldImageInstalls, | ||||||||||||||||||||||||||||||||||
upsertImageInstall, | ||||||||||||||||||||||||||||||||||
shouldUpdateMetrics, | ||||||||||||||||||||||||||||||||||
truncateShortTextFields, | ||||||||||||||||||||||||||||||||||
StatePatchDeviceMetricsRecordBody, | ||||||||||||||||||||||||||||||||||
validDeviceMetricsRecordPatchFields, | ||||||||||||||||||||||||||||||||||
} from '../state-patch-utils'; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const { BadRequestError, UnauthorizedError, InternalRequestError } = errors; | ||||||||||||||||||||||||||||||||||
|
@@ -31,7 +34,7 @@ const { api } = sbvrUtils; | |||||||||||||||||||||||||||||||||
* These typings should be used as a guide to what should be sent, but cannot be trusted as what actually *is* sent. | ||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||
export type StatePatchV3Body = { | ||||||||||||||||||||||||||||||||||
[uuid: string]: { | ||||||||||||||||||||||||||||||||||
[uuid: string]: StatePatchDeviceMetricsRecordBody & { | ||||||||||||||||||||||||||||||||||
status?: string; | ||||||||||||||||||||||||||||||||||
os_version?: string; | ||||||||||||||||||||||||||||||||||
os_variant?: string; | ||||||||||||||||||||||||||||||||||
|
@@ -42,15 +45,7 @@ export type StatePatchV3Body = { | |||||||||||||||||||||||||||||||||
mac_address?: string; | ||||||||||||||||||||||||||||||||||
api_port?: number; | ||||||||||||||||||||||||||||||||||
api_secret?: string; | ||||||||||||||||||||||||||||||||||
memory_usage?: number; | ||||||||||||||||||||||||||||||||||
memory_total?: number; | ||||||||||||||||||||||||||||||||||
storage_block_device?: string; | ||||||||||||||||||||||||||||||||||
storage_usage?: number; | ||||||||||||||||||||||||||||||||||
storage_total?: number; | ||||||||||||||||||||||||||||||||||
cpu_temp?: number; | ||||||||||||||||||||||||||||||||||
cpu_usage?: number; | ||||||||||||||||||||||||||||||||||
cpu_id?: string; | ||||||||||||||||||||||||||||||||||
is_undervolted?: boolean; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||
* Used for setting dependent devices as online | ||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||
|
@@ -103,11 +98,13 @@ const fetchData = async ( | |||||||||||||||||||||||||||||||||
belongs_to__application: { | ||||||||||||||||||||||||||||||||||
$select: 'uuid', | ||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||
reports__device_metrics_record: {}, | ||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||
})) as Array< | ||||||||||||||||||||||||||||||||||
Pick<Device, 'id' | 'uuid'> & { | ||||||||||||||||||||||||||||||||||
belongs_to__application: Array<Pick<Application, 'uuid'>>; | ||||||||||||||||||||||||||||||||||
reports__device_metrics_record: DeviceMetricsRecord[]; | ||||||||||||||||||||||||||||||||||
Comment on lines
+101
to
+107
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't really care to fetch any of the rest columns.
Suggested change
|
||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
>; | ||||||||||||||||||||||||||||||||||
if (devices.length !== uuids.length) { | ||||||||||||||||||||||||||||||||||
|
@@ -269,23 +266,14 @@ export const statePatchV3: RequestHandler = async (req, res) => { | |||||||||||||||||||||||||||||||||
let deviceBody: | ||||||||||||||||||||||||||||||||||
| Pick< | ||||||||||||||||||||||||||||||||||
StatePatchV3Body[string], | ||||||||||||||||||||||||||||||||||
(typeof v3ValidPatchFields)[number] | ||||||||||||||||||||||||||||||||||
(typeof v3ValidDevicePatchFields)[number] | ||||||||||||||||||||||||||||||||||
> & { | ||||||||||||||||||||||||||||||||||
is_running__release?: number | null; | ||||||||||||||||||||||||||||||||||
} = _.pick(state, v3ValidPatchFields); | ||||||||||||||||||||||||||||||||||
} = _.pick(state, v3ValidDevicePatchFields); | ||||||||||||||||||||||||||||||||||
let metricsBody: Pick< | ||||||||||||||||||||||||||||||||||
StatePatchV3Body[string], | ||||||||||||||||||||||||||||||||||
(typeof metricsPatchFields)[number] | ||||||||||||||||||||||||||||||||||
> = _.pick(state, metricsPatchFields); | ||||||||||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||||||||||
Object.keys(metricsBody).length > 0 && | ||||||||||||||||||||||||||||||||||
(await shouldUpdateMetrics(uuid)) | ||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||
// If we should force a metrics update then merge the two together and clear `metricsBody` so | ||||||||||||||||||||||||||||||||||
// that we don't try to merge it again later | ||||||||||||||||||||||||||||||||||
deviceBody = { ...deviceBody, ...metricsBody }; | ||||||||||||||||||||||||||||||||||
metricsBody = {}; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
(typeof validDeviceMetricsRecordPatchFields)[number] | ||||||||||||||||||||||||||||||||||
> = _.pick(state, validDeviceMetricsRecordPatchFields); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
if (deviceBody.cpu_id != null) { | ||||||||||||||||||||||||||||||||||
if (/[^\x20-\x7E]/.test(deviceBody.cpu_id)) { | ||||||||||||||||||||||||||||||||||
|
@@ -297,12 +285,25 @@ export const statePatchV3: RequestHandler = async (req, res) => { | |||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
if (apps != null || Object.keys(deviceBody).length > 0) { | ||||||||||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||||||||||
Object.keys(metricsBody).length > 0 && | ||||||||||||||||||||||||||||||||||
!(await shouldUpdateMetrics(uuid)) | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a special case I think where we always want to recognize changes to the |
||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||
// If we don't want to update the metrics then clear them | ||||||||||||||||||||||||||||||||||
metricsBody = {}; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||||||||||
apps != null || | ||||||||||||||||||||||||||||||||||
Object.keys(deviceBody).length > 0 || | ||||||||||||||||||||||||||||||||||
Object.keys(metricsBody).length > 0 | ||||||||||||||||||||||||||||||||||
Page- marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||
// We lazily fetch the necessary data only if we absolutely must to avoid unnecessary work if it turns out we don't need it | ||||||||||||||||||||||||||||||||||
data ??= await fetchData(req, custom, uuids, appReleasesCriteria); | ||||||||||||||||||||||||||||||||||
const { images, releasesByAppUuid } = data; | ||||||||||||||||||||||||||||||||||
const device = data.devicesByUuid[uuid]; | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const [latestDeviceMetricsRecord] = | ||||||||||||||||||||||||||||||||||
device.reports__device_metrics_record; | ||||||||||||||||||||||||||||||||||
if (apps != null) { | ||||||||||||||||||||||||||||||||||
const userAppUuid = device.belongs_to__application[0].uuid; | ||||||||||||||||||||||||||||||||||
if (releasesByAppUuid[userAppUuid] != null) { | ||||||||||||||||||||||||||||||||||
|
@@ -321,7 +322,6 @@ export const statePatchV3: RequestHandler = async (req, res) => { | |||||||||||||||||||||||||||||||||
// sbvr-types does not export SHORT TEXT VARCHAR length 255 to import. | ||||||||||||||||||||||||||||||||||
deviceBody = truncateShortTextFields(deviceBody); | ||||||||||||||||||||||||||||||||||
// If we're updating anyway then ensure the metrics data is included | ||||||||||||||||||||||||||||||||||
deviceBody = { ...deviceBody, ...metricsBody }; | ||||||||||||||||||||||||||||||||||
updateFns.push(async (resinApiTx) => { | ||||||||||||||||||||||||||||||||||
await resinApiTx.patch({ | ||||||||||||||||||||||||||||||||||
resource: 'device', | ||||||||||||||||||||||||||||||||||
|
@@ -334,6 +334,26 @@ export const statePatchV3: RequestHandler = async (req, res) => { | |||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
if (Object.keys(metricsBody).length > 0) { | ||||||||||||||||||||||||||||||||||
updateFns.push(async (resinApiTx) => { | ||||||||||||||||||||||||||||||||||
if (latestDeviceMetricsRecord == null) { | ||||||||||||||||||||||||||||||||||
await resinApiTx.post({ | ||||||||||||||||||||||||||||||||||
resource: 'device_metrics_record', | ||||||||||||||||||||||||||||||||||
body: { | ||||||||||||||||||||||||||||||||||
...metricsBody, | ||||||||||||||||||||||||||||||||||
...{ is_reported_by__device: device.id }, | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I less object to GC :) |
||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||
await resinApiTx.patch({ | ||||||||||||||||||||||||||||||||||
resource: 'device_metrics_record', | ||||||||||||||||||||||||||||||||||
id: { is_reported_by__device: device.id }, | ||||||||||||||||||||||||||||||||||
body: metricsBody, | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about adding v both here and in the v2 patch?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, this avoids an unnecessary postgres write in the case there are no changes |
||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
if (apps != null) { | ||||||||||||||||||||||||||||||||||
const imgInstalls: Array<{ | ||||||||||||||||||||||||||||||||||
imageId: number; | ||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reminder about the discussion on this at
See: https://github.com/balena-io/open-balena-api/pull/1371/files#r1255500040