Skip to content

Commit 7d916ca

Browse files
committed
feat(cli): skip existing devices
1 parent 1459991 commit 7d916ca

File tree

3 files changed

+108
-31
lines changed

3 files changed

+108
-31
lines changed

cli/commands/import-devices.ts

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getAPISettings } from '@hello.nrfcloud.com/nrfcloud-api-helpers/setting
55
import chalk from 'chalk'
66
import { chunk } from 'lodash-es'
77
import { table } from 'table'
8+
import { getByDeviceIds } from '../../devices/getByDeviceIds.js'
89
import { compareLists } from '../../devices/import/compareLists.js'
910
import { readDeviceCertificates } from '../../devices/import/readDeviceCertificates.js'
1011
import { readDevicesList } from '../../devices/import/readDevicesList.js'
@@ -79,20 +80,37 @@ export const importDevicesCommand = ({
7980
process.exit(1)
8081
}
8182

83+
const byIds = getByDeviceIds({ db, DevicesTableName: devicesTableName })
84+
const existing = await byIds(
85+
Array.from(devices.keys()).map((imei) => `oob-${imei}`),
86+
)
87+
const existingDevices = Object.keys(existing)
88+
8289
console.log(
8390
table([
8491
['Fingerprint', 'Device ID', 'Model', 'HW version'],
85-
...Array.from(devices.entries()).map(
86-
([imei, { fingerprint, hwVersion }]) => [
92+
...Array.from(devices.entries())
93+
.filter(([imei]) => !existingDevices.includes(`oob-${imei}`))
94+
.map(([imei, { fingerprint, hwVersion }]) => [
8795
chalk.green(fingerprint),
8896
chalk.blue(imei),
8997
chalk.blue(model),
9098
chalk.blue(hwVersion),
91-
],
92-
),
99+
]),
93100
]),
94101
)
95102

103+
if (existingDevices.length > 0) {
104+
console.warn(
105+
chalk.yellow(
106+
`Skipping`,
107+
existingDevices.length,
108+
`devices which are already registered.`,
109+
),
110+
)
111+
console.warn('')
112+
}
113+
96114
if (dryRun === true) {
97115
console.log(chalk.gray(`Dry run, not registering devices.`))
98116
process.exit(2)
@@ -112,15 +130,17 @@ export const importDevicesCommand = ({
112130
for (const page of chunk([...deviceCertificates.entries()], 1000)) {
113131
// Bulk-ops API allows max 1,000 devices per request
114132
const registration = await client.register(
115-
Array.from(page).map(([imei, { certificate: certPem }]) => {
116-
const deviceId = `oob-${imei}`
117-
return {
118-
deviceId,
119-
subType: model.replace(/[^0-9a-z-]/gi, '-'),
120-
tags: [model.replace(/[^0-9a-z-]/gi, ':')],
121-
certPem,
122-
}
123-
}),
133+
Array.from(page)
134+
.filter(([imei]) => !existingDevices.includes(`oob-${imei}`))
135+
.map(([imei, { certificate: certPem }]) => {
136+
const deviceId = `oob-${imei}`
137+
return {
138+
deviceId,
139+
subType: model.replace(/[^0-9a-z-]/gi, '-'),
140+
tags: [model.replace(/[^0-9a-z-]/gi, ':')],
141+
certPem,
142+
}
143+
}),
124144
)
125145

126146
if ('error' in registration) {
@@ -140,7 +160,9 @@ export const importDevicesCommand = ({
140160
devicesTableName,
141161
})
142162

143-
for (const [imei, { fingerprint, hwVersion }] of devices.entries()) {
163+
for (const [imei, { fingerprint, hwVersion }] of devices
164+
.entries()
165+
.filter(([imei]) => !existingDevices.includes(`oob-${imei}`))) {
144166
const deviceId = `oob-${imei}`
145167

146168
const res = await r({

devices/getByDeviceIds.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import {
2+
type DynamoDBClient,
3+
BatchGetItemCommand,
4+
} from '@aws-sdk/client-dynamodb'
5+
import { marshall } from '@aws-sdk/util-dynamodb'
6+
import { chunk } from 'lodash-es'
7+
import type { Device } from './device.js'
8+
import { toDevice } from './getDeviceById.js'
9+
10+
export const getByDeviceIds =
11+
({
12+
db,
13+
DevicesTableName,
14+
}: {
15+
db: DynamoDBClient
16+
DevicesTableName: string
17+
}) =>
18+
async (
19+
deviceIds: Array<string>,
20+
): Promise<
21+
Record<
22+
string,
23+
| {
24+
device: Device
25+
}
26+
| { error: Error }
27+
>
28+
> => {
29+
const result: Record<string, { device: Device } | { error: Error }> = {}
30+
31+
for (const page of chunk(deviceIds, 100)) {
32+
const res = await db.send(
33+
new BatchGetItemCommand({
34+
RequestItems: {
35+
[DevicesTableName]: {
36+
Keys: page.map((deviceId) => marshall({ deviceId })),
37+
},
38+
},
39+
}),
40+
)
41+
42+
if (res.Responses?.[DevicesTableName] === undefined) continue
43+
44+
for (const item of res.Responses[DevicesTableName]) {
45+
const device = toDevice(item)
46+
result[device.id] = { device }
47+
}
48+
}
49+
50+
return result
51+
}

devices/getDeviceById.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,8 @@ export const getDeviceById =
3030
error: new DeviceNotFoundError(deviceId),
3131
}
3232

33-
const {
34-
deviceId: id,
35-
model,
36-
account,
37-
fingerprint,
38-
hideDataBefore,
39-
} = unmarshall(res.Item)
40-
const device: Device = {
41-
id,
42-
fingerprint,
43-
model,
44-
account,
45-
}
46-
if (hideDataBefore !== undefined) {
47-
device.hideDataBefore = new Date(hideDataBefore)
48-
}
4933
return {
50-
device,
34+
device: toDevice(res.Item),
5135
}
5236
} catch (error) {
5337
return {
@@ -56,6 +40,26 @@ export const getDeviceById =
5640
}
5741
}
5842

43+
export const toDevice = (item: Record<string, any>): Device => {
44+
const {
45+
deviceId: id,
46+
model,
47+
account,
48+
fingerprint,
49+
hideDataBefore,
50+
} = unmarshall(item)
51+
const device: Device = {
52+
id,
53+
fingerprint,
54+
model,
55+
account,
56+
}
57+
if (hideDataBefore !== undefined) {
58+
device.hideDataBefore = new Date(hideDataBefore)
59+
}
60+
return device
61+
}
62+
5963
export class DeviceNotFoundError extends Error {
6064
public readonly id: string
6165
constructor(id: string) {

0 commit comments

Comments
 (0)