@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.
Files changed (227) hide show
  1. package/lib/application-control/backup.d.ts.map +1 -1
  2. package/lib/application-control/backup.js +11 -5
  3. package/lib/application-control/backup.js.map +1 -1
  4. package/lib/application-control/config.d.ts +12 -4
  5. package/lib/application-control/config.d.ts.map +1 -1
  6. package/lib/application-control/config.js +59 -16
  7. package/lib/application-control/config.js.map +1 -1
  8. package/lib/application-control/environment-variables.d.ts.map +1 -1
  9. package/lib/application-control/environment-variables.js.map +1 -1
  10. package/lib/application-control/index.d.ts +5 -5
  11. package/lib/application-control/index.d.ts.map +1 -1
  12. package/lib/application-control/index.js +4 -6
  13. package/lib/application-control/index.js.map +1 -1
  14. package/lib/application-control/install.d.ts +1 -1
  15. package/lib/application-control/install.d.ts.map +1 -1
  16. package/lib/application-control/install.js +58 -57
  17. package/lib/application-control/install.js.map +1 -1
  18. package/lib/application-control/models.d.ts +7 -5
  19. package/lib/application-control/models.d.ts.map +1 -1
  20. package/lib/application-control/models.js +78 -57
  21. package/lib/application-control/models.js.map +1 -1
  22. package/lib/application-control/status.d.ts +0 -6
  23. package/lib/application-control/status.d.ts.map +1 -1
  24. package/lib/application-control/status.js +21 -33
  25. package/lib/application-control/status.js.map +1 -1
  26. package/lib/application-control/utils.d.ts +3 -2
  27. package/lib/application-control/utils.d.ts.map +1 -1
  28. package/lib/application-control/utils.js +54 -34
  29. package/lib/application-control/utils.js.map +1 -1
  30. package/lib/cloud-connection/app-install-status.d.ts +16 -0
  31. package/lib/cloud-connection/app-install-status.d.ts.map +1 -0
  32. package/lib/cloud-connection/app-install-status.js +53 -0
  33. package/lib/cloud-connection/app-install-status.js.map +1 -0
  34. package/lib/cloud-connection/bootstrap-provision.d.ts +2 -0
  35. package/lib/cloud-connection/bootstrap-provision.d.ts.map +1 -0
  36. package/lib/cloud-connection/bootstrap-provision.js +34 -0
  37. package/lib/cloud-connection/bootstrap-provision.js.map +1 -0
  38. package/lib/cloud-connection/cmd-status.d.ts +16 -0
  39. package/lib/cloud-connection/cmd-status.d.ts.map +1 -0
  40. package/lib/cloud-connection/cmd-status.js +49 -0
  41. package/lib/cloud-connection/cmd-status.js.map +1 -0
  42. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +21 -34
  43. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  44. package/lib/cloud-connection/device-agent-cloud-connection.js +211 -387
  45. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  46. package/lib/cloud-connection/device-agent.d.ts.map +1 -1
  47. package/lib/cloud-connection/device-agent.js +22 -26
  48. package/lib/cloud-connection/device-agent.js.map +1 -1
  49. package/lib/cloud-connection/live-updates-handler.d.ts +34 -0
  50. package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -0
  51. package/lib/cloud-connection/live-updates-handler.js +167 -0
  52. package/lib/cloud-connection/live-updates-handler.js.map +1 -0
  53. package/lib/cloud-connection/messages.d.ts +14 -0
  54. package/lib/cloud-connection/messages.d.ts.map +1 -0
  55. package/lib/cloud-connection/messages.js +38 -0
  56. package/lib/cloud-connection/messages.js.map +1 -0
  57. package/lib/cloud-connection/passthrough-handler.d.ts +11 -0
  58. package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -0
  59. package/lib/cloud-connection/passthrough-handler.js +59 -0
  60. package/lib/cloud-connection/passthrough-handler.js.map +1 -0
  61. package/lib/cloud-connection/publisher.d.ts +15 -0
  62. package/lib/cloud-connection/publisher.d.ts.map +1 -0
  63. package/lib/cloud-connection/publisher.js +58 -0
  64. package/lib/cloud-connection/publisher.js.map +1 -0
  65. package/lib/cloud-connection/shadow-handler.d.ts +33 -0
  66. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -0
  67. package/lib/cloud-connection/shadow-handler.js +108 -0
  68. package/lib/cloud-connection/shadow-handler.js.map +1 -0
  69. package/lib/cloud-connection/shadow-handler.test.d.ts +2 -0
  70. package/lib/cloud-connection/shadow-handler.test.d.ts.map +1 -0
  71. package/lib/cloud-connection/shadow-handler.test.js +321 -0
  72. package/lib/cloud-connection/shadow-handler.test.js.map +1 -0
  73. package/lib/cloud-connection/shadow.d.ts +16 -0
  74. package/lib/cloud-connection/shadow.d.ts.map +1 -0
  75. package/lib/cloud-connection/shadow.js +36 -0
  76. package/lib/cloud-connection/shadow.js.map +1 -0
  77. package/lib/device-control/device-control.d.ts.map +1 -1
  78. package/lib/device-control/device-control.js +1 -0
  79. package/lib/device-control/device-control.js.map +1 -1
  80. package/lib/docker/docker-cmd.js +1 -1
  81. package/lib/docker/docker-compose-cmd.d.ts.map +1 -1
  82. package/lib/docker/docker-compose-cmd.js +1 -1
  83. package/lib/docker/docker-compose-cmd.js.map +1 -1
  84. package/lib/endpoints.js +10 -10
  85. package/lib/endpoints.js.map +1 -1
  86. package/lib/environment.d.ts +1 -0
  87. package/lib/environment.d.ts.map +1 -1
  88. package/lib/environment.js +2 -1
  89. package/lib/environment.js.map +1 -1
  90. package/lib/infrastructure/agent-config.d.ts +15 -58
  91. package/lib/infrastructure/agent-config.d.ts.map +1 -1
  92. package/lib/infrastructure/agent-config.js +22 -15
  93. package/lib/infrastructure/agent-config.js.map +1 -1
  94. package/lib/infrastructure/agent-config.test.js +25 -23
  95. package/lib/infrastructure/agent-config.test.js.map +1 -1
  96. package/lib/infrastructure/system-id.d.ts +2 -0
  97. package/lib/infrastructure/system-id.d.ts.map +1 -0
  98. package/lib/infrastructure/system-id.js +21 -0
  99. package/lib/infrastructure/system-id.js.map +1 -0
  100. package/lib/infrastructure/tokens-and-device-cfg.d.ts +4 -0
  101. package/lib/infrastructure/tokens-and-device-cfg.d.ts.map +1 -0
  102. package/lib/infrastructure/tokens-and-device-cfg.js +27 -0
  103. package/lib/infrastructure/tokens-and-device-cfg.js.map +1 -0
  104. package/lib/infrastructure/urls.d.ts.map +1 -1
  105. package/lib/infrastructure/urls.js +3 -3
  106. package/lib/infrastructure/urls.js.map +1 -1
  107. package/lib/local-connection/rabbitmq-connection.d.ts +4 -0
  108. package/lib/local-connection/rabbitmq-connection.d.ts.map +1 -0
  109. package/lib/local-connection/rabbitmq-connection.js +58 -0
  110. package/lib/local-connection/rabbitmq-connection.js.map +1 -0
  111. package/lib/root.d.ts.map +1 -1
  112. package/lib/root.js +2 -7
  113. package/lib/root.js.map +1 -1
  114. package/lib/subcommands/app/app.d.ts +2 -1
  115. package/lib/subcommands/app/app.d.ts.map +1 -1
  116. package/lib/subcommands/app/app.js +112 -77
  117. package/lib/subcommands/app/app.js.map +1 -1
  118. package/lib/subcommands/app/index.js +2 -2
  119. package/lib/subcommands/device/clean.d.ts +2 -0
  120. package/lib/subcommands/device/clean.d.ts.map +1 -0
  121. package/lib/subcommands/device/clean.js +29 -0
  122. package/lib/subcommands/device/clean.js.map +1 -0
  123. package/lib/subcommands/device/device.d.ts +1 -1
  124. package/lib/subcommands/device/device.d.ts.map +1 -1
  125. package/lib/subcommands/device/device.js +44 -33
  126. package/lib/subcommands/device/device.js.map +1 -1
  127. package/lib/subcommands/device/index.d.ts.map +1 -1
  128. package/lib/subcommands/device/index.js +2 -1
  129. package/lib/subcommands/device/index.js.map +1 -1
  130. package/lib/subcommands/get-model-package.js +5 -5
  131. package/lib/subcommands/index.d.ts +0 -1
  132. package/lib/subcommands/index.d.ts.map +1 -1
  133. package/lib/subcommands/index.js +1 -1
  134. package/lib/subcommands/login.d.ts +0 -1
  135. package/lib/subcommands/login.d.ts.map +1 -1
  136. package/lib/subcommands/login.js +6 -14
  137. package/lib/subcommands/login.js.map +1 -1
  138. package/lib/util/clean-certs.d.ts +2 -0
  139. package/lib/util/clean-certs.d.ts.map +1 -0
  140. package/lib/util/clean-certs.js +16 -0
  141. package/lib/util/clean-certs.js.map +1 -0
  142. package/lib/util/directories.d.ts +16 -15
  143. package/lib/util/directories.d.ts.map +1 -1
  144. package/lib/util/directories.js +45 -26
  145. package/lib/util/directories.js.map +1 -1
  146. package/lib/util/fetch-with-timeout.d.ts +4 -0
  147. package/lib/util/fetch-with-timeout.d.ts.map +1 -0
  148. package/lib/util/fetch-with-timeout.js +15 -0
  149. package/lib/util/fetch-with-timeout.js.map +1 -0
  150. package/lib/util/get-device-id.d.ts +1 -1
  151. package/lib/util/get-device-id.d.ts.map +1 -1
  152. package/lib/util/get-device-id.js +14 -19
  153. package/lib/util/get-device-id.js.map +1 -1
  154. package/lib/util/http-client.d.ts +1 -1
  155. package/lib/util/http-client.d.ts.map +1 -1
  156. package/lib/util/http-client.js +10 -8
  157. package/lib/util/http-client.js.map +1 -1
  158. package/lib/util/logger.d.ts.map +1 -1
  159. package/lib/util/logger.js +4 -5
  160. package/lib/util/logger.js.map +1 -1
  161. package/lib/util/require-logged-in-and-paid-plan.d.ts +2 -0
  162. package/lib/util/require-logged-in-and-paid-plan.d.ts.map +1 -0
  163. package/lib/util/require-logged-in-and-paid-plan.js +18 -0
  164. package/lib/util/require-logged-in-and-paid-plan.js.map +1 -0
  165. package/lib/util/run-in-dir.d.ts.map +1 -1
  166. package/lib/util/run-in-dir.js +1 -0
  167. package/lib/util/run-in-dir.js.map +1 -1
  168. package/lib/util/timer.d.ts +2 -0
  169. package/lib/util/timer.d.ts.map +1 -0
  170. package/lib/util/timer.js +6 -0
  171. package/lib/util/timer.js.map +1 -0
  172. package/package.json +32 -35
  173. package/readme.md +100 -89
  174. package/src/application-control/backup.ts +11 -6
  175. package/src/application-control/config.ts +75 -13
  176. package/src/application-control/environment-variables.ts +3 -3
  177. package/src/application-control/index.ts +18 -11
  178. package/src/application-control/install.ts +82 -78
  179. package/src/application-control/models.ts +104 -72
  180. package/src/application-control/status.ts +29 -40
  181. package/src/application-control/utils.ts +66 -38
  182. package/src/cloud-connection/app-install-status.ts +62 -0
  183. package/src/cloud-connection/bootstrap-provision.ts +40 -0
  184. package/src/cloud-connection/cmd-status.ts +52 -0
  185. package/src/cloud-connection/device-agent-cloud-connection.ts +302 -526
  186. package/src/cloud-connection/device-agent.ts +31 -38
  187. package/src/cloud-connection/live-updates-handler.ts +226 -0
  188. package/src/cloud-connection/messages.ts +39 -0
  189. package/src/cloud-connection/passthrough-handler.ts +67 -0
  190. package/src/cloud-connection/publisher.ts +86 -0
  191. package/src/cloud-connection/shadow-handler.test.ts +361 -0
  192. package/src/cloud-connection/shadow-handler.ts +175 -0
  193. package/src/cloud-connection/shadow.ts +50 -0
  194. package/src/device-control/device-control.ts +1 -0
  195. package/src/docker/docker-cmd.ts +1 -1
  196. package/src/docker/docker-compose-cmd.ts +5 -2
  197. package/src/endpoints.ts +9 -9
  198. package/src/environment.ts +11 -3
  199. package/src/infrastructure/agent-config.test.ts +33 -29
  200. package/src/infrastructure/agent-config.ts +57 -22
  201. package/src/infrastructure/system-id.ts +18 -0
  202. package/src/infrastructure/tokens-and-device-cfg.ts +34 -0
  203. package/src/infrastructure/urls.ts +4 -2
  204. package/src/local-connection/rabbitmq-connection.ts +53 -0
  205. package/src/root.ts +2 -8
  206. package/src/subcommands/app/app.ts +119 -83
  207. package/src/subcommands/app/index.ts +3 -3
  208. package/src/subcommands/device/clean.ts +26 -0
  209. package/src/subcommands/device/device.ts +67 -54
  210. package/src/subcommands/device/index.ts +2 -1
  211. package/src/subcommands/get-model-package.ts +5 -5
  212. package/src/subcommands/index.ts +1 -1
  213. package/src/subcommands/login.ts +6 -14
  214. package/src/util/clean-certs.ts +12 -0
  215. package/src/util/directories.ts +68 -52
  216. package/src/util/fetch-with-timeout.ts +18 -0
  217. package/src/util/get-device-id.ts +16 -18
  218. package/src/util/http-client.ts +18 -13
  219. package/src/util/logger.ts +6 -6
  220. package/src/util/require-logged-in-and-paid-plan.ts +16 -0
  221. package/src/util/run-in-dir.ts +2 -1
  222. package/src/util/timer.ts +1 -0
  223. package/lib/infrastructure/certificates-and-tokens.d.ts +0 -6
  224. package/lib/infrastructure/certificates-and-tokens.d.ts.map +0 -1
  225. package/lib/infrastructure/certificates-and-tokens.js +0 -43
  226. package/lib/infrastructure/certificates-and-tokens.js.map +0 -1
  227. 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
+ };
@@ -1,3 +1,4 @@
1
+ // eslint-disable-next-line
1
2
  const osu = require('node-os-utils');
2
3
 
3
4
  export async function getCpuUtil(): Promise<number> {
@@ -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: { args: string[]; dir: string }) {
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 "alwaysai/lib/infrastructure";
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 "development":
7
- domain = "a6i0.net";
6
+ case 'development':
7
+ domain = 'a6i0.net';
8
8
  break;
9
- case "qa":
10
- domain = "a6i1.net";
9
+ case 'qa':
10
+ domain = 'a6i1.net';
11
11
  break;
12
- case "production":
13
- domain = "alwaysai.co";
12
+ case 'production':
13
+ domain = 'alwaysai.co';
14
14
  break;
15
15
  default:
16
- domain = "alwaysai.co";
16
+ domain = 'alwaysai.co';
17
17
  break;
18
18
  }
19
19
  return domain;
@@ -1,10 +1,18 @@
1
1
  import { platform } from 'os';
2
2
 
3
- export const ALWAYSAI_OS_PLATFORM = parseOsPlatform(process.env.ALWAYSAI_OS_PLATFORM);
4
- export const ALWAYSAI_SHOW_HIDDEN = parseBoolean(process.env.ALWAYSAI_SHOW_HIDDEN);
5
- export const ALWAYSAI_DEVICE_AGENT_MODE = process.env.ALWAYSAI_DEVICE_AGENT_MODE;
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) {