@alwaysai/device-agent 0.0.8 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/application-control/backup.d.ts.map +1 -1
- package/lib/application-control/backup.js +2 -0
- package/lib/application-control/backup.js.map +1 -1
- package/lib/application-control/config.d.ts +17 -0
- package/lib/application-control/config.d.ts.map +1 -0
- package/lib/application-control/config.js +62 -0
- package/lib/application-control/config.js.map +1 -0
- package/lib/application-control/environment-variables.d.ts.map +1 -1
- package/lib/application-control/environment-variables.js +4 -12
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/index.d.ts +2 -1
- package/lib/application-control/index.d.ts.map +1 -1
- package/lib/application-control/index.js +6 -1
- package/lib/application-control/index.js.map +1 -1
- package/lib/application-control/install.d.ts +12 -10
- package/lib/application-control/install.d.ts.map +1 -1
- package/lib/application-control/install.js +79 -41
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/models.d.ts +3 -0
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +92 -19
- package/lib/application-control/models.js.map +1 -1
- package/lib/application-control/utils.d.ts +4 -3
- package/lib/application-control/utils.d.ts.map +1 -1
- package/lib/application-control/utils.js +30 -10
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +16 -9
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +165 -89
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/infrastructure/agent-config.d.ts.map +1 -1
- package/lib/infrastructure/agent-config.js +7 -18
- package/lib/infrastructure/agent-config.js.map +1 -1
- package/lib/infrastructure/agent-config.test.js +47 -0
- package/lib/infrastructure/agent-config.test.js.map +1 -1
- package/lib/subcommands/login.d.ts.map +1 -1
- package/lib/subcommands/login.js +4 -3
- package/lib/subcommands/login.js.map +1 -1
- package/lib/util/copy-dir.d.ts.map +1 -1
- package/lib/util/copy-dir.js +3 -1
- package/lib/util/copy-dir.js.map +1 -1
- package/lib/util/run-in-dir.d.ts +2 -0
- package/lib/util/run-in-dir.d.ts.map +1 -0
- package/lib/util/run-in-dir.js +17 -0
- package/lib/util/run-in-dir.js.map +1 -0
- package/package.json +3 -3
- package/src/application-control/backup.ts +3 -0
- package/src/application-control/config.ts +61 -0
- package/src/application-control/environment-variables.ts +4 -10
- package/src/application-control/index.ts +5 -0
- package/src/application-control/install.ts +121 -52
- package/src/application-control/models.ts +132 -22
- package/src/application-control/utils.ts +37 -12
- package/src/cloud-connection/device-agent-cloud-connection.ts +197 -105
- package/src/infrastructure/agent-config.test.ts +56 -0
- package/src/infrastructure/agent-config.ts +10 -19
- package/src/subcommands/login.ts +6 -4
- package/src/util/copy-dir.ts +3 -1
- package/src/util/run-in-dir.ts +15 -0
- package/lib/util/run-cli-cmd.d.ts +0 -5
- package/lib/util/run-cli-cmd.d.ts.map +0 -1
- package/lib/util/run-cli-cmd.js +0 -24
- package/lib/util/run-cli-cmd.js.map +0 -1
- package/src/util/run-cli-cmd.ts +0 -18
|
@@ -13,7 +13,11 @@ import {
|
|
|
13
13
|
getAppLogs,
|
|
14
14
|
getAppStatus,
|
|
15
15
|
} from '../application-control/status';
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
getInstalledApps,
|
|
18
|
+
installApp,
|
|
19
|
+
uninstallApp,
|
|
20
|
+
} from '../application-control/install';
|
|
17
21
|
import {
|
|
18
22
|
keyMirrors,
|
|
19
23
|
validateClientMessage,
|
|
@@ -29,14 +33,18 @@ import {
|
|
|
29
33
|
AppStateMessage,
|
|
30
34
|
AppLogsMessage,
|
|
31
35
|
AppInstallStatusMessage,
|
|
32
|
-
|
|
36
|
+
SignedUrlsRequestMessage,
|
|
33
37
|
DeviceAgentMessage,
|
|
34
38
|
ClientMessage,
|
|
39
|
+
AppDetailsPacket,
|
|
35
40
|
} from '@alwaysai/device-agent-schemas';
|
|
36
41
|
import { getDeviceId } from '../util/get-device-id';
|
|
37
|
-
import { logger } from 'alwaysai/lib/util';
|
|
42
|
+
import { JsSpawner, logger } from 'alwaysai/lib/util';
|
|
38
43
|
import { getCpuUtil, getDiskUtil, getMemUtil } from '../device-control/device-control';
|
|
39
44
|
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
45
|
+
import { buildApp, getAppConfig, getAppDir } from '../application-control/utils';
|
|
46
|
+
import { updateModelsWithPresignedUrls } from '../application-control/models';
|
|
47
|
+
import { updateAppConfig } from '../application-control/config';
|
|
40
48
|
|
|
41
49
|
export class DeviceAgentCloudConnection {
|
|
42
50
|
private clientId = getDeviceId();
|
|
@@ -55,21 +63,63 @@ export class DeviceAgentCloudConnection {
|
|
|
55
63
|
[keyMirrors.agentMessageType.app_install_status]: 5000,
|
|
56
64
|
};
|
|
57
65
|
private appLogStreams = new Set<string>();
|
|
58
|
-
private
|
|
59
|
-
private readonly
|
|
60
|
-
private readonly
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
cloudRequest: `${this.cloudTopicPrefix}request`,
|
|
66
|
-
};
|
|
67
|
-
private readonly subsribedTopics = {
|
|
68
|
-
command: `${this.agentTopicPrefix}command`,
|
|
69
|
-
response: `${this.agentTopicPrefix}response`,
|
|
66
|
+
private deviceType = 'aai-device';
|
|
67
|
+
private readonly shadowPrefix = `$aws/things/${this.clientId}/shadow/name/`;
|
|
68
|
+
private readonly shadowTopics = {
|
|
69
|
+
projects: {
|
|
70
|
+
updateDelta: `${this.shadowPrefix}projects/update/delta`,
|
|
71
|
+
getAccepted: `${this.shadowPrefix}projects/get/accepted`,
|
|
72
|
+
},
|
|
70
73
|
};
|
|
74
|
+
private readonly toCloudTopic = `topic/to_cloud/${this.deviceType}/${this.clientId}`;
|
|
75
|
+
private readonly toClientTopic = `topic/to_client/${this.deviceType}/${this.clientId}`;
|
|
76
|
+
private readonly toDeviceTopic = `topic/to_device/${this.deviceType}/${this.clientId}`;
|
|
77
|
+
|
|
78
|
+
// device shadow utils
|
|
79
|
+
|
|
80
|
+
public getShadowPrefix() {
|
|
81
|
+
return this.shadowPrefix;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private async handleNamedShadowUpdate({ payload }: { payload: string }) {
|
|
85
|
+
const delta = JSON.parse(payload);
|
|
86
|
+
const deltaKeys = Object.keys(delta);
|
|
87
|
+
|
|
88
|
+
for (const projectId of deltaKeys) {
|
|
89
|
+
const projectShadow = delta[projectId];
|
|
90
|
+
if (projectShadow.appConfig) {
|
|
91
|
+
const appConfig = projectShadow.appConfig;
|
|
92
|
+
const appDir = getAppDir(projectId);
|
|
93
|
+
await updateAppConfig(projectId, appConfig);
|
|
94
|
+
|
|
95
|
+
if (appConfig.models) {
|
|
96
|
+
this.publishCloudRequest({
|
|
97
|
+
messageType: keyMirrors.agentMessageType.signed_urls_request,
|
|
98
|
+
modelsOnlyUrlsRequest: {
|
|
99
|
+
projectId,
|
|
100
|
+
models: appConfig.models,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (appConfig.scripts && !appConfig.models) {
|
|
106
|
+
const appState = await getAppStatus({ projectId });
|
|
107
|
+
|
|
108
|
+
await buildApp({ appDir });
|
|
71
109
|
|
|
72
|
-
|
|
110
|
+
if (
|
|
111
|
+
appState.services.length &&
|
|
112
|
+
appState.services[0].state !== keyMirrors.appState.stopped
|
|
113
|
+
) {
|
|
114
|
+
restartApp({ projectId });
|
|
115
|
+
}
|
|
116
|
+
await this.publishReportedState(projectId);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private async startAppLogStream(projectId: string) {
|
|
73
123
|
this.appLogStreams.add(projectId);
|
|
74
124
|
const readable = await getAppLogs({
|
|
75
125
|
projectId,
|
|
@@ -93,10 +143,10 @@ export class DeviceAgentCloudConnection {
|
|
|
93
143
|
};
|
|
94
144
|
const packet = this.buildMessagePacket(
|
|
95
145
|
this.getClientId(),
|
|
96
|
-
this.
|
|
146
|
+
this.toClientTopic,
|
|
97
147
|
message,
|
|
98
148
|
);
|
|
99
|
-
this.publishMessage(this.
|
|
149
|
+
this.publishMessage(this.toClientTopic, JSON.stringify(packet));
|
|
100
150
|
});
|
|
101
151
|
|
|
102
152
|
readable.on('error', (error) => {
|
|
@@ -104,7 +154,7 @@ export class DeviceAgentCloudConnection {
|
|
|
104
154
|
});
|
|
105
155
|
|
|
106
156
|
readable.on('finished', () => {
|
|
107
|
-
|
|
157
|
+
logger.info(`App logs finished piping for project ${projectId}`);
|
|
108
158
|
});
|
|
109
159
|
}
|
|
110
160
|
|
|
@@ -233,73 +283,6 @@ export class DeviceAgentCloudConnection {
|
|
|
233
283
|
}
|
|
234
284
|
}
|
|
235
285
|
|
|
236
|
-
private async handleCloudResponse(message: ClientMessage) {
|
|
237
|
-
const payload = message.payload;
|
|
238
|
-
switch (payload.messageType) {
|
|
239
|
-
case keyMirrors.clientMessageType.app_install_cloud_response: {
|
|
240
|
-
const { projectId, appReleaseHash, appInstallPayload, modelsInstallPayload } =
|
|
241
|
-
payload.appInstallCloudResponse;
|
|
242
|
-
|
|
243
|
-
this.initAppInstallStatus({
|
|
244
|
-
status: keyMirrors.appInstallStatus.in_progress,
|
|
245
|
-
appReleaseHash,
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
this.startPublishingLiveUpdates(
|
|
249
|
-
this.publishableTopics.appInstallStatus,
|
|
250
|
-
keyMirrors.agentMessageType.app_install_status,
|
|
251
|
-
this.getAppInstallStatusMessage,
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
// Install the app and models
|
|
255
|
-
try {
|
|
256
|
-
const signedUrlsPayload = {
|
|
257
|
-
appInstallPayload,
|
|
258
|
-
modelsInstallPayload,
|
|
259
|
-
};
|
|
260
|
-
await installApp({
|
|
261
|
-
projectId,
|
|
262
|
-
appReleaseHash,
|
|
263
|
-
signedUrlsPayload,
|
|
264
|
-
});
|
|
265
|
-
this.updateAppInstallStatus({
|
|
266
|
-
status: keyMirrors.appInstallStatus.success,
|
|
267
|
-
});
|
|
268
|
-
} catch (e) {
|
|
269
|
-
console.error(e);
|
|
270
|
-
const message: string = e.message;
|
|
271
|
-
|
|
272
|
-
// uninstall the failed app to put system back in good state
|
|
273
|
-
await uninstallApp({ projectId });
|
|
274
|
-
this.updateAppInstallStatus({
|
|
275
|
-
status: keyMirrors.appInstallStatus.failure,
|
|
276
|
-
message,
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
break;
|
|
280
|
-
}
|
|
281
|
-
default:
|
|
282
|
-
logger.error(`Invalid cloud response message type '${message}'`);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
private handleClientMessage(message: ClientMessage) {
|
|
287
|
-
const payload = message.payload;
|
|
288
|
-
switch (payload.messageType) {
|
|
289
|
-
case keyMirrors.clientMessageType.app_state_control:
|
|
290
|
-
this.handleAppStateControl(payload.appStateControl);
|
|
291
|
-
break;
|
|
292
|
-
case keyMirrors.clientMessageType.app_version_control:
|
|
293
|
-
this.handleAppVersionControl(payload.appVersionControl);
|
|
294
|
-
break;
|
|
295
|
-
case keyMirrors.clientMessageType.live_state_updates:
|
|
296
|
-
this.handleAgentCommand(payload);
|
|
297
|
-
break;
|
|
298
|
-
default:
|
|
299
|
-
logger.error(`Invalid Client Message '${JSON.stringify(payload)}'`);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
286
|
private handleAppStateControl(payload: AppStateControlPacket) {
|
|
304
287
|
const { baseCommand, projectId } = payload;
|
|
305
288
|
switch (baseCommand) {
|
|
@@ -319,7 +302,7 @@ export class DeviceAgentCloudConnection {
|
|
|
319
302
|
const { projectId, appReleaseHash } = payload;
|
|
320
303
|
const signedUrlsRequest = { projectId, appReleaseHash };
|
|
321
304
|
this.publishCloudRequest({
|
|
322
|
-
messageType: keyMirrors.agentMessageType.
|
|
305
|
+
messageType: keyMirrors.agentMessageType.signed_urls_request,
|
|
323
306
|
signedUrlsRequest,
|
|
324
307
|
});
|
|
325
308
|
}
|
|
@@ -363,12 +346,11 @@ export class DeviceAgentCloudConnection {
|
|
|
363
346
|
};
|
|
364
347
|
}) {
|
|
365
348
|
this.restartLiveUpdatesTimeout();
|
|
366
|
-
|
|
367
349
|
if (deviceStats !== undefined) {
|
|
368
350
|
this.liveUpdatesAlive.device_stats = deviceStats;
|
|
369
351
|
if (deviceStats) {
|
|
370
352
|
this.startPublishingLiveUpdates(
|
|
371
|
-
this.
|
|
353
|
+
this.toClientTopic,
|
|
372
354
|
keyMirrors.agentMessageType.device_stats,
|
|
373
355
|
this.getDeviceStatsMessage,
|
|
374
356
|
);
|
|
@@ -379,7 +361,7 @@ export class DeviceAgentCloudConnection {
|
|
|
379
361
|
this.liveUpdatesAlive.app_state = appState;
|
|
380
362
|
if (appState) {
|
|
381
363
|
this.startPublishingLiveUpdates(
|
|
382
|
-
this.
|
|
364
|
+
this.toClientTopic,
|
|
383
365
|
keyMirrors.agentMessageType.app_state,
|
|
384
366
|
this.getAppStateMessage,
|
|
385
367
|
);
|
|
@@ -395,8 +377,20 @@ export class DeviceAgentCloudConnection {
|
|
|
395
377
|
}
|
|
396
378
|
}
|
|
397
379
|
|
|
398
|
-
private async
|
|
399
|
-
const
|
|
380
|
+
private async publishReportedState(projectId) {
|
|
381
|
+
const newAppCfg = await getAppConfig(projectId);
|
|
382
|
+
const packet = {
|
|
383
|
+
state: {
|
|
384
|
+
reported: {
|
|
385
|
+
[projectId]: { appConfig: newAppCfg },
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
this.publishMessage(`${this.shadowPrefix}projects/update`, JSON.stringify(packet));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private async publishCloudRequest(payload: SignedUrlsRequestMessage) {
|
|
393
|
+
const topic = this.toCloudTopic;
|
|
400
394
|
const deviceRequestPacket = this.buildMessagePacket(
|
|
401
395
|
this.getClientId(),
|
|
402
396
|
topic,
|
|
@@ -418,9 +412,9 @@ export class DeviceAgentCloudConnection {
|
|
|
418
412
|
host: this.host,
|
|
419
413
|
});
|
|
420
414
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
415
|
+
this.device.subscribe(this.toDeviceTopic);
|
|
416
|
+
this.device.subscribe(this.shadowTopics.projects.getAccepted);
|
|
417
|
+
this.device.subscribe(this.shadowTopics.projects.updateDelta);
|
|
424
418
|
}
|
|
425
419
|
|
|
426
420
|
public getClientId(): string {
|
|
@@ -432,17 +426,105 @@ export class DeviceAgentCloudConnection {
|
|
|
432
426
|
this.device.publish(topic, message);
|
|
433
427
|
}
|
|
434
428
|
|
|
435
|
-
public
|
|
429
|
+
public async handleClientMessage({
|
|
436
430
|
topic,
|
|
437
431
|
message,
|
|
438
432
|
}: {
|
|
439
433
|
topic: string;
|
|
440
434
|
message: ClientMessage;
|
|
441
435
|
}) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
436
|
+
const payload = message.payload;
|
|
437
|
+
switch (payload.messageType) {
|
|
438
|
+
case keyMirrors.clientMessageType.app_state_control: {
|
|
439
|
+
this.handleAppStateControl(payload.appStateControl);
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
case keyMirrors.clientMessageType.app_version_control: {
|
|
443
|
+
this.handleAppVersionControl(payload.appVersionControl);
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
case keyMirrors.clientMessageType.live_state_updates: {
|
|
447
|
+
this.handleAgentCommand(payload);
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
case keyMirrors.clientMessageType.app_install_cloud_response: {
|
|
451
|
+
const { projectId, appReleaseHash, appInstallPayload, modelsInstallPayload } =
|
|
452
|
+
payload.appInstallCloudResponse;
|
|
453
|
+
|
|
454
|
+
this.initAppInstallStatus({
|
|
455
|
+
status: keyMirrors.appInstallStatus.in_progress,
|
|
456
|
+
appReleaseHash,
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
this.startPublishingLiveUpdates(
|
|
460
|
+
this.toClientTopic,
|
|
461
|
+
keyMirrors.agentMessageType.app_install_status,
|
|
462
|
+
this.getAppInstallStatusMessage,
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
// Install the app and models
|
|
466
|
+
try {
|
|
467
|
+
const signedUrlsPayload = {
|
|
468
|
+
appInstallPayload,
|
|
469
|
+
modelsInstallPayload,
|
|
470
|
+
};
|
|
471
|
+
await installApp({
|
|
472
|
+
projectId,
|
|
473
|
+
appReleaseHash,
|
|
474
|
+
signedUrlsPayload,
|
|
475
|
+
});
|
|
476
|
+
this.updateAppInstallStatus({
|
|
477
|
+
status: keyMirrors.appInstallStatus.success,
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// update app config shadow for project
|
|
481
|
+
await this.publishReportedState(projectId);
|
|
482
|
+
} catch (e) {
|
|
483
|
+
console.error(e);
|
|
484
|
+
const message: string = e.message;
|
|
485
|
+
|
|
486
|
+
// uninstall the failed app to put system back in good state
|
|
487
|
+
await uninstallApp({ projectId });
|
|
488
|
+
this.updateAppInstallStatus({
|
|
489
|
+
status: keyMirrors.appInstallStatus.failure,
|
|
490
|
+
message,
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
// delete shadow for project
|
|
494
|
+
this.publishMessage(`${this.shadowPrefix}${projectId}/delete`, '');
|
|
495
|
+
}
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
case keyMirrors.clientMessageType.models_install_cloud_response: {
|
|
499
|
+
const { projectId, newModels } = payload.modelsInstallCloudResponse;
|
|
500
|
+
|
|
501
|
+
try {
|
|
502
|
+
await updateModelsWithPresignedUrls(projectId, newModels);
|
|
503
|
+
|
|
504
|
+
await this.publishReportedState(projectId);
|
|
505
|
+
} catch (e) {
|
|
506
|
+
console.error(e);
|
|
507
|
+
}
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
default:
|
|
511
|
+
logger.error(`Invalid Client Message '${JSON.stringify(payload)}'`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
public async handleShadowTopic({ topic, payload }: { topic: string; payload: string }) {
|
|
516
|
+
const shadowName = topic.split('/')[5];
|
|
517
|
+
const message = JSON.parse(payload);
|
|
518
|
+
if (topic === this.shadowTopics.projects.updateDelta) {
|
|
519
|
+
this.handleNamedShadowUpdate({ payload });
|
|
520
|
+
} else if (topic === this.shadowTopics.projects.getAccepted) {
|
|
521
|
+
if (message.delta) {
|
|
522
|
+
this.handleNamedShadowUpdate({
|
|
523
|
+
payload: JSON.stringify(message.delta),
|
|
524
|
+
});
|
|
525
|
+
} else {
|
|
526
|
+
console.log(`No delta updates in shadow ${shadowName}`);
|
|
527
|
+
}
|
|
446
528
|
}
|
|
447
529
|
}
|
|
448
530
|
}
|
|
@@ -453,6 +535,9 @@ export function runDeviceAgentCloudInterface() {
|
|
|
453
535
|
deviceAgent.device.on('connect', function () {
|
|
454
536
|
deviceAgent.publishMessage('connection', deviceAgent.getClientId());
|
|
455
537
|
console.log('Device Agent has connected to the cloud');
|
|
538
|
+
|
|
539
|
+
// Get shadow updates
|
|
540
|
+
deviceAgent.publishMessage(`${deviceAgent.getShadowPrefix()}projects/get`, '');
|
|
456
541
|
});
|
|
457
542
|
|
|
458
543
|
deviceAgent.device.on('disconnect', function () {
|
|
@@ -462,14 +547,21 @@ export function runDeviceAgentCloudInterface() {
|
|
|
462
547
|
deviceAgent.device.on('message', function (topic: string, payload: string) {
|
|
463
548
|
try {
|
|
464
549
|
const jsonPacket = JSON.parse(payload);
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
550
|
+
if (jsonPacket.hasOwnProperty('state')) {
|
|
551
|
+
deviceAgent.handleShadowTopic({
|
|
552
|
+
topic,
|
|
553
|
+
payload: JSON.stringify(jsonPacket.state),
|
|
554
|
+
});
|
|
468
555
|
} else {
|
|
469
|
-
|
|
556
|
+
const valid = validateClientMessage(jsonPacket);
|
|
557
|
+
if (!valid) {
|
|
558
|
+
console.error(JSON.stringify(validateClientMessage.errors));
|
|
559
|
+
} else {
|
|
560
|
+
deviceAgent.handleClientMessage({ topic, message: jsonPacket });
|
|
561
|
+
}
|
|
470
562
|
}
|
|
471
563
|
} catch (error) {
|
|
472
|
-
|
|
564
|
+
console.error(error);
|
|
473
565
|
}
|
|
474
566
|
});
|
|
475
567
|
}
|
|
@@ -95,7 +95,9 @@ describe('Test Agent Config', () => {
|
|
|
95
95
|
const version = 'im-a-version';
|
|
96
96
|
|
|
97
97
|
await configFile.setAppInstalling({ projectId, version });
|
|
98
|
+
expect(await configFile.getAppVersion({ projectId })).toEqual(version);
|
|
98
99
|
await configFile.setAppInstalled({ projectId, version });
|
|
100
|
+
expect(await configFile.getAppVersion({ projectId })).toEqual(version);
|
|
99
101
|
await configFile.setAppUninstalled({ projectId });
|
|
100
102
|
const apps = await configFile.getApps();
|
|
101
103
|
expect(apps).toEqual([]);
|
|
@@ -110,9 +112,13 @@ describe('Test Agent Config', () => {
|
|
|
110
112
|
test('Set and get backup', async () => {
|
|
111
113
|
const projectId = 'add-me';
|
|
112
114
|
const version = 'im-a-version';
|
|
115
|
+
const version2 = 'newer-version';
|
|
113
116
|
|
|
117
|
+
// first installation, we use the version
|
|
114
118
|
await configFile.setAppInstalling({ projectId, version });
|
|
119
|
+
expect(await configFile.getAppVersion({ projectId })).toEqual(version);
|
|
115
120
|
await configFile.setAppInstalled({ projectId, version });
|
|
121
|
+
expect(await configFile.getAppVersion({ projectId })).toEqual(version);
|
|
116
122
|
const backup1 = await configFile.getAppBackup({ projectId });
|
|
117
123
|
expect(backup1).toBeNull();
|
|
118
124
|
await configFile.setAppBackup({ projectId });
|
|
@@ -138,6 +144,56 @@ describe('Test Agent Config', () => {
|
|
|
138
144
|
});
|
|
139
145
|
const backup2 = await configFile.getAppBackup({ projectId });
|
|
140
146
|
expect(backup2).toEqual({ version });
|
|
147
|
+
|
|
148
|
+
await configFile.setAppInstalling({ projectId, version: version2 });
|
|
149
|
+
expect(await configFile.getAppVersion({ projectId })).toEqual(version);
|
|
150
|
+
await configFile.setAppInstalled({ projectId, version: version2 });
|
|
151
|
+
expect(await configFile.getAppVersion({ projectId })).toEqual(version2);
|
|
152
|
+
const backup3 = await configFile.getAppBackup({ projectId });
|
|
153
|
+
expect(backup3).toEqual({ version });
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('Test app rollback', async () => {
|
|
157
|
+
const projectId = 'add-me';
|
|
158
|
+
const version1 = 'original-version';
|
|
159
|
+
const version2 = 'newer-version';
|
|
160
|
+
await configFile.setAppInstalling({ projectId, version: version1 });
|
|
161
|
+
await configFile.setAppInstalled({ projectId, version: version1 });
|
|
162
|
+
expect(await configFile.getAppVersion({ projectId })).toEqual(version1);
|
|
163
|
+
expect(await configFile.getAppBackup({ projectId })).toBeNull();
|
|
164
|
+
|
|
165
|
+
// next installation - verify app is present and ready
|
|
166
|
+
expect(await configFile.isAppPresent({ projectId })).toBe(true);
|
|
167
|
+
expect(await configFile.isAppReady({ projectId })).toBe(true);
|
|
168
|
+
|
|
169
|
+
// test to see if app has backup
|
|
170
|
+
await configFile.setAppBackup({ projectId });
|
|
171
|
+
const expectedBackupVersion = (await configFile.getAppBackup({ projectId }))
|
|
172
|
+
?.version;
|
|
173
|
+
expect(expectedBackupVersion).toEqual(version1);
|
|
174
|
+
expect(await configFile.getAppVersion({ projectId })).toEqual(version1);
|
|
175
|
+
|
|
176
|
+
// simulate second version install
|
|
177
|
+
await configFile.setAppInstalling({ projectId, version: version2 });
|
|
178
|
+
const newExpectedBackupVersion = (await configFile.getAppBackup({ projectId }))
|
|
179
|
+
?.version;
|
|
180
|
+
expect(newExpectedBackupVersion).toEqual(version1);
|
|
181
|
+
await configFile.setAppInstalled({ projectId, version: version2 });
|
|
182
|
+
expect(await configFile.getAppVersion({ projectId })).toEqual(version2);
|
|
183
|
+
|
|
184
|
+
// simulate the rollback step: set the version that we will roll back to
|
|
185
|
+
await configFile.setAppInstalling({ projectId, version: version1 });
|
|
186
|
+
|
|
187
|
+
// test state during rollback
|
|
188
|
+
expect(await configFile.isAppReady({ projectId })).toEqual(false);
|
|
189
|
+
expect(await configFile.getAppVersion({ projectId })).toEqual(version2);
|
|
190
|
+
|
|
191
|
+
// test final state of rolled-back app
|
|
192
|
+
await configFile.setAppInstalled({ projectId, version: version1 });
|
|
193
|
+
const finalBackupVersion = (await configFile.getAppBackup({ projectId }))?.version;
|
|
194
|
+
expect(finalBackupVersion).toEqual(version1);
|
|
195
|
+
expect(await configFile.isAppReady({ projectId })).toEqual(true);
|
|
196
|
+
expect(await configFile.getAppVersion({ projectId })).toEqual(version1);
|
|
141
197
|
});
|
|
142
198
|
});
|
|
143
199
|
});
|
|
@@ -2,7 +2,6 @@ import { ConfigFileSchema } from '@alwaysai/config-nodejs';
|
|
|
2
2
|
import { JSONSchemaType } from 'ajv';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import { join } from 'path';
|
|
5
|
-
import { ALWAYSAI_DEVICE_AGENT_MODE } from '../environment';
|
|
6
5
|
|
|
7
6
|
export interface AppBackupConfig {
|
|
8
7
|
version: string;
|
|
@@ -134,17 +133,16 @@ export function AgentConfigFile(dir = ALWAYSAI_CONFIG_DIR) {
|
|
|
134
133
|
|
|
135
134
|
async function setAppInstalling(props: { projectId: string; version: string }) {
|
|
136
135
|
const { projectId, version } = props;
|
|
136
|
+
|
|
137
137
|
const app = await getApp({ projectId });
|
|
138
138
|
if (app) {
|
|
139
139
|
await removeApp({ projectId });
|
|
140
140
|
const config = configFile.read();
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
version,
|
|
144
|
-
ready: false,
|
|
145
|
-
});
|
|
141
|
+
// NOTE: do not update the version for an existing app until it is installed
|
|
142
|
+
config.applications.push({ ...app, ...{ ready: false } });
|
|
146
143
|
configFile.write(config);
|
|
147
144
|
} else {
|
|
145
|
+
// NOTE: for a brand-new app, we need to specify the version
|
|
148
146
|
const config = configFile.read();
|
|
149
147
|
config.applications.push({
|
|
150
148
|
projectId,
|
|
@@ -161,21 +159,14 @@ export function AgentConfigFile(dir = ALWAYSAI_CONFIG_DIR) {
|
|
|
161
159
|
if (app) {
|
|
162
160
|
await removeApp({ projectId });
|
|
163
161
|
const config = configFile.read();
|
|
164
|
-
app
|
|
165
|
-
config.applications.push({
|
|
166
|
-
projectId,
|
|
167
|
-
version,
|
|
168
|
-
ready: true,
|
|
169
|
-
});
|
|
162
|
+
config.applications.push({ ...app, ...{ version, ready: true } });
|
|
170
163
|
configFile.write(config);
|
|
171
164
|
} else {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
});
|
|
178
|
-
configFile.write(config);
|
|
165
|
+
// NOTE: we should never be setting an app as installed
|
|
166
|
+
// if it doesn't exist (setAppInstalling was never called)
|
|
167
|
+
throw new Error(
|
|
168
|
+
`App ${projectId} was not previously configured and could not be set to installed!`,
|
|
169
|
+
);
|
|
179
170
|
}
|
|
180
171
|
}
|
|
181
172
|
|
package/src/subcommands/login.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { CliLeaf, CliStringInput
|
|
1
|
+
import { CliLeaf, CliStringInput } from '@alwaysai/alwayscli';
|
|
2
|
+
import { alwaysaiUserLoginYesComponent } from 'alwaysai/lib/components/user';
|
|
2
3
|
import { writeCertificateAndToken } from '../infrastructure/certificates-and-tokens';
|
|
3
|
-
import { runCliCmd } from '../util/run-cli-cmd';
|
|
4
4
|
|
|
5
5
|
export const loginCliLeaf = CliLeaf({
|
|
6
6
|
name: 'login',
|
|
@@ -21,8 +21,10 @@ export const loginCliLeaf = CliLeaf({
|
|
|
21
21
|
},
|
|
22
22
|
async action(_, opts) {
|
|
23
23
|
const { email, password, device } = opts;
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
|
|
25
|
+
await alwaysaiUserLoginYesComponent({
|
|
26
|
+
alwaysaiUserEmail: email,
|
|
27
|
+
alwaysaiUserPassword: password,
|
|
26
28
|
});
|
|
27
29
|
if (device) {
|
|
28
30
|
await writeCertificateAndToken({ deviceUuid: device });
|
package/src/util/copy-dir.ts
CHANGED
|
@@ -6,5 +6,7 @@ export async function copyDir(props: { srcPath: string; destPath: string }) {
|
|
|
6
6
|
const allFileNames = await src.readdir();
|
|
7
7
|
const dest = JsSpawner({ path: destPath });
|
|
8
8
|
await dest.mkdirp();
|
|
9
|
-
|
|
9
|
+
if (allFileNames.length) {
|
|
10
|
+
await dest.untar(await src.tar(...allFileNames));
|
|
11
|
+
}
|
|
10
12
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export async function runInDir<T extends any[], R extends any>(
|
|
2
|
+
func: (...args: T) => R,
|
|
3
|
+
args: T,
|
|
4
|
+
dir: string,
|
|
5
|
+
) {
|
|
6
|
+
const origCwd = process.cwd();
|
|
7
|
+
let out: R;
|
|
8
|
+
try {
|
|
9
|
+
process.chdir(dir);
|
|
10
|
+
out = await func(...args);
|
|
11
|
+
} finally {
|
|
12
|
+
process.chdir(origCwd);
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"run-cli-cmd.d.ts","sourceRoot":"","sources":["../../src/util/run-cli-cmd.ts"],"names":[],"mappings":"AAEA,wBAAsB,SAAS,CAAC,KAAK,EAAE;IAAE,GAAG,EAAE,MAAM,EAAE,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAerF"}
|
package/lib/util/run-cli-cmd.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.runCliCmd = void 0;
|
|
4
|
-
const alwaysai = require("alwaysai");
|
|
5
|
-
async function runCliCmd(props) {
|
|
6
|
-
const { cmd, cwd } = props;
|
|
7
|
-
const origCwd = process.cwd();
|
|
8
|
-
try {
|
|
9
|
-
if (cwd) {
|
|
10
|
-
process.chdir(cwd);
|
|
11
|
-
}
|
|
12
|
-
await alwaysai.aai(...cmd);
|
|
13
|
-
}
|
|
14
|
-
catch (err) {
|
|
15
|
-
throw err;
|
|
16
|
-
}
|
|
17
|
-
finally {
|
|
18
|
-
if (cwd) {
|
|
19
|
-
process.chdir(origCwd);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
exports.runCliCmd = runCliCmd;
|
|
24
|
-
//# sourceMappingURL=run-cli-cmd.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"run-cli-cmd.js","sourceRoot":"","sources":["../../src/util/run-cli-cmd.ts"],"names":[],"mappings":";;;AAAA,qCAAqC;AAE9B,KAAK,UAAU,SAAS,CAAC,KAAsC;IACpE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC;IAC3B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC9B,IAAI;QACF,IAAI,GAAG,EAAE;YACP,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;SACpB;QACD,MAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;KAC5B;IAAC,OAAO,GAAG,EAAE;QACZ,MAAM,GAAG,CAAC;KACX;YAAS;QACR,IAAI,GAAG,EAAE;YACP,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;SACxB;KACF;AACH,CAAC;AAfD,8BAeC"}
|
package/src/util/run-cli-cmd.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import * as alwaysai from 'alwaysai';
|
|
2
|
-
|
|
3
|
-
export async function runCliCmd(props: { cmd: string[]; cwd?: string }): Promise<void> {
|
|
4
|
-
const { cmd, cwd } = props;
|
|
5
|
-
const origCwd = process.cwd();
|
|
6
|
-
try {
|
|
7
|
-
if (cwd) {
|
|
8
|
-
process.chdir(cwd);
|
|
9
|
-
}
|
|
10
|
-
await alwaysai.aai(...cmd);
|
|
11
|
-
} catch (err) {
|
|
12
|
-
throw err;
|
|
13
|
-
} finally {
|
|
14
|
-
if (cwd) {
|
|
15
|
-
process.chdir(origCwd);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
}
|