@alwaysai/device-agent 0.0.12 → 0.0.13
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 +8 -2
- package/lib/application-control/backup.js.map +1 -1
- package/lib/application-control/config.d.ts +12 -4
- package/lib/application-control/config.d.ts.map +1 -1
- package/lib/application-control/config.js +59 -16
- package/lib/application-control/config.js.map +1 -1
- package/lib/application-control/environment-variables.d.ts.map +1 -1
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/index.d.ts +4 -4
- package/lib/application-control/index.d.ts.map +1 -1
- package/lib/application-control/index.js +4 -3
- package/lib/application-control/index.js.map +1 -1
- package/lib/application-control/install.d.ts.map +1 -1
- package/lib/application-control/install.js +28 -14
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/models.d.ts +7 -1
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +69 -39
- package/lib/application-control/models.js.map +1 -1
- package/lib/application-control/status.d.ts.map +1 -1
- package/lib/application-control/status.js +18 -14
- package/lib/application-control/status.js.map +1 -1
- package/lib/application-control/utils.d.ts +0 -2
- package/lib/application-control/utils.d.ts.map +1 -1
- package/lib/application-control/utils.js +7 -16
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/app-install-status.d.ts +16 -0
- package/lib/cloud-connection/app-install-status.d.ts.map +1 -0
- package/lib/cloud-connection/app-install-status.js +53 -0
- package/lib/cloud-connection/app-install-status.js.map +1 -0
- package/lib/cloud-connection/bootstrap-provision.d.ts +2 -0
- package/lib/cloud-connection/bootstrap-provision.d.ts.map +1 -0
- package/lib/cloud-connection/bootstrap-provision.js +34 -0
- package/lib/cloud-connection/bootstrap-provision.js.map +1 -0
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +12 -34
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +169 -385
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/device-agent.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent.js +22 -26
- package/lib/cloud-connection/device-agent.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts +34 -0
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -0
- package/lib/cloud-connection/live-updates-handler.js +167 -0
- package/lib/cloud-connection/live-updates-handler.js.map +1 -0
- package/lib/cloud-connection/messages.d.ts +14 -0
- package/lib/cloud-connection/messages.d.ts.map +1 -0
- package/lib/cloud-connection/messages.js +38 -0
- package/lib/cloud-connection/messages.js.map +1 -0
- package/lib/cloud-connection/publisher.d.ts +14 -0
- package/lib/cloud-connection/publisher.d.ts.map +1 -0
- package/lib/cloud-connection/publisher.js +44 -0
- package/lib/cloud-connection/publisher.js.map +1 -0
- package/lib/cloud-connection/shadow-handler.d.ts +34 -0
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -0
- package/lib/cloud-connection/shadow-handler.js +94 -0
- package/lib/cloud-connection/shadow-handler.js.map +1 -0
- package/lib/cloud-connection/shadow.d.ts +16 -0
- package/lib/cloud-connection/shadow.d.ts.map +1 -0
- package/lib/cloud-connection/shadow.js +36 -0
- package/lib/cloud-connection/shadow.js.map +1 -0
- package/lib/device-control/device-control.d.ts.map +1 -1
- package/lib/device-control/device-control.js +1 -0
- package/lib/device-control/device-control.js.map +1 -1
- package/lib/docker/docker-cmd.js +1 -1
- package/lib/docker/docker-compose-cmd.d.ts.map +1 -1
- package/lib/docker/docker-compose-cmd.js +1 -1
- package/lib/docker/docker-compose-cmd.js.map +1 -1
- package/lib/endpoints.js +10 -10
- package/lib/endpoints.js.map +1 -1
- package/lib/environment.d.ts.map +1 -1
- package/lib/environment.js.map +1 -1
- package/lib/infrastructure/agent-config.d.ts +4 -14
- package/lib/infrastructure/agent-config.d.ts.map +1 -1
- package/lib/infrastructure/agent-config.js +22 -15
- package/lib/infrastructure/agent-config.js.map +1 -1
- package/lib/infrastructure/agent-config.test.js +26 -18
- package/lib/infrastructure/agent-config.test.js.map +1 -1
- package/lib/infrastructure/system-id.d.ts +2 -0
- package/lib/infrastructure/system-id.d.ts.map +1 -0
- package/lib/infrastructure/system-id.js +21 -0
- package/lib/infrastructure/system-id.js.map +1 -0
- package/lib/infrastructure/tokens-and-device-cfg.d.ts +4 -0
- package/lib/infrastructure/tokens-and-device-cfg.d.ts.map +1 -0
- package/lib/infrastructure/tokens-and-device-cfg.js +31 -0
- package/lib/infrastructure/tokens-and-device-cfg.js.map +1 -0
- package/lib/infrastructure/urls.d.ts.map +1 -1
- package/lib/infrastructure/urls.js +3 -3
- package/lib/infrastructure/urls.js.map +1 -1
- package/lib/root.d.ts.map +1 -1
- package/lib/root.js +2 -7
- package/lib/root.js.map +1 -1
- package/lib/subcommands/app/app.d.ts.map +1 -1
- package/lib/subcommands/app/app.js +62 -60
- package/lib/subcommands/app/app.js.map +1 -1
- package/lib/subcommands/app/index.js +2 -2
- package/lib/subcommands/device/clean.d.ts +2 -0
- package/lib/subcommands/device/clean.d.ts.map +1 -0
- package/lib/subcommands/device/clean.js +29 -0
- package/lib/subcommands/device/clean.js.map +1 -0
- package/lib/subcommands/device/device.d.ts.map +1 -1
- package/lib/subcommands/device/device.js +40 -27
- package/lib/subcommands/device/device.js.map +1 -1
- package/lib/subcommands/device/index.d.ts.map +1 -1
- package/lib/subcommands/device/index.js +2 -1
- package/lib/subcommands/device/index.js.map +1 -1
- package/lib/subcommands/get-model-package.js +5 -5
- package/lib/subcommands/index.js +1 -1
- package/lib/subcommands/login.js +8 -8
- package/lib/subcommands/login.js.map +1 -1
- package/lib/util/clean-certs.d.ts +2 -0
- package/lib/util/clean-certs.d.ts.map +1 -0
- package/lib/util/clean-certs.js +16 -0
- package/lib/util/clean-certs.js.map +1 -0
- package/lib/util/directories.d.ts +16 -15
- package/lib/util/directories.d.ts.map +1 -1
- package/lib/util/directories.js +45 -26
- package/lib/util/directories.js.map +1 -1
- package/lib/util/get-device-id.d.ts +1 -1
- package/lib/util/get-device-id.d.ts.map +1 -1
- package/lib/util/get-device-id.js +14 -19
- package/lib/util/get-device-id.js.map +1 -1
- package/lib/util/http-client.d.ts +1 -1
- package/lib/util/http-client.d.ts.map +1 -1
- package/lib/util/http-client.js +10 -8
- package/lib/util/http-client.js.map +1 -1
- package/lib/util/logger.d.ts.map +1 -1
- package/lib/util/logger.js +4 -5
- package/lib/util/logger.js.map +1 -1
- package/lib/util/run-in-dir.d.ts.map +1 -1
- package/lib/util/run-in-dir.js +1 -0
- package/lib/util/run-in-dir.js.map +1 -1
- package/package.json +17 -8
- package/src/application-control/backup.ts +8 -3
- package/src/application-control/config.ts +75 -13
- package/src/application-control/environment-variables.ts +3 -3
- package/src/application-control/index.ts +19 -6
- package/src/application-control/install.ts +52 -28
- package/src/application-control/models.ts +100 -56
- package/src/application-control/status.ts +26 -21
- package/src/application-control/utils.ts +9 -20
- package/src/cloud-connection/app-install-status.ts +62 -0
- package/src/cloud-connection/bootstrap-provision.ts +40 -0
- package/src/cloud-connection/device-agent-cloud-connection.ts +257 -528
- package/src/cloud-connection/device-agent.ts +31 -38
- package/src/cloud-connection/live-updates-handler.ts +226 -0
- package/src/cloud-connection/messages.ts +39 -0
- package/src/cloud-connection/publisher.ts +65 -0
- package/src/cloud-connection/shadow-handler.ts +154 -0
- package/src/cloud-connection/shadow.ts +50 -0
- package/src/device-control/device-control.ts +1 -0
- package/src/docker/docker-cmd.ts +1 -1
- package/src/docker/docker-compose-cmd.ts +5 -2
- package/src/endpoints.ts +9 -9
- package/src/environment.ts +8 -3
- package/src/infrastructure/agent-config.test.ts +34 -23
- package/src/infrastructure/agent-config.ts +33 -20
- package/src/infrastructure/system-id.ts +18 -0
- package/src/infrastructure/tokens-and-device-cfg.ts +39 -0
- package/src/infrastructure/urls.ts +4 -2
- package/src/root.ts +2 -8
- package/src/subcommands/app/app.ts +64 -62
- package/src/subcommands/app/index.ts +3 -3
- package/src/subcommands/device/clean.ts +26 -0
- package/src/subcommands/device/device.ts +66 -50
- package/src/subcommands/device/index.ts +2 -1
- package/src/subcommands/get-model-package.ts +5 -5
- package/src/subcommands/index.ts +1 -1
- package/src/subcommands/login.ts +8 -8
- package/src/util/clean-certs.ts +12 -0
- package/src/util/directories.ts +68 -52
- package/src/util/get-device-id.ts +16 -18
- package/src/util/http-client.ts +18 -13
- package/src/util/logger.ts +6 -6
- package/src/util/run-in-dir.ts +2 -1
- package/lib/infrastructure/certificates-and-tokens.d.ts +0 -6
- package/lib/infrastructure/certificates-and-tokens.d.ts.map +0 -1
- package/lib/infrastructure/certificates-and-tokens.js +0 -43
- package/lib/infrastructure/certificates-and-tokens.js.map +0 -1
- package/src/infrastructure/certificates-and-tokens.ts +0 -53
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+
// eslint-disable-next-line
|
|
2
|
+
const awsIot = require('aws-iot-device-sdk');
|
|
2
3
|
import {
|
|
3
|
-
BOOTSTRAP_CLAIM_ID_FILE_NAME,
|
|
4
|
-
BOOTSTRAP_CLAIM_ID_FILE_PATH,
|
|
5
4
|
DEVICE_PRIVATE_KEY_FILE_NAME,
|
|
6
5
|
CERTIFICATE_OWNERSHIP_TOKEN_FILE_NAME,
|
|
7
6
|
DEVICE_CLAIM_ID_FILE_NAME,
|
|
8
|
-
DEVICE_CERTIFICATE_FILE_NAME
|
|
9
|
-
} from
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
const process = require(
|
|
7
|
+
DEVICE_CERTIFICATE_FILE_NAME
|
|
8
|
+
} from '../util/directories';
|
|
9
|
+
import { getDeviceUuid } from '../util/get-device-id';
|
|
10
|
+
import { LOCAL_CERT_AND_KEY_DIR } from 'alwaysai/lib/constants';
|
|
11
|
+
import { JsSpawner } from 'alwaysai/lib/util';
|
|
12
|
+
import { logger } from '../util/logger';
|
|
13
|
+
import { getTargetHardwareUuid } from 'alwaysai/lib/core/app';
|
|
14
|
+
// eslint-disable-next-line
|
|
15
|
+
const process = require('process');
|
|
17
16
|
|
|
18
17
|
interface DeviceAgentConfigType {
|
|
19
18
|
keyPath: string;
|
|
@@ -24,12 +23,12 @@ interface DeviceAgentConfigType {
|
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
interface FleetProvisionTemplateMessageType {
|
|
27
|
-
certificateOwnershipToken:
|
|
26
|
+
certificateOwnershipToken: string;
|
|
28
27
|
parameters: {
|
|
29
|
-
hardwareId:
|
|
30
|
-
deviceUuid:
|
|
31
|
-
certificateId:
|
|
32
|
-
deviceType:
|
|
28
|
+
hardwareId: string;
|
|
29
|
+
deviceUuid: string;
|
|
30
|
+
certificateId: string;
|
|
31
|
+
deviceType: string;
|
|
33
32
|
};
|
|
34
33
|
}
|
|
35
34
|
|
|
@@ -38,10 +37,10 @@ export class DeviceAgent {
|
|
|
38
37
|
this.device = awsIot.device(config);
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
public deviceType =
|
|
40
|
+
public deviceType = 'aai-device';
|
|
42
41
|
public device = awsIot.device;
|
|
43
42
|
public hardwareId = async () => await getTargetHardwareUuid(JsSpawner());
|
|
44
|
-
public deviceId =
|
|
43
|
+
public deviceId = getDeviceUuid();
|
|
45
44
|
|
|
46
45
|
public publishMessage(topic: string, message: string) {
|
|
47
46
|
this.device.publish(topic, message);
|
|
@@ -51,40 +50,39 @@ export class DeviceAgent {
|
|
|
51
50
|
export class BootstrapAgent extends DeviceAgent {
|
|
52
51
|
public async subscribeToAllTopics() {
|
|
53
52
|
const AWS_CERTIFICATE_ACCEPT_TOPIC =
|
|
54
|
-
|
|
53
|
+
'$aws/certificates/create/json/accepted';
|
|
55
54
|
const PROVISIONING_ACCEPTED_TOPIC =
|
|
56
|
-
|
|
55
|
+
'$aws/provisioning-templates/FleetProvisionTemplate/provision/json/accepted';
|
|
57
56
|
|
|
58
57
|
const topics = [AWS_CERTIFICATE_ACCEPT_TOPIC, PROVISIONING_ACCEPTED_TOPIC];
|
|
59
58
|
const resp = this.device.subscribe(
|
|
60
59
|
topics,
|
|
61
60
|
function (err: any, granted: { topic: string; qos: number }[]) {
|
|
62
|
-
logger.debug(granted);
|
|
61
|
+
logger.debug(`Bootstrap Agent: ${JSON.stringify(granted, null, 2)}`);
|
|
63
62
|
}
|
|
64
63
|
);
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
public async handleAwsCertificateTopics(topic: string, payload: any) {
|
|
68
67
|
switch (topic) {
|
|
69
|
-
case
|
|
68
|
+
case '$aws/certificates/create/json/accepted': {
|
|
70
69
|
const {
|
|
71
70
|
certificateId,
|
|
72
71
|
certificatePem,
|
|
73
72
|
privateKey,
|
|
74
|
-
certificateOwnershipToken
|
|
73
|
+
certificateOwnershipToken
|
|
75
74
|
} = JSON.parse(payload);
|
|
76
|
-
console.log(certificateId);
|
|
77
75
|
|
|
78
76
|
const certSpawner = JsSpawner({ path: LOCAL_CERT_AND_KEY_DIR });
|
|
79
77
|
|
|
80
78
|
await certSpawner.writeFile(
|
|
81
|
-
DEVICE_CERTIFICATE_FILE_NAME,
|
|
79
|
+
DEVICE_CERTIFICATE_FILE_NAME(),
|
|
82
80
|
certificatePem
|
|
83
81
|
);
|
|
84
82
|
|
|
85
|
-
await certSpawner.writeFile(DEVICE_PRIVATE_KEY_FILE_NAME, privateKey);
|
|
83
|
+
await certSpawner.writeFile(DEVICE_PRIVATE_KEY_FILE_NAME(), privateKey);
|
|
86
84
|
|
|
87
|
-
await certSpawner.writeFile(DEVICE_CLAIM_ID_FILE_NAME, certificateId);
|
|
85
|
+
await certSpawner.writeFile(DEVICE_CLAIM_ID_FILE_NAME(), certificateId);
|
|
88
86
|
|
|
89
87
|
await certSpawner.writeFile(
|
|
90
88
|
CERTIFICATE_OWNERSHIP_TOKEN_FILE_NAME,
|
|
@@ -97,24 +95,19 @@ export class BootstrapAgent extends DeviceAgent {
|
|
|
97
95
|
hardwareId: await this.hardwareId(),
|
|
98
96
|
deviceUuid: this.deviceId,
|
|
99
97
|
certificateId,
|
|
100
|
-
deviceType: this.deviceType
|
|
101
|
-
}
|
|
98
|
+
deviceType: this.deviceType
|
|
99
|
+
}
|
|
102
100
|
};
|
|
103
|
-
console.log(content);
|
|
104
101
|
|
|
105
102
|
this.publishMessage(
|
|
106
|
-
|
|
103
|
+
'$aws/provisioning-templates/FleetProvisionTemplate/provision/json',
|
|
107
104
|
JSON.stringify(content)
|
|
108
105
|
);
|
|
109
106
|
|
|
110
107
|
break;
|
|
111
108
|
}
|
|
112
|
-
case
|
|
113
|
-
|
|
114
|
-
// Resolve the `child_process` module, and `spawn`
|
|
115
|
-
// a new process.
|
|
116
|
-
// The `child_process` module lets us
|
|
117
|
-
// access OS functionalities by running any bash command.`.
|
|
109
|
+
case '$aws/provisioning-templates/FleetProvisionTemplate/provision/json/accepted': {
|
|
110
|
+
logger.info('Device agent provisioning: success');
|
|
118
111
|
process.exit();
|
|
119
112
|
}
|
|
120
113
|
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppLogsMessage,
|
|
3
|
+
DeviceAgentMessagePayload,
|
|
4
|
+
keyMirrors,
|
|
5
|
+
LiveUpdatesToggles
|
|
6
|
+
} from '@alwaysai/device-agent-schemas';
|
|
7
|
+
import { getAppLogs } from '../application-control';
|
|
8
|
+
import { logger } from '../util/logger';
|
|
9
|
+
import sleep from '../util/sleep';
|
|
10
|
+
import { Publisher } from './publisher';
|
|
11
|
+
import { getAppStateMessage, getDeviceStatsMessage } from './messages';
|
|
12
|
+
import { AgentMessageTypeValue } from '@alwaysai/device-agent-schemas/lib/constants';
|
|
13
|
+
import { AppInstallStatusManager } from './app-install-status';
|
|
14
|
+
|
|
15
|
+
export class LiveUpdatesHandler {
|
|
16
|
+
private publisher: Publisher;
|
|
17
|
+
private appInstallStatusMgr: AppInstallStatusManager;
|
|
18
|
+
|
|
19
|
+
private liveUpdatesTimeout: ReturnType<typeof setTimeout>;
|
|
20
|
+
private liveUpdatesAlive = {
|
|
21
|
+
[keyMirrors.agentMessageType.device_stats]: false,
|
|
22
|
+
[keyMirrors.agentMessageType.app_state]: false,
|
|
23
|
+
[keyMirrors.agentMessageType.app_logs]: false,
|
|
24
|
+
[keyMirrors.agentMessageType.app_install_status]: false
|
|
25
|
+
};
|
|
26
|
+
private liveUpdatesSleepIntervals = {
|
|
27
|
+
[keyMirrors.agentMessageType.device_stats]: 5000,
|
|
28
|
+
[keyMirrors.agentMessageType.app_state]: 5000,
|
|
29
|
+
[keyMirrors.agentMessageType.app_logs]: 5000,
|
|
30
|
+
[keyMirrors.agentMessageType.app_install_status]: 5000
|
|
31
|
+
};
|
|
32
|
+
private appLogStreams = new Set<string>();
|
|
33
|
+
private appInstallStatuses = new Set<string>();
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
publisher: Publisher,
|
|
37
|
+
appInstallStatusMgr: AppInstallStatusManager
|
|
38
|
+
) {
|
|
39
|
+
this.publisher = publisher;
|
|
40
|
+
this.appInstallStatusMgr = appInstallStatusMgr;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private async startAppLogStream(projectId: string) {
|
|
44
|
+
logger.info(`Starting log stream for ${projectId}`);
|
|
45
|
+
this.appLogStreams.add(projectId);
|
|
46
|
+
const readable = await getAppLogs({
|
|
47
|
+
projectId,
|
|
48
|
+
args: ['--tail', '100', '--no-log-prefix']
|
|
49
|
+
});
|
|
50
|
+
readable.on('data', (chunk: Buffer) => {
|
|
51
|
+
if (!this.appLogStreams.has(projectId)) {
|
|
52
|
+
// why doesn't typescript know about this function?
|
|
53
|
+
// @ts-ignore
|
|
54
|
+
readable.destroy();
|
|
55
|
+
logger.info(`App log stream terminated for project ${projectId}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const logStr = chunk.toString();
|
|
59
|
+
const message: AppLogsMessage = {
|
|
60
|
+
messageType: keyMirrors.agentMessageType.app_logs,
|
|
61
|
+
appLogs: {
|
|
62
|
+
projectId,
|
|
63
|
+
logChunk: logStr
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
this.publisher.publishToClient(message);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
readable.on('error', (error) => {
|
|
70
|
+
logger.error(
|
|
71
|
+
`App log stream terminated for project ${projectId}: ${error}`
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
readable.on('finished', () => {
|
|
76
|
+
logger.info(`App logs finished piping for project ${projectId}`);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Use arrow function to allow `this` to be accessed
|
|
81
|
+
private getAppInstallStatusMessage = async (appReleaseHash: string) => {
|
|
82
|
+
const appInstallStatus =
|
|
83
|
+
this.appInstallStatusMgr.getStatusPacket(appReleaseHash);
|
|
84
|
+
const appInstallStatusMessage = {
|
|
85
|
+
messageType: keyMirrors.agentMessageType.app_install_status,
|
|
86
|
+
appInstallStatus
|
|
87
|
+
};
|
|
88
|
+
return appInstallStatusMessage;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
public async startPublishingLiveUpdates<T extends any[]>(
|
|
92
|
+
messageType: AgentMessageTypeValue,
|
|
93
|
+
getMessageData: (...args: T) => Promise<DeviceAgentMessagePayload>,
|
|
94
|
+
args: T
|
|
95
|
+
) {
|
|
96
|
+
logger.info(`Turned on live updates for ${messageType}`);
|
|
97
|
+
// eslint-disable-next-line no-constant-condition
|
|
98
|
+
while (true) {
|
|
99
|
+
try {
|
|
100
|
+
const message = await getMessageData(...args);
|
|
101
|
+
this.publisher.publishToClient(message);
|
|
102
|
+
} catch (e) {
|
|
103
|
+
logger.error(
|
|
104
|
+
`Error publishing live updates for ${messageType}: ${e.message}`
|
|
105
|
+
);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
if (!this.continuePublishing(messageType, ...args)) {
|
|
109
|
+
logger.info(`Turned off live updates for ${messageType}`);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
await sleep(this.getLiveUpdatesInterval(messageType));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private continuePublishing(flag: AgentMessageTypeValue, data?: any): boolean {
|
|
117
|
+
switch (flag) {
|
|
118
|
+
case keyMirrors.agentMessageType.device_stats:
|
|
119
|
+
case keyMirrors.agentMessageType.app_state:
|
|
120
|
+
return this.liveUpdatesAlive[flag];
|
|
121
|
+
case keyMirrors.agentMessageType.app_install_status: {
|
|
122
|
+
const appReleaseHash: string = data;
|
|
123
|
+
return this.appInstallStatuses.has(appReleaseHash);
|
|
124
|
+
}
|
|
125
|
+
default:
|
|
126
|
+
logger.error(`Unrecognized publishable flag ${flag}`);
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private getLiveUpdatesInterval(flag: AgentMessageTypeValue): number {
|
|
132
|
+
const exists = this.liveUpdatesSleepIntervals[flag];
|
|
133
|
+
if (exists) {
|
|
134
|
+
return exists;
|
|
135
|
+
}
|
|
136
|
+
logger.error(`Unrecognized live updates flag ${flag}`);
|
|
137
|
+
return -1;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private setLiveUpdates(toggles: LiveUpdatesToggles) {
|
|
141
|
+
if (toggles.deviceStats) {
|
|
142
|
+
this.liveUpdatesAlive.device_stats = toggles.deviceStats;
|
|
143
|
+
}
|
|
144
|
+
if (toggles.appState) {
|
|
145
|
+
this.liveUpdatesAlive.app_state = toggles.appState;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private restartLiveUpdatesTimeout() {
|
|
150
|
+
clearTimeout(this.liveUpdatesTimeout);
|
|
151
|
+
this.liveUpdatesTimeout = setTimeout(() => {
|
|
152
|
+
this.setLiveUpdates({
|
|
153
|
+
deviceStats: false,
|
|
154
|
+
appState: false
|
|
155
|
+
});
|
|
156
|
+
this.appLogStreams.clear();
|
|
157
|
+
// TODO: Make constant, not hard coded
|
|
158
|
+
}, 600000); // 10 min
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public async update({
|
|
162
|
+
deviceStats,
|
|
163
|
+
appState,
|
|
164
|
+
appLogs,
|
|
165
|
+
appInstallStatus
|
|
166
|
+
}: {
|
|
167
|
+
deviceStats?: boolean;
|
|
168
|
+
appState?: boolean;
|
|
169
|
+
appLogs?: {
|
|
170
|
+
projectId: string;
|
|
171
|
+
toggle: boolean;
|
|
172
|
+
};
|
|
173
|
+
appInstallStatus?: {
|
|
174
|
+
appReleaseHash: string;
|
|
175
|
+
toggle: boolean;
|
|
176
|
+
};
|
|
177
|
+
}) {
|
|
178
|
+
this.restartLiveUpdatesTimeout();
|
|
179
|
+
if (deviceStats !== undefined) {
|
|
180
|
+
this.liveUpdatesAlive.device_stats = deviceStats;
|
|
181
|
+
if (deviceStats) {
|
|
182
|
+
this.startPublishingLiveUpdates(
|
|
183
|
+
keyMirrors.agentMessageType.device_stats,
|
|
184
|
+
getDeviceStatsMessage,
|
|
185
|
+
[]
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (appState !== undefined) {
|
|
191
|
+
this.liveUpdatesAlive.app_state = appState;
|
|
192
|
+
if (appState) {
|
|
193
|
+
this.startPublishingLiveUpdates(
|
|
194
|
+
keyMirrors.agentMessageType.app_state,
|
|
195
|
+
getAppStateMessage,
|
|
196
|
+
[]
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (appLogs !== undefined) {
|
|
202
|
+
if (appLogs.toggle) {
|
|
203
|
+
this.startAppLogStream(appLogs.projectId);
|
|
204
|
+
} else {
|
|
205
|
+
this.appLogStreams.delete(appLogs.projectId);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (appInstallStatus !== undefined) {
|
|
210
|
+
if (appInstallStatus.toggle) {
|
|
211
|
+
this.liveUpdatesAlive.app_install_status = appInstallStatus.toggle;
|
|
212
|
+
this.appInstallStatuses.add(appInstallStatus.appReleaseHash);
|
|
213
|
+
this.startPublishingLiveUpdates(
|
|
214
|
+
keyMirrors.agentMessageType.app_install_status,
|
|
215
|
+
this.getAppInstallStatusMessage,
|
|
216
|
+
[appInstallStatus.appReleaseHash]
|
|
217
|
+
);
|
|
218
|
+
} else {
|
|
219
|
+
this.appInstallStatuses.delete(appInstallStatus.appReleaseHash);
|
|
220
|
+
}
|
|
221
|
+
if (this.appInstallStatuses.size === 0) {
|
|
222
|
+
this.liveUpdatesAlive.app_install_status = false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { AppStatePacket, keyMirrors } from '@alwaysai/device-agent-schemas';
|
|
2
|
+
import { getAppStatus } from '../application-control';
|
|
3
|
+
import {
|
|
4
|
+
getCpuUtil,
|
|
5
|
+
getDiskUtil,
|
|
6
|
+
getMemUtil
|
|
7
|
+
} from '../device-control/device-control';
|
|
8
|
+
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
9
|
+
|
|
10
|
+
export async function getAppStateMessage() {
|
|
11
|
+
const appStateMessage: AppStatePacket[] = [];
|
|
12
|
+
const apps = await AgentConfigFile().getReadyApps();
|
|
13
|
+
for (const app of apps) {
|
|
14
|
+
const projectId = app.projectId;
|
|
15
|
+
const status = await getAppStatus({ projectId });
|
|
16
|
+
appStateMessage.push(status);
|
|
17
|
+
}
|
|
18
|
+
const appStatePackage = {
|
|
19
|
+
messageType: keyMirrors.agentMessageType.app_state,
|
|
20
|
+
appState: appStateMessage
|
|
21
|
+
};
|
|
22
|
+
return appStatePackage;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function getDeviceStatsMessage() {
|
|
26
|
+
const cpuUsage = await getCpuUtil();
|
|
27
|
+
const diskUtil = await getDiskUtil();
|
|
28
|
+
const memUtil = await getMemUtil();
|
|
29
|
+
|
|
30
|
+
const deviceStatsMessage = {
|
|
31
|
+
messageType: keyMirrors.agentMessageType.device_stats,
|
|
32
|
+
deviceStats: {
|
|
33
|
+
cpuUsage,
|
|
34
|
+
diskUtil,
|
|
35
|
+
usedMemoryPercentage: memUtil
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
return deviceStatsMessage;
|
|
39
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeviceAgentMessage,
|
|
3
|
+
DeviceAgentMessagePayload,
|
|
4
|
+
getClientTopic,
|
|
5
|
+
getCloudTopic
|
|
6
|
+
} from '@alwaysai/device-agent-schemas';
|
|
7
|
+
import { logger } from '../util/logger';
|
|
8
|
+
|
|
9
|
+
export class Publisher {
|
|
10
|
+
private client: any;
|
|
11
|
+
private clientId: string;
|
|
12
|
+
private readonly toClientTopic: string;
|
|
13
|
+
private readonly toCloudTopic: string;
|
|
14
|
+
|
|
15
|
+
constructor(client: any, clientId: string) {
|
|
16
|
+
this.client = client;
|
|
17
|
+
this.clientId = clientId;
|
|
18
|
+
this.toClientTopic = getClientTopic(this.clientId);
|
|
19
|
+
this.toCloudTopic = getCloudTopic(this.clientId);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private buildMessagePacket(
|
|
23
|
+
topic: string,
|
|
24
|
+
payload: DeviceAgentMessagePayload
|
|
25
|
+
): DeviceAgentMessage {
|
|
26
|
+
const packet = {
|
|
27
|
+
timestamp: new Date().toUTCString(),
|
|
28
|
+
deviceId: this.clientId,
|
|
29
|
+
topic,
|
|
30
|
+
payload
|
|
31
|
+
};
|
|
32
|
+
return packet;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public publish(topic: string, payload: string) {
|
|
36
|
+
// TODO: topic validation
|
|
37
|
+
this.client.publish(topic, payload, (err: any) => {
|
|
38
|
+
if (err) {
|
|
39
|
+
logger.error(
|
|
40
|
+
`Error publishing message: \nTopic: ${topic}\nMessage: ${payload}\nError: ${err}`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public publishDeviceAgentPayload(
|
|
47
|
+
topic: string,
|
|
48
|
+
payload: DeviceAgentMessagePayload
|
|
49
|
+
) {
|
|
50
|
+
const packet = this.buildMessagePacket(topic, payload);
|
|
51
|
+
const packetStr = JSON.stringify(packet);
|
|
52
|
+
logger.debug(
|
|
53
|
+
`Publishing message:\n${JSON.stringify({ topic, packet }, null, 2)}`
|
|
54
|
+
);
|
|
55
|
+
this.publish(topic, packetStr);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public publishToClient(payload: DeviceAgentMessagePayload) {
|
|
59
|
+
this.publishDeviceAgentPayload(this.toClientTopic, payload);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public publishToCloud(payload: DeviceAgentMessagePayload) {
|
|
63
|
+
this.publishDeviceAgentPayload(this.toCloudTopic, payload);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppConfig,
|
|
3
|
+
validateAppConfig
|
|
4
|
+
} from '@alwaysai/app-configuration-schemas';
|
|
5
|
+
import { readAppCfgFile } from '../application-control';
|
|
6
|
+
import { logger } from '../util/logger';
|
|
7
|
+
import { Publisher } from './publisher';
|
|
8
|
+
import { AppConfigModels, getAppCfgModelsDiff } from './shadow';
|
|
9
|
+
|
|
10
|
+
interface ShadowTopics {
|
|
11
|
+
projects: {
|
|
12
|
+
update: string;
|
|
13
|
+
get: string;
|
|
14
|
+
updateDelta: string;
|
|
15
|
+
getAccepted: string;
|
|
16
|
+
delete: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type AppConfigUpdate = {
|
|
21
|
+
projectId: string;
|
|
22
|
+
newAppCfg: AppConfig;
|
|
23
|
+
updatedModels?: AppConfigModels;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export class ShadowHandler {
|
|
27
|
+
private clientId: string;
|
|
28
|
+
private publisher: Publisher;
|
|
29
|
+
public readonly shadowPrefix: string;
|
|
30
|
+
public readonly shadowTopics: ShadowTopics;
|
|
31
|
+
|
|
32
|
+
constructor(clientId: string, publisher: Publisher) {
|
|
33
|
+
this.clientId = clientId;
|
|
34
|
+
this.publisher = publisher;
|
|
35
|
+
this.shadowPrefix = `$aws/things/${this.clientId}/shadow/name/`;
|
|
36
|
+
this.shadowTopics = {
|
|
37
|
+
projects: {
|
|
38
|
+
update: `${this.shadowPrefix}projects/update`,
|
|
39
|
+
get: `${this.shadowPrefix}projects/get`,
|
|
40
|
+
updateDelta: `${this.shadowPrefix}projects/update/delta`,
|
|
41
|
+
getAccepted: `${this.shadowPrefix}projects/get/accepted`,
|
|
42
|
+
delete: `${this.shadowPrefix}projects/delete`
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private async handleNamedShadowUpdate({
|
|
48
|
+
delta
|
|
49
|
+
}: {
|
|
50
|
+
delta: any;
|
|
51
|
+
}): Promise<AppConfigUpdate[]> {
|
|
52
|
+
const appConfigUpdates: AppConfigUpdate[] = [];
|
|
53
|
+
|
|
54
|
+
const deltaKeys = Object.keys(delta);
|
|
55
|
+
|
|
56
|
+
for (const projectId of deltaKeys) {
|
|
57
|
+
const projectShadow = delta[projectId];
|
|
58
|
+
|
|
59
|
+
if (projectShadow.appConfig) {
|
|
60
|
+
const newAppCfg = JSON.parse(projectShadow.appConfig);
|
|
61
|
+
if (!validateAppConfig(newAppCfg)) {
|
|
62
|
+
logger.error(
|
|
63
|
+
`Received invalid app config for ${projectId}!/n${JSON.stringify(
|
|
64
|
+
validateAppConfig.errors,
|
|
65
|
+
null,
|
|
66
|
+
2
|
|
67
|
+
)}`
|
|
68
|
+
);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const { updatedModels } = await getAppCfgModelsDiff({
|
|
72
|
+
newAppCfg,
|
|
73
|
+
projectId
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (updatedModels && Object.keys(updatedModels).length) {
|
|
77
|
+
appConfigUpdates.push({ projectId, newAppCfg, updatedModels });
|
|
78
|
+
} else {
|
|
79
|
+
appConfigUpdates.push({ projectId, newAppCfg });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return appConfigUpdates;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Public interface
|
|
87
|
+
|
|
88
|
+
public async handleShadowTopic({
|
|
89
|
+
topic,
|
|
90
|
+
payload
|
|
91
|
+
}: {
|
|
92
|
+
topic: string;
|
|
93
|
+
payload: any;
|
|
94
|
+
}): Promise<AppConfigUpdate[]> {
|
|
95
|
+
// TODO: make use a function like the other topic getters
|
|
96
|
+
const shadowName = topic.split('/')[5];
|
|
97
|
+
switch (topic) {
|
|
98
|
+
case this.shadowTopics.projects.updateDelta:
|
|
99
|
+
return await this.handleNamedShadowUpdate({ delta: payload });
|
|
100
|
+
case this.shadowTopics.projects.getAccepted:
|
|
101
|
+
if (payload['delta']) {
|
|
102
|
+
return await this.handleNamedShadowUpdate({
|
|
103
|
+
delta: payload['delta']
|
|
104
|
+
});
|
|
105
|
+
} else {
|
|
106
|
+
logger.info(`No delta updates in named shadow '${shadowName}'`);
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
default:
|
|
110
|
+
logger.info(
|
|
111
|
+
`Ignoring shadow message: ${JSON.stringify(
|
|
112
|
+
{ topic, payload },
|
|
113
|
+
null,
|
|
114
|
+
2
|
|
115
|
+
)}`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
public async publishAppState(projectId: string) {
|
|
122
|
+
const appCfg = await readAppCfgFile({ projectId });
|
|
123
|
+
const packet = {
|
|
124
|
+
state: {
|
|
125
|
+
reported: {
|
|
126
|
+
[projectId]: { appConfig: JSON.stringify(appCfg) }
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
clientToken: this.clientId
|
|
130
|
+
};
|
|
131
|
+
this.publisher.publish(
|
|
132
|
+
this.shadowTopics.projects.update,
|
|
133
|
+
JSON.stringify(packet)
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public getShadowUpdates() {
|
|
138
|
+
this.publisher.publish(
|
|
139
|
+
this.shadowTopics.projects.get,
|
|
140
|
+
JSON.stringify({
|
|
141
|
+
clientToken: this.clientId
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public deleteProjectShadow() {
|
|
147
|
+
this.publisher.publish(
|
|
148
|
+
`${this.shadowTopics.projects.delete}`,
|
|
149
|
+
JSON.stringify({
|
|
150
|
+
clientToken: this.clientId
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { logger } from '../util/logger';
|
|
2
|
+
import { readAppCfgFile } from '../application-control';
|
|
3
|
+
import { AppConfig } from '@alwaysai/app-configuration-schemas';
|
|
4
|
+
|
|
5
|
+
export type AppConfigModels = {
|
|
6
|
+
[modelId: string]: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type AppConfigScripts = {
|
|
10
|
+
start: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const getAppCfgModelsDiff = async ({
|
|
14
|
+
newAppCfg,
|
|
15
|
+
projectId
|
|
16
|
+
}: {
|
|
17
|
+
newAppCfg: AppConfig;
|
|
18
|
+
projectId: string;
|
|
19
|
+
}) => {
|
|
20
|
+
const updatedModels: AppConfigModels = {};
|
|
21
|
+
const untouchedModels: AppConfigModels = {};
|
|
22
|
+
const newScripts: AppConfigScripts = { start: '' };
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const shadowScripts = newAppCfg.scripts;
|
|
26
|
+
const shadowModels = newAppCfg.models;
|
|
27
|
+
|
|
28
|
+
const localAppCfg = await readAppCfgFile({ projectId });
|
|
29
|
+
const localModels = localAppCfg.models;
|
|
30
|
+
|
|
31
|
+
Object.keys(shadowModels).forEach((modelId: string) => {
|
|
32
|
+
const localVersion = localModels[modelId];
|
|
33
|
+
const shadowVersion = shadowModels[modelId];
|
|
34
|
+
if (!localVersion || localVersion !== shadowVersion) {
|
|
35
|
+
updatedModels[modelId] = shadowVersion;
|
|
36
|
+
} else {
|
|
37
|
+
untouchedModels[modelId] = localVersion;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
shadowScripts &&
|
|
42
|
+
Object.keys(shadowScripts).forEach((scriptName: string) => {
|
|
43
|
+
newScripts[scriptName] = shadowScripts[scriptName];
|
|
44
|
+
});
|
|
45
|
+
} catch (e) {
|
|
46
|
+
logger.error('Error parsing app config update: ', e);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { scripts: newScripts, updatedModels, untouchedModels };
|
|
50
|
+
};
|
package/src/docker/docker-cmd.ts
CHANGED
|
@@ -6,7 +6,7 @@ export async function runDockerLogin(props: { token: string }) {
|
|
|
6
6
|
const server = '994534263224.dkr.ecr.us-west-2.amazonaws.com';
|
|
7
7
|
const output = await spawner.run({
|
|
8
8
|
exe: 'docker',
|
|
9
|
-
args: ['login', '--username', 'AWS', '--password', token, server]
|
|
9
|
+
args: ['login', '--username', 'AWS', '--password', token, server]
|
|
10
10
|
});
|
|
11
11
|
return output;
|
|
12
12
|
}
|