@alwaysai/device-agent 0.0.12 → 0.0.14
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 +11 -5
- 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 +5 -5
- package/lib/application-control/index.d.ts.map +1 -1
- package/lib/application-control/index.js +4 -6
- package/lib/application-control/index.js.map +1 -1
- package/lib/application-control/install.d.ts +1 -1
- package/lib/application-control/install.d.ts.map +1 -1
- package/lib/application-control/install.js +58 -57
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/models.d.ts +7 -5
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +78 -57
- package/lib/application-control/models.js.map +1 -1
- package/lib/application-control/status.d.ts +0 -6
- package/lib/application-control/status.d.ts.map +1 -1
- package/lib/application-control/status.js +21 -33
- package/lib/application-control/status.js.map +1 -1
- package/lib/application-control/utils.d.ts +3 -2
- package/lib/application-control/utils.d.ts.map +1 -1
- package/lib/application-control/utils.js +54 -34
- 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/cmd-status.d.ts +16 -0
- package/lib/cloud-connection/cmd-status.d.ts.map +1 -0
- package/lib/cloud-connection/cmd-status.js +49 -0
- package/lib/cloud-connection/cmd-status.js.map +1 -0
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +21 -34
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +211 -387
- 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/passthrough-handler.d.ts +11 -0
- package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -0
- package/lib/cloud-connection/passthrough-handler.js +59 -0
- package/lib/cloud-connection/passthrough-handler.js.map +1 -0
- package/lib/cloud-connection/publisher.d.ts +15 -0
- package/lib/cloud-connection/publisher.d.ts.map +1 -0
- package/lib/cloud-connection/publisher.js +58 -0
- package/lib/cloud-connection/publisher.js.map +1 -0
- package/lib/cloud-connection/shadow-handler.d.ts +33 -0
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -0
- package/lib/cloud-connection/shadow-handler.js +108 -0
- package/lib/cloud-connection/shadow-handler.js.map +1 -0
- package/lib/cloud-connection/shadow-handler.test.d.ts +2 -0
- package/lib/cloud-connection/shadow-handler.test.d.ts.map +1 -0
- package/lib/cloud-connection/shadow-handler.test.js +321 -0
- package/lib/cloud-connection/shadow-handler.test.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 +1 -0
- package/lib/environment.d.ts.map +1 -1
- package/lib/environment.js +2 -1
- package/lib/environment.js.map +1 -1
- package/lib/infrastructure/agent-config.d.ts +15 -58
- 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 +25 -23
- 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 +27 -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/local-connection/rabbitmq-connection.d.ts +4 -0
- package/lib/local-connection/rabbitmq-connection.d.ts.map +1 -0
- package/lib/local-connection/rabbitmq-connection.js +58 -0
- package/lib/local-connection/rabbitmq-connection.js.map +1 -0
- 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 +2 -1
- package/lib/subcommands/app/app.d.ts.map +1 -1
- package/lib/subcommands/app/app.js +112 -77
- 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 +1 -1
- package/lib/subcommands/device/device.d.ts.map +1 -1
- package/lib/subcommands/device/device.js +44 -33
- 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.d.ts +0 -1
- package/lib/subcommands/index.d.ts.map +1 -1
- package/lib/subcommands/index.js +1 -1
- package/lib/subcommands/login.d.ts +0 -1
- package/lib/subcommands/login.d.ts.map +1 -1
- package/lib/subcommands/login.js +6 -14
- 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/fetch-with-timeout.d.ts +4 -0
- package/lib/util/fetch-with-timeout.d.ts.map +1 -0
- package/lib/util/fetch-with-timeout.js +15 -0
- package/lib/util/fetch-with-timeout.js.map +1 -0
- 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/require-logged-in-and-paid-plan.d.ts +2 -0
- package/lib/util/require-logged-in-and-paid-plan.d.ts.map +1 -0
- package/lib/util/require-logged-in-and-paid-plan.js +18 -0
- package/lib/util/require-logged-in-and-paid-plan.js.map +1 -0
- 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/lib/util/timer.d.ts +2 -0
- package/lib/util/timer.d.ts.map +1 -0
- package/lib/util/timer.js +6 -0
- package/lib/util/timer.js.map +1 -0
- package/package.json +32 -35
- package/readme.md +100 -89
- package/src/application-control/backup.ts +11 -6
- package/src/application-control/config.ts +75 -13
- package/src/application-control/environment-variables.ts +3 -3
- package/src/application-control/index.ts +18 -11
- package/src/application-control/install.ts +82 -78
- package/src/application-control/models.ts +104 -72
- package/src/application-control/status.ts +29 -40
- package/src/application-control/utils.ts +66 -38
- package/src/cloud-connection/app-install-status.ts +62 -0
- package/src/cloud-connection/bootstrap-provision.ts +40 -0
- package/src/cloud-connection/cmd-status.ts +52 -0
- package/src/cloud-connection/device-agent-cloud-connection.ts +302 -526
- 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/passthrough-handler.ts +67 -0
- package/src/cloud-connection/publisher.ts +86 -0
- package/src/cloud-connection/shadow-handler.test.ts +361 -0
- package/src/cloud-connection/shadow-handler.ts +175 -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 +11 -3
- package/src/infrastructure/agent-config.test.ts +33 -29
- package/src/infrastructure/agent-config.ts +57 -22
- package/src/infrastructure/system-id.ts +18 -0
- package/src/infrastructure/tokens-and-device-cfg.ts +34 -0
- package/src/infrastructure/urls.ts +4 -2
- package/src/local-connection/rabbitmq-connection.ts +53 -0
- package/src/root.ts +2 -8
- package/src/subcommands/app/app.ts +119 -83
- package/src/subcommands/app/index.ts +3 -3
- package/src/subcommands/device/clean.ts +26 -0
- package/src/subcommands/device/device.ts +67 -54
- 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 +6 -14
- package/src/util/clean-certs.ts +12 -0
- package/src/util/directories.ts +68 -52
- package/src/util/fetch-with-timeout.ts +18 -0
- 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/require-logged-in-and-paid-plan.ts +16 -0
- package/src/util/run-in-dir.ts +2 -1
- package/src/util/timer.ts +1 -0
- 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
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { AppConfig } from '@alwaysai/app-configuration-schemas';
|
|
2
|
+
import { readAppCfgFile } from '../application-control';
|
|
3
|
+
import { Publisher } from './publisher';
|
|
4
|
+
import { ShadowHandler } from './shadow-handler';
|
|
5
|
+
|
|
6
|
+
jest.mock('../application-control');
|
|
7
|
+
jest.mock('./publisher');
|
|
8
|
+
const mockClient = jest.fn();
|
|
9
|
+
const clientId = 'test-client';
|
|
10
|
+
const projectId1 = 'test-project';
|
|
11
|
+
const projectId2 = 'test-project-2';
|
|
12
|
+
|
|
13
|
+
describe('Test Shadow Handler', () => {
|
|
14
|
+
let publisher: Publisher;
|
|
15
|
+
let shadowHandler: ShadowHandler;
|
|
16
|
+
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
publisher = new Publisher(mockClient, clientId);
|
|
19
|
+
shadowHandler = new ShadowHandler(clientId, publisher);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test.skip('reject buffer payload', async () => {
|
|
23
|
+
//FIXME: Invalid input is silently ignored, need input validation
|
|
24
|
+
expect(() => {
|
|
25
|
+
shadowHandler.handleShadowTopic({
|
|
26
|
+
topic: shadowHandler.shadowTopics.projects.updateDelta,
|
|
27
|
+
payload: Buffer.from('test-payload')
|
|
28
|
+
});
|
|
29
|
+
}).toThrow(Error);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('ignore message from self', async () => {
|
|
33
|
+
const ogAppCfg1: AppConfig = {
|
|
34
|
+
scripts: {
|
|
35
|
+
start: 'python app.py'
|
|
36
|
+
},
|
|
37
|
+
models: {
|
|
38
|
+
'alwaysai/mobilenet_ssd': 2
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
jest.mocked(readAppCfgFile).mockResolvedValue(ogAppCfg1);
|
|
42
|
+
|
|
43
|
+
const appCfg1: AppConfig = {
|
|
44
|
+
scripts: {
|
|
45
|
+
start: 'python app.py'
|
|
46
|
+
},
|
|
47
|
+
models: {
|
|
48
|
+
'alwaysai/yolo_v3': 4
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const payload = {
|
|
52
|
+
[projectId1]: {
|
|
53
|
+
appConfig: JSON.stringify(appCfg1)
|
|
54
|
+
},
|
|
55
|
+
clientToken: clientId
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const appCfgUpdates = await shadowHandler.handleShadowTopic({
|
|
59
|
+
topic: shadowHandler.shadowTopics.projects.updateDelta,
|
|
60
|
+
payload
|
|
61
|
+
});
|
|
62
|
+
expect(appCfgUpdates.length).toBe(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('handle shadow get response with updated models', async () => {
|
|
66
|
+
const ogAppCfg1: AppConfig = {
|
|
67
|
+
scripts: {
|
|
68
|
+
start: 'python app.py'
|
|
69
|
+
},
|
|
70
|
+
models: {
|
|
71
|
+
'alwaysai/mobilenet_ssd': 3
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
jest.mocked(readAppCfgFile).mockResolvedValue(ogAppCfg1);
|
|
76
|
+
const appCfg1: AppConfig = {
|
|
77
|
+
scripts: {
|
|
78
|
+
start: 'python app.py'
|
|
79
|
+
},
|
|
80
|
+
models: {
|
|
81
|
+
'alwaysai/yolo_v3': 4
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const payload = {
|
|
86
|
+
delta: {
|
|
87
|
+
[projectId1]: {
|
|
88
|
+
appConfig: JSON.stringify(appCfg1)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const appCfgUpdates = await shadowHandler.handleShadowTopic({
|
|
94
|
+
topic: shadowHandler.shadowTopics.projects.getAccepted,
|
|
95
|
+
payload
|
|
96
|
+
});
|
|
97
|
+
expect(appCfgUpdates.length).toBe(1);
|
|
98
|
+
expect(appCfgUpdates[0]).toEqual({
|
|
99
|
+
projectId: projectId1,
|
|
100
|
+
newAppCfg: appCfg1,
|
|
101
|
+
updatedModels: {
|
|
102
|
+
'alwaysai/yolo_v3': 4
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('handle shadow delta without app config', async () => {
|
|
108
|
+
const ogAppCfg1: AppConfig = {
|
|
109
|
+
scripts: {
|
|
110
|
+
start: 'python app.py'
|
|
111
|
+
},
|
|
112
|
+
models: {
|
|
113
|
+
'alwaysai/mobilenet_ssd': 2
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
jest.mocked(readAppCfgFile).mockResolvedValue(ogAppCfg1);
|
|
117
|
+
|
|
118
|
+
const payload = {
|
|
119
|
+
[projectId1]: {}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const appCfgUpdates = await shadowHandler.handleShadowTopic({
|
|
123
|
+
topic: shadowHandler.shadowTopics.projects.updateDelta,
|
|
124
|
+
payload
|
|
125
|
+
});
|
|
126
|
+
expect(appCfgUpdates.length).toBe(0);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('handle shadow delta with updated models', async () => {
|
|
130
|
+
const ogAppCfg1: AppConfig = {
|
|
131
|
+
scripts: {
|
|
132
|
+
start: 'python app.py'
|
|
133
|
+
},
|
|
134
|
+
models: {
|
|
135
|
+
'alwaysai/mobilenet_ssd': 2
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
jest.mocked(readAppCfgFile).mockResolvedValue(ogAppCfg1);
|
|
140
|
+
const appCfg1: AppConfig = {
|
|
141
|
+
scripts: {
|
|
142
|
+
start: 'python app.py'
|
|
143
|
+
},
|
|
144
|
+
models: {
|
|
145
|
+
'alwaysai/mobilenet_ssd': 3,
|
|
146
|
+
'alwaysai/yolo_v4': 5
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const payload = {
|
|
151
|
+
[projectId1]: {
|
|
152
|
+
appConfig: JSON.stringify(appCfg1)
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const appCfgUpdates = await shadowHandler.handleShadowTopic({
|
|
157
|
+
topic: shadowHandler.shadowTopics.projects.updateDelta,
|
|
158
|
+
payload
|
|
159
|
+
});
|
|
160
|
+
expect(appCfgUpdates.length).toBe(1);
|
|
161
|
+
expect(appCfgUpdates[0]).toEqual({
|
|
162
|
+
projectId: projectId1,
|
|
163
|
+
newAppCfg: appCfg1,
|
|
164
|
+
updatedModels: {
|
|
165
|
+
'alwaysai/mobilenet_ssd': 3,
|
|
166
|
+
'alwaysai/yolo_v4': 5
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('handle shadow delta with updated models for two apps', async () => {
|
|
172
|
+
const ogAppCfg1: AppConfig = {
|
|
173
|
+
scripts: {
|
|
174
|
+
start: 'python app.py'
|
|
175
|
+
},
|
|
176
|
+
models: {
|
|
177
|
+
'alwaysai/mobilenet_ssd': 2
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
jest.mocked(readAppCfgFile).mockResolvedValueOnce(ogAppCfg1);
|
|
181
|
+
const ogAppCfg2: AppConfig = {
|
|
182
|
+
scripts: {
|
|
183
|
+
start: 'python app.py'
|
|
184
|
+
},
|
|
185
|
+
models: {
|
|
186
|
+
'alwaysai/yolo_v4': 5
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
jest.mocked(readAppCfgFile).mockResolvedValueOnce(ogAppCfg2);
|
|
190
|
+
|
|
191
|
+
const appCfg1: AppConfig = {
|
|
192
|
+
scripts: {
|
|
193
|
+
start: 'python app.py'
|
|
194
|
+
},
|
|
195
|
+
models: {
|
|
196
|
+
'alwaysai/mobilenet_ssd': 3,
|
|
197
|
+
'alwaysai/yolo_v4': 5
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
const appCfg2: AppConfig = {
|
|
201
|
+
scripts: {
|
|
202
|
+
start: 'python app.py'
|
|
203
|
+
},
|
|
204
|
+
models: {
|
|
205
|
+
'alwaysai/yolo_v4': 5,
|
|
206
|
+
'alwaysai/human_pose': 7
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
const payload = {
|
|
210
|
+
[projectId1]: {
|
|
211
|
+
appConfig: JSON.stringify(appCfg1)
|
|
212
|
+
},
|
|
213
|
+
[projectId2]: {
|
|
214
|
+
appConfig: JSON.stringify(appCfg2)
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const appCfgUpdates = await shadowHandler.handleShadowTopic({
|
|
219
|
+
topic: shadowHandler.shadowTopics.projects.updateDelta,
|
|
220
|
+
payload
|
|
221
|
+
});
|
|
222
|
+
expect(appCfgUpdates.length).toBe(2);
|
|
223
|
+
expect(appCfgUpdates[0]).toEqual({
|
|
224
|
+
projectId: projectId1,
|
|
225
|
+
newAppCfg: appCfg1,
|
|
226
|
+
updatedModels: {
|
|
227
|
+
'alwaysai/mobilenet_ssd': 3,
|
|
228
|
+
'alwaysai/yolo_v4': 5
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
expect(appCfgUpdates[1]).toEqual({
|
|
232
|
+
projectId: projectId2,
|
|
233
|
+
newAppCfg: appCfg2,
|
|
234
|
+
updatedModels: {
|
|
235
|
+
'alwaysai/human_pose': 7
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('handle shadow delta with updated analytics', async () => {
|
|
241
|
+
const ogAppCfg1: AppConfig = {
|
|
242
|
+
scripts: {
|
|
243
|
+
start: 'python app.py'
|
|
244
|
+
},
|
|
245
|
+
models: {}
|
|
246
|
+
};
|
|
247
|
+
jest.mocked(readAppCfgFile).mockResolvedValue(ogAppCfg1);
|
|
248
|
+
|
|
249
|
+
const appCfg1: AppConfig = {
|
|
250
|
+
scripts: {
|
|
251
|
+
start: 'python app.py'
|
|
252
|
+
},
|
|
253
|
+
models: {},
|
|
254
|
+
analytics: {
|
|
255
|
+
enable_cloud_publish: true
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const payload = {
|
|
260
|
+
[projectId1]: {
|
|
261
|
+
appConfig: JSON.stringify(appCfg1)
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const appCfgUpdates = await shadowHandler.handleShadowTopic({
|
|
266
|
+
topic: shadowHandler.shadowTopics.projects.updateDelta,
|
|
267
|
+
payload
|
|
268
|
+
});
|
|
269
|
+
expect(appCfgUpdates.length).toBe(1);
|
|
270
|
+
expect(appCfgUpdates[0]).toEqual({
|
|
271
|
+
projectId: projectId1,
|
|
272
|
+
newAppCfg: appCfg1
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test('handle shadow delta with invalid app config', async () => {
|
|
277
|
+
const ogAppCfg1: AppConfig = {
|
|
278
|
+
scripts: {
|
|
279
|
+
start: 'python app.py'
|
|
280
|
+
},
|
|
281
|
+
models: {
|
|
282
|
+
'alwaysai/mobilenet_ssd': 2
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
jest.mocked(readAppCfgFile).mockResolvedValue(ogAppCfg1);
|
|
287
|
+
const appCfg1 = {
|
|
288
|
+
scripts: {
|
|
289
|
+
start: 'python app.py'
|
|
290
|
+
},
|
|
291
|
+
models: {
|
|
292
|
+
'alwaysai/mobilenet_ssd': '3',
|
|
293
|
+
'alwaysai/yolo_v4': '5'
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const payload = {
|
|
298
|
+
[projectId1]: {
|
|
299
|
+
appConfig: JSON.stringify(appCfg1)
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const appCfgUpdates = await shadowHandler.handleShadowTopic({
|
|
304
|
+
topic: shadowHandler.shadowTopics.projects.updateDelta,
|
|
305
|
+
payload
|
|
306
|
+
});
|
|
307
|
+
expect(appCfgUpdates.length).toBe(0);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test.skip('publish app state', async () => {
|
|
311
|
+
// FIXME: For some reason publisher is not being called...
|
|
312
|
+
const testAppCfg: AppConfig = {
|
|
313
|
+
scripts: {
|
|
314
|
+
start: ''
|
|
315
|
+
},
|
|
316
|
+
models: {}
|
|
317
|
+
};
|
|
318
|
+
jest.mocked(readAppCfgFile).mockResolvedValue(testAppCfg);
|
|
319
|
+
|
|
320
|
+
shadowHandler.publishAppState(projectId1);
|
|
321
|
+
expect(jest.mocked(readAppCfgFile)).toBeCalledWith({ projectId1 });
|
|
322
|
+
const packet = {
|
|
323
|
+
state: {
|
|
324
|
+
reported: {
|
|
325
|
+
[projectId1]: { appConfig: JSON.stringify(testAppCfg) }
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
clientToken: clientId
|
|
329
|
+
};
|
|
330
|
+
expect(jest.mocked(publisher.publish)).toBeCalledWith(
|
|
331
|
+
shadowHandler.shadowTopics.projects.update,
|
|
332
|
+
JSON.stringify(packet)
|
|
333
|
+
);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test('get shadow updates', async () => {
|
|
337
|
+
shadowHandler.getShadowUpdates();
|
|
338
|
+
expect(jest.mocked(publisher.publish)).toBeCalledWith(
|
|
339
|
+
shadowHandler.shadowTopics.projects.get,
|
|
340
|
+
JSON.stringify({
|
|
341
|
+
clientToken: clientId
|
|
342
|
+
})
|
|
343
|
+
);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test('delete project shadow', async () => {
|
|
347
|
+
shadowHandler.deleteProjectShadow(projectId1);
|
|
348
|
+
const packet = {
|
|
349
|
+
state: {
|
|
350
|
+
reported: {
|
|
351
|
+
[projectId1]: null
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
clientToken: clientId
|
|
355
|
+
};
|
|
356
|
+
expect(jest.mocked(publisher.publish)).toBeCalledWith(
|
|
357
|
+
shadowHandler.shadowTopics.projects.update,
|
|
358
|
+
JSON.stringify(packet)
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
@@ -0,0 +1,175 @@
|
|
|
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
|
+
export 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
|
+
// FIXME: Raise an exception to be handled at higher layer
|
|
63
|
+
logger.error(
|
|
64
|
+
`Received invalid app config for ${projectId}!\n${JSON.stringify(
|
|
65
|
+
validateAppConfig.errors,
|
|
66
|
+
null,
|
|
67
|
+
2
|
|
68
|
+
)}`
|
|
69
|
+
);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const { updatedModels } = await getAppCfgModelsDiff({
|
|
73
|
+
newAppCfg,
|
|
74
|
+
projectId
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (updatedModels && Object.keys(updatedModels).length) {
|
|
78
|
+
appConfigUpdates.push({ projectId, newAppCfg, updatedModels });
|
|
79
|
+
} else {
|
|
80
|
+
appConfigUpdates.push({ projectId, newAppCfg });
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
logger.warn(
|
|
84
|
+
`Ignoring shadow update for ${projectId} due to no app config`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return appConfigUpdates;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Public interface
|
|
92
|
+
|
|
93
|
+
public async handleShadowTopic({
|
|
94
|
+
topic,
|
|
95
|
+
payload
|
|
96
|
+
}: {
|
|
97
|
+
topic: string;
|
|
98
|
+
payload: any;
|
|
99
|
+
}): Promise<AppConfigUpdate[]> {
|
|
100
|
+
// TODO: make use a function like the other topic getters
|
|
101
|
+
const shadowName = topic.split('/')[5];
|
|
102
|
+
switch (topic) {
|
|
103
|
+
case this.shadowTopics.projects.updateDelta:
|
|
104
|
+
if (payload.clientToken === this.clientId) {
|
|
105
|
+
logger.debug(
|
|
106
|
+
`Ignoring message sent from self: ${JSON.stringify(
|
|
107
|
+
{ topic, payload },
|
|
108
|
+
null,
|
|
109
|
+
2
|
|
110
|
+
)}`
|
|
111
|
+
);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
return await this.handleNamedShadowUpdate({ delta: payload });
|
|
115
|
+
case this.shadowTopics.projects.getAccepted:
|
|
116
|
+
if (payload['delta']) {
|
|
117
|
+
return await this.handleNamedShadowUpdate({
|
|
118
|
+
delta: payload['delta']
|
|
119
|
+
});
|
|
120
|
+
} else {
|
|
121
|
+
logger.info(`No delta updates in named shadow '${shadowName}'`);
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
default:
|
|
125
|
+
logger.info(
|
|
126
|
+
`Ignoring shadow message: ${JSON.stringify(
|
|
127
|
+
{ topic, payload },
|
|
128
|
+
null,
|
|
129
|
+
2
|
|
130
|
+
)}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public async publishAppState(projectId: string) {
|
|
137
|
+
const appCfg = await readAppCfgFile({ projectId });
|
|
138
|
+
const packet = {
|
|
139
|
+
state: {
|
|
140
|
+
reported: {
|
|
141
|
+
[projectId]: { appConfig: JSON.stringify(appCfg) }
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
clientToken: this.clientId
|
|
145
|
+
};
|
|
146
|
+
this.publisher.publish(
|
|
147
|
+
this.shadowTopics.projects.update,
|
|
148
|
+
JSON.stringify(packet)
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public getShadowUpdates() {
|
|
153
|
+
this.publisher.publish(
|
|
154
|
+
this.shadowTopics.projects.get,
|
|
155
|
+
JSON.stringify({
|
|
156
|
+
clientToken: this.clientId
|
|
157
|
+
})
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public deleteProjectShadow(projectId: string) {
|
|
162
|
+
const packet = {
|
|
163
|
+
state: {
|
|
164
|
+
reported: {
|
|
165
|
+
[projectId]: null
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
clientToken: this.clientId
|
|
169
|
+
};
|
|
170
|
+
this.publisher.publish(
|
|
171
|
+
this.shadowTopics.projects.update,
|
|
172
|
+
JSON.stringify(packet)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -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
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { JsSpawner } from 'alwaysai/lib/util';
|
|
2
2
|
|
|
3
|
-
export async function runDockerComposeCmd(props: {
|
|
3
|
+
export async function runDockerComposeCmd(props: {
|
|
4
|
+
args: string[];
|
|
5
|
+
dir: string;
|
|
6
|
+
}) {
|
|
4
7
|
const { args, dir } = props;
|
|
5
8
|
const spawner = JsSpawner();
|
|
6
9
|
const output = await spawner.run({
|
|
7
10
|
exe: 'docker-compose',
|
|
8
11
|
args,
|
|
9
|
-
cwd: dir
|
|
12
|
+
cwd: dir
|
|
10
13
|
});
|
|
11
14
|
return output;
|
|
12
15
|
}
|
package/src/endpoints.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { getSystemId } from
|
|
1
|
+
import { getSystemId } from './infrastructure/system-id';
|
|
2
2
|
|
|
3
3
|
export const getSecondLevelDomain = () => {
|
|
4
|
-
let domain =
|
|
4
|
+
let domain = '';
|
|
5
5
|
switch (getSystemId()) {
|
|
6
|
-
case
|
|
7
|
-
domain =
|
|
6
|
+
case 'development':
|
|
7
|
+
domain = 'a6i0.net';
|
|
8
8
|
break;
|
|
9
|
-
case
|
|
10
|
-
domain =
|
|
9
|
+
case 'qa':
|
|
10
|
+
domain = 'a6i1.net';
|
|
11
11
|
break;
|
|
12
|
-
case
|
|
13
|
-
domain =
|
|
12
|
+
case 'production':
|
|
13
|
+
domain = 'alwaysai.co';
|
|
14
14
|
break;
|
|
15
15
|
default:
|
|
16
|
-
domain =
|
|
16
|
+
domain = 'alwaysai.co';
|
|
17
17
|
break;
|
|
18
18
|
}
|
|
19
19
|
return domain;
|
package/src/environment.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { platform } from 'os';
|
|
2
2
|
|
|
3
|
-
export const ALWAYSAI_OS_PLATFORM = parseOsPlatform(
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export const ALWAYSAI_OS_PLATFORM = parseOsPlatform(
|
|
4
|
+
process.env.ALWAYSAI_OS_PLATFORM
|
|
5
|
+
);
|
|
6
|
+
export const ALWAYSAI_SHOW_HIDDEN = parseBoolean(
|
|
7
|
+
process.env.ALWAYSAI_SHOW_HIDDEN
|
|
8
|
+
);
|
|
9
|
+
export const ALWAYSAI_DEVICE_AGENT_MODE =
|
|
10
|
+
process.env.ALWAYSAI_DEVICE_AGENT_MODE;
|
|
6
11
|
export const ALWAYSAI_LOG_LEVEL = process.env.AAI_LOG_LEVEL;
|
|
7
12
|
export const ALWAYSAI_LOG_TO_CONSOLE = process.env.ALWAYSAI_LOG_TO_CONSOLE;
|
|
13
|
+
export const ALWAYSAI_ANALYTICS_PASSTHROUGH = parseBoolean(
|
|
14
|
+
process.env.ALWAYSAI_ANALYTICS_PASSTHROUGH
|
|
15
|
+
);
|
|
8
16
|
|
|
9
17
|
function parseOsPlatform(str: string | undefined): NodeJS.Platform {
|
|
10
18
|
switch (str) {
|