@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
@@ -1,10 +1,10 @@
1
- import { keyMirrors, ModelInstallPayload } from '@alwaysai/device-agent-schemas';
2
- import { app } from 'alwaysai/lib/components';
1
+ import * as path from 'path';
2
+ import { ModelInstallPayload } from '@alwaysai/device-agent-schemas';
3
3
  import {
4
4
  appModelsAddComponent,
5
5
  appModelsRemoveAllComponent,
6
6
  appModelsRemoveComponent,
7
- appModelsUpdateComponent,
7
+ appModelsUpdateComponent
8
8
  } from 'alwaysai/lib/components/app';
9
9
  import { JsSpawner } from 'alwaysai/lib/util';
10
10
  import { logger } from '../util/logger';
@@ -13,15 +13,19 @@ import { join, dirname } from 'path';
13
13
  import { AgentConfigFile } from '../infrastructure/agent-config';
14
14
  import { copyDir } from '../util/copy-dir';
15
15
  import { runInDir } from '../util/run-in-dir';
16
- import { getAppStatus, restartApp } from './status';
16
+ import { restartApp } from './status';
17
17
 
18
18
  import { ModelDetails } from './types';
19
19
  import {
20
20
  buildApp,
21
21
  downloadPackageUsingPresignedUrl,
22
22
  getAppDir,
23
- requireAppInstalled,
23
+ requireAppReady
24
24
  } from './utils';
25
+ import { MODEL_JSON_FILE_NAME } from 'alwaysai/lib/core/model';
26
+ import { APP_MODELS_DIRECTORY_NAME } from 'alwaysai/lib/constants';
27
+ import { readAppCfgFile, writeAppCfgFile } from './config';
28
+ import { AppConfig } from '@alwaysai/app-configuration-schemas';
25
29
 
26
30
  export async function getAppModels(props: { projectId: string }) {
27
31
  const { projectId } = props;
@@ -45,23 +49,12 @@ export async function getAppModels(props: { projectId: string }) {
45
49
  return modelDetails;
46
50
  }
47
51
 
48
- export async function addModel(props: { projectId: string; modelId: string }) {
52
+ export async function removeModel(props: {
53
+ projectId: string;
54
+ modelId: string;
55
+ }) {
49
56
  const { projectId, modelId } = props;
50
- await requireAppInstalled({ projectId });
51
-
52
- const appDir = getAppDir(projectId);
53
- await appModelsAddComponent({
54
- yes: false,
55
- dir: appDir,
56
- id: modelId,
57
- addToProject: false,
58
- });
59
- await buildApp({ appDir });
60
- }
61
-
62
- export async function removeModel(props: { projectId: string; modelId: string }) {
63
- const { projectId, modelId } = props;
64
- await requireAppInstalled({ projectId });
57
+ await requireAppReady({ projectId });
65
58
 
66
59
  const appDir = getAppDir(projectId);
67
60
 
@@ -71,16 +64,19 @@ export async function removeModel(props: { projectId: string; modelId: string })
71
64
  {
72
65
  id: modelId,
73
66
  purge: true,
74
- removeFromProject: false,
75
- },
67
+ removeFromProject: false
68
+ }
76
69
  ],
77
- appDir,
70
+ appDir
78
71
  );
79
72
  }
80
73
 
81
- export async function replaceModels(props: { projectId: string; modelIds: string[] }) {
74
+ export async function replaceModels(props: {
75
+ projectId: string;
76
+ modelIds: string[];
77
+ }) {
82
78
  const { projectId, modelIds } = props;
83
- await requireAppInstalled({ projectId });
79
+ await requireAppReady({ projectId });
84
80
 
85
81
  const appDir = getAppDir(projectId);
86
82
 
@@ -89,17 +85,17 @@ export async function replaceModels(props: { projectId: string; modelIds: string
89
85
  [
90
86
  {
91
87
  purge: true,
92
- removeFromProject: false,
93
- },
88
+ removeFromProject: false
89
+ }
94
90
  ],
95
- appDir,
91
+ appDir
96
92
  );
97
93
  for (const modelId of modelIds) {
98
94
  await appModelsAddComponent({
99
95
  yes: false,
100
96
  dir: appDir,
101
97
  id: modelId,
102
- addToProject: false,
98
+ addToProject: false
103
99
  });
104
100
  }
105
101
  await buildApp({ appDir });
@@ -107,88 +103,124 @@ export async function replaceModels(props: { projectId: string; modelIds: string
107
103
 
108
104
  export async function updateModels(props: { projectId: string }) {
109
105
  const { projectId } = props;
110
- await requireAppInstalled({ projectId });
106
+ await requireAppReady({ projectId });
111
107
 
112
108
  const appDir = getAppDir(projectId);
113
109
  await appModelsUpdateComponent({
114
110
  yes: false,
115
- dir: appDir,
111
+ dir: appDir
116
112
  });
117
113
  await buildApp({ appDir });
118
114
  }
119
115
 
120
116
  export async function installModelsWithPresignedURLs(
121
117
  modelPayloads: ModelInstallPayload[],
122
- targetDir: string,
118
+ targetDir: string
123
119
  ) {
124
120
  const spawner = JsSpawner();
121
+ await spawner.mkdirp(targetDir);
125
122
  await Promise.all(
126
123
  modelPayloads.map(async (payload: ModelInstallPayload) => {
124
+ logger.info(`Installing ${payload.id}: ${payload.version}`);
125
+ const version = payload.version;
127
126
  const modelDest = `${targetDir}/${payload.id}`;
128
127
  await spawner.mkdirp(modelDest);
129
128
  const localDest = `${modelDest}/${payload.version}.tar.gz`;
130
129
  await downloadPackageUsingPresignedUrl({
131
130
  localDest,
132
- presignedUrl: payload.modelSignedUrl,
131
+ presignedUrl: payload.modelSignedUrl
133
132
  });
134
133
  await spawner.untar(createReadStream(localDest), dirname(modelDest));
134
+ await updateModelJson(modelDest, (modelJson) => ({
135
+ ...modelJson,
136
+ version
137
+ }));
135
138
  await spawner.rimraf(localDest);
136
- }),
139
+ })
137
140
  );
141
+
142
+ async function readModelJson(dir: string) {
143
+ const filePath = spawner.resolvePath(dir, MODEL_JSON_FILE_NAME);
144
+ const output = await spawner.readFile(filePath);
145
+ const parsed = JSON.parse(output);
146
+ return parsed;
147
+ }
148
+
149
+ async function updateModelJson(dir: string, updater: (current: any) => any) {
150
+ const parsed = await readModelJson(dir);
151
+ const updated = updater(parsed);
152
+ const filePath = spawner.resolvePath(dir, MODEL_JSON_FILE_NAME);
153
+ const serialized = JSON.stringify(updated, null, 2);
154
+ await spawner.writeFile(filePath, serialized);
155
+ }
138
156
  }
139
157
 
140
- export async function updateModelsWithPresignedUrls(
141
- project: string,
142
- modelInstallPayloads: ModelInstallPayload[],
143
- ) {
144
- // create temp dir with all untouched models
158
+ export async function updateModelsWithPresignedUrls(props: {
159
+ projectId: string;
160
+ modelInstallPayloads: ModelInstallPayload[];
161
+ appReleaseHash: string;
162
+ newAppCfg: AppConfig;
163
+ }) {
164
+ const { projectId, modelInstallPayloads, appReleaseHash, newAppCfg } = props;
165
+ logger.info(`Installing models for ${projectId}`);
145
166
  const spawner = JsSpawner();
146
- const appDir = getAppDir(project);
147
- const ogDir = `${appDir}/models`;
148
- const tmpDir = `${ogDir}.tmp`;
167
+ const appDir = getAppDir(projectId);
168
+
169
+ if (await AgentConfigFile().isAppPresent({ projectId })) {
170
+ if (!(await AgentConfigFile().isAppReady({ projectId }))) {
171
+ throw new Error('Application already has installation in progress!');
172
+ }
173
+ logger.info('Updating installed application');
174
+ await AgentConfigFile().setAppInstalling({
175
+ projectId,
176
+ version: appReleaseHash
177
+ });
178
+ } else {
179
+ throw new Error('Application is not installed!');
180
+ }
181
+ const ogAppCfg = await readAppCfgFile({ projectId });
182
+
183
+ const ogDir = path.join(appDir, APP_MODELS_DIRECTORY_NAME);
184
+ // Copy all current models to restore dir in case of failure
149
185
  const restoreDir = `${ogDir}.restore`;
186
+ await spawner.rimraf(restoreDir);
150
187
  await copyDir({ srcPath: ogDir, destPath: restoreDir });
151
- await spawner.rimraf(tmpDir);
152
188
 
153
- try {
154
- // Move unchanged models
155
- const existingModels = await getAppModels({ projectId: project });
156
- for (const model of existingModels) {
157
- if (
158
- !modelInstallPayloads
159
- .map((newModel: ModelInstallPayload) => newModel.id)
160
- .includes(model.modelId)
161
- ) {
162
- // model does not need to be updated
163
- await copyDir({
164
- srcPath: `${ogDir}/${model.modelId}`,
165
- destPath: `${tmpDir}/${model.modelId}`,
166
- });
167
- }
168
- }
189
+ // Create temp dir to install new models
190
+ const tmpDir = `${ogDir}.tmp`;
169
191
 
170
- // install model packages
192
+ try {
193
+ await spawner.rimraf(tmpDir);
194
+ await copyDir({ srcPath: ogDir, destPath: tmpDir });
171
195
  await installModelsWithPresignedURLs(modelInstallPayloads, tmpDir);
196
+ // TODO: Purge outdated models
172
197
  await spawner.rimraf(ogDir);
173
198
  await copyDir({ srcPath: tmpDir, destPath: ogDir });
174
199
  await spawner.rimraf(tmpDir);
175
200
 
176
- await buildApp({ appDir: getAppDir(project) });
201
+ await writeAppCfgFile({ projectId, appCfg: newAppCfg });
202
+ await buildApp({ appDir });
177
203
 
178
- const appStatus = await getAppStatus({ projectId: project });
179
- const appIsRunning =
180
- appStatus.services.length &&
181
- appStatus.services[0].state !== keyMirrors.appState.stopped;
204
+ await AgentConfigFile().setAppInstalled({
205
+ projectId,
206
+ version: appReleaseHash
207
+ });
182
208
 
183
- if (appIsRunning) {
184
- restartApp({ projectId: project });
185
- }
209
+ await restartApp({ projectId });
186
210
 
187
- logger.info(`Models installed for project ${project}`);
211
+ logger.info(`Models installed for project ${projectId}`);
212
+ /* Leave error handling to higher level so errors are sent to cloud
188
213
  } catch (e) {
189
- logger.error(e, 'Error updating app models from presigned URL, restoring models.');
190
- await spawner.rimraf(tmpDir);
214
+ logger.error(
215
+ 'Error updating app models from presigned URL, restoring models.',
216
+ e
217
+ );
191
218
  await spawner.rimraf(ogDir);
192
219
  await copyDir({ srcPath: restoreDir, destPath: ogDir });
220
+ await writeAppCfgFile({ projectId, appCfg: ogAppCfg });
221
+ */
222
+ } finally {
223
+ await spawner.rimraf(tmpDir);
224
+ await spawner.rimraf(restoreDir);
193
225
  }
194
226
  }
@@ -1,33 +1,17 @@
1
1
  import compose from 'docker-compose';
2
- import { fetchAppReleaseHistory } from 'alwaysai/lib/infrastructure';
3
2
  import { JsSpawner } from 'alwaysai/lib/util';
4
3
 
5
4
  import { runDockerLogin } from '../docker/docker-cmd';
6
- import { getAppDir, requireAppInstalled } from './utils';
5
+ import { getAppDir, requireAppInstalled, requireAppReady } from './utils';
7
6
  import {
8
7
  ServiceStatusPacket,
9
8
  AppStatePacket,
10
9
  keyMirrors,
11
- AppStateValue,
10
+ AppStateValue
12
11
  } from '@alwaysai/device-agent-schemas';
13
12
  import { AgentConfigFile } from '../infrastructure/agent-config';
14
13
  import { logger } from '../util/logger';
15
14
 
16
- export async function listAppReleases(props: { projectId: string }) {
17
- const { projectId } = props;
18
- const releaseHistory = await fetchAppReleaseHistory(projectId);
19
- return releaseHistory;
20
- }
21
-
22
- export async function listAppLatestRelease(props: { projectId: string }) {
23
- const { projectId } = props;
24
- const releaseHistory = await fetchAppReleaseHistory(projectId);
25
- if (releaseHistory.length >= 1) {
26
- return releaseHistory[0]['releaseHash'];
27
- }
28
- return undefined;
29
- }
30
-
31
15
  export async function getAppStatus(props: {
32
16
  projectId: string;
33
17
  }): Promise<AppStatePacket> {
@@ -38,7 +22,7 @@ export async function getAppStatus(props: {
38
22
 
39
23
  const appDetails = {
40
24
  projectId,
41
- version: await AgentConfigFile().getAppVersion({ projectId }),
25
+ version: await AgentConfigFile().getAppVersion({ projectId })
42
26
  };
43
27
  if (!(await AgentConfigFile().isAppReady({ projectId }))) {
44
28
  // App is being installed or updated
@@ -53,7 +37,7 @@ export async function getAppStatus(props: {
53
37
 
54
38
  if (status.exitCode !== 0) {
55
39
  throw new Error(
56
- `Failed to get application status! stdout=${status.out} stderr=${status.err}`,
40
+ `Failed to get application status! stdout=${status.out} stderr=${status.err}`
57
41
  );
58
42
  }
59
43
 
@@ -64,7 +48,7 @@ export async function getAppStatus(props: {
64
48
  const containerId = await spawner.run({
65
49
  exe: 'docker-compose',
66
50
  cwd: appDir,
67
- args: ['ps', '-q', name],
51
+ args: ['ps', '-q', name]
68
52
  });
69
53
  if (containerId === '') {
70
54
  // The service was not yet started or failed to start
@@ -72,7 +56,7 @@ export async function getAppStatus(props: {
72
56
  } else {
73
57
  const slashContainerName = await spawner.run({
74
58
  exe: 'docker',
75
- args: ['inspect', '-f', '{{.Name}}', containerId],
59
+ args: ['inspect', '-f', '{{.Name}}', containerId]
76
60
  });
77
61
  const containerName = slashContainerName.substring(1);
78
62
 
@@ -102,8 +86,8 @@ export async function getAppStatus(props: {
102
86
  `Unable to find container for ${name} in ${JSON.stringify(
103
87
  status.data.services,
104
88
  null,
105
- 2,
106
- )}`,
89
+ 2
90
+ )}`
107
91
  );
108
92
  }
109
93
  }
@@ -117,7 +101,7 @@ export async function getAppLogs(props: {
117
101
  args?: string[];
118
102
  }): Promise<NodeJS.ReadableStream> {
119
103
  const { projectId, services, args } = props;
120
- await requireAppInstalled({ projectId });
104
+ await requireAppReady({ projectId });
121
105
 
122
106
  const appDir = getAppDir(projectId);
123
107
 
@@ -134,7 +118,7 @@ export async function getAppLogs(props: {
134
118
  return await JsSpawner().runStreaming({
135
119
  exe: 'docker-compose',
136
120
  args: ['logs', '-f', ...argsList, ...serviceList],
137
- cwd: appDir,
121
+ cwd: appDir
138
122
  });
139
123
  }
140
124
 
@@ -143,24 +127,24 @@ export async function startApp(props: {
143
127
  dockerLoginToken?: string;
144
128
  }): Promise<void> {
145
129
  const { projectId, dockerLoginToken } = props;
146
- await requireAppInstalled({ projectId });
130
+ await requireAppReady({ projectId });
147
131
 
148
132
  const appDir = getAppDir(projectId);
149
133
  if (dockerLoginToken !== undefined) {
150
134
  const result = await runDockerLogin({ token: dockerLoginToken });
151
- logger.info(result);
135
+ logger.debug(`docker login: ${result}`);
152
136
  }
153
137
 
154
138
  // TODO: Check if app is running
155
139
  // Start app
156
140
  const upOut = await compose.upAll({ cwd: appDir });
157
- logger.info(upOut);
141
+ logger.debug(`docker-compose up: ${JSON.stringify(upOut, null, 2)}`);
158
142
  if (upOut.exitCode !== 0) {
159
143
  throw new Error(
160
- `Failed to start application! stdout=${upOut.out} stderr=${upOut.err}`,
144
+ `Failed to start application! stdout=${upOut.out} stderr=${upOut.err}`
161
145
  );
162
146
  }
163
- logger.info('Started', projectId);
147
+ logger.info(`Started ${projectId}`);
164
148
  }
165
149
 
166
150
  export async function stopApp(props: { projectId: string }): Promise<void> {
@@ -168,16 +152,21 @@ export async function stopApp(props: { projectId: string }): Promise<void> {
168
152
  await requireAppInstalled({ projectId });
169
153
 
170
154
  const appDir = getAppDir(projectId);
171
- // TODO: Check if app is running
172
- // Stop app
173
- const output = await compose.down({ cwd: appDir });
174
- logger.info(output);
175
- if (output.exitCode !== 0) {
176
- throw new Error(
177
- `Failed to stop application! stdout=${output.out} stderr=${output.err}`,
178
- );
155
+ const appStatus = await getAppStatus({ projectId });
156
+ const appIsRunning =
157
+ appStatus.services.length &&
158
+ appStatus.services[0].state !== keyMirrors.appState.stopped;
159
+ if (appIsRunning) {
160
+ // Stop app
161
+ const output = await compose.down({ cwd: appDir });
162
+ logger.debug(`docker-compose down: ${JSON.stringify(output, null, 2)}`);
163
+ if (output.exitCode !== 0) {
164
+ throw new Error(
165
+ `Failed to stop application! stdout=${output.out} stderr=${output.err}`
166
+ );
167
+ }
168
+ logger.info(`Stopped ${projectId}`);
179
169
  }
180
- logger.info('Stopped', projectId);
181
170
  }
182
171
 
183
172
  export async function restartApp(props: { projectId: string }): Promise<void> {
@@ -1,17 +1,24 @@
1
1
  import compose from 'docker-compose';
2
2
  import * as path from 'path';
3
3
  import * as fs from 'fs';
4
- import { AAI_DIR, TARGET_JSON_FILE_NAME } from 'alwaysai/lib/constants';
4
+ import {
5
+ DOCKER_COMPOSE_FILE,
6
+ TARGET_JSON_FILE_NAME
7
+ } from 'alwaysai/lib/constants';
5
8
 
6
9
  import { AgentConfigFile } from '../infrastructure/agent-config';
7
- import nodeFetch from 'node-fetch';
8
- import { TargetJsonFile } from 'alwaysai/lib/core/app';
9
- import { appDeployLinuxAndRemoteDevice } from 'alwaysai/lib/components/app';
10
+ import { fetchWithTimeout } from '../util/fetch-with-timeout';
11
+ import {
12
+ getDockerComposeCmdForApp,
13
+ TargetJsonFile,
14
+ updateDockerComposeTargetHw,
15
+ writeDockerComposeFile,
16
+ writeStandaloneDockerfile
17
+ } from 'alwaysai/lib/core/app';
10
18
  import { runInDir } from '../util/run-in-dir';
11
- import { JsSpawner } from 'alwaysai/lib/util';
12
19
  import { logger } from '../util/logger';
13
-
14
- export const APP_ROOT = path.join(AAI_DIR, 'applications');
20
+ import { APP_ROOT } from '../util/directories';
21
+ import { JsSpawner } from 'alwaysai/lib/util/spawner';
15
22
 
16
23
  export function getAppDir(projectId: string): string {
17
24
  return path.join(APP_ROOT, projectId);
@@ -23,6 +30,11 @@ export async function requireAppInstalled(props: { projectId: string }) {
23
30
  if (!(await AgentConfigFile().isAppPresent({ projectId }))) {
24
31
  throw new Error('Application is not installed');
25
32
  }
33
+ }
34
+
35
+ export async function requireAppReady(props: { projectId: string }) {
36
+ const { projectId } = props;
37
+ await requireAppInstalled({ projectId });
26
38
  if (!(await AgentConfigFile().isAppReady({ projectId }))) {
27
39
  throw new Error('Application is not done installing or updating');
28
40
  }
@@ -32,46 +44,71 @@ export async function buildApp(props: { appDir: string }) {
32
44
  const { appDir } = props;
33
45
 
34
46
  // Build standalone image and docker-compose
35
- const targetJson = TargetJsonFile(appDir);
36
- const targetConfig = targetJson.read();
37
- if (targetConfig.targetProtocol !== 'docker:') {
47
+ const targetJsonFile = TargetJsonFile(appDir);
48
+ const targetCfg = targetJsonFile.read();
49
+ if (targetCfg.targetProtocol !== 'docker:') {
38
50
  throw new Error(`${TARGET_JSON_FILE_NAME} is not properly configured!`);
39
51
  }
40
52
  await runInDir(
41
- appDeployLinuxAndRemoteDevice,
42
- [
43
- {
44
- yes: true,
45
- generateDockerCompose: true,
46
- logs: false,
47
- stop: false,
48
- targetJson,
49
- targetConfig,
50
- },
51
- ],
52
- appDir,
53
+ async () => {
54
+ await writeStandaloneDockerfile();
55
+ const spawner = JsSpawner();
56
+ const targetHardware = targetCfg.targetHardware;
57
+ if (await spawner.exists(DOCKER_COMPOSE_FILE)) {
58
+ await updateDockerComposeTargetHw({
59
+ targetHardware
60
+ });
61
+ } else {
62
+ await writeDockerComposeFile({
63
+ spawner: JsSpawner(),
64
+ cmd: await getDockerComposeCmdForApp({ targetHardware })
65
+ });
66
+ }
67
+ },
68
+ [],
69
+ appDir
53
70
  );
54
71
 
55
72
  const buildOut = await compose.buildAll({ cwd: appDir });
56
- logger.info(buildOut);
73
+ logger.debug(`docker-compose build: ${JSON.stringify(buildOut, null, 2)}`);
57
74
  if (buildOut.exitCode !== 0) {
58
75
  throw new Error(
59
- `Failed to build application! stdout=${buildOut.out} stderr=${buildOut.err}`,
76
+ `Failed to build application! stdout=${buildOut.out} stderr=${buildOut.err}`
60
77
  );
61
78
  }
62
79
  }
63
80
 
81
+ class HTTPResponseError extends Error {
82
+ public response;
83
+ constructor(response) {
84
+ super(`HTTP Error Response: ${response.status} ${response.statusText}`);
85
+ this.response = response;
86
+ }
87
+ }
88
+
89
+ const checkStatus = (response) => {
90
+ if (response.ok) {
91
+ // response.status >= 200 && response.status < 300
92
+ return response;
93
+ } else {
94
+ throw new HTTPResponseError(response);
95
+ }
96
+ };
97
+
64
98
  export async function downloadPackageUsingPresignedUrl(props: {
65
99
  localDest: string;
66
100
  presignedUrl: string;
67
101
  }): Promise<void> {
68
- logger.info('downloading URL');
69
102
  const { localDest, presignedUrl } = props;
70
- const response = await nodeFetch(presignedUrl);
71
- if (response.status !== 200) {
72
- // If the URL is invalid; I think we shouldn't get here with the new changes
103
+ logger.debug(`Downloading package from ${presignedUrl}`);
104
+ let response: any;
105
+ try {
106
+ response = await fetchWithTimeout(presignedUrl);
107
+ } catch (error) {
108
+ const errorBody =
109
+ error.type === 'aborted' ? error : await error.response.text();
73
110
  throw new Error(
74
- `Status Code: ${response.status}, ${response.statusText}. Response: ${response.body}`,
111
+ `downloadPackageUsingPresignedUrl: Error=${error}\n${errorBody}`
75
112
  );
76
113
  }
77
114
 
@@ -84,12 +121,3 @@ export async function downloadPackageUsingPresignedUrl(props: {
84
121
  stream.on('error', reject);
85
122
  });
86
123
  }
87
-
88
- export async function getAppConfig(project: string) {
89
- const appCfgPath = path.join(getAppDir(project), 'alwaysai.app.json');
90
- if (!fs.existsSync(appCfgPath)) {
91
- throw new Error(`Application config not found for project ${project}`);
92
- }
93
-
94
- return JSON.parse(await JsSpawner().readFile(appCfgPath));
95
- }
@@ -0,0 +1,62 @@
1
+ import { AppInstallStatusPacket } from '@alwaysai/device-agent-schemas';
2
+ import { AppInstallStatusValue } from '@alwaysai/device-agent-schemas/lib/constants';
3
+
4
+ export class AppInstallStatus {
5
+ private appReleaseHash: string;
6
+ private status: AppInstallStatusValue;
7
+ private message?: string;
8
+
9
+ constructor(appReleaseHash, status, message?) {
10
+ this.appReleaseHash = appReleaseHash;
11
+ this.status = status;
12
+ this.message = message;
13
+ }
14
+
15
+ public getAppReleaseHash() {
16
+ return this.appReleaseHash;
17
+ }
18
+
19
+ public update(status, message?) {
20
+ this.status = status;
21
+ this.message = message;
22
+ }
23
+
24
+ public getStatusPacket(): AppInstallStatusPacket {
25
+ return {
26
+ status: this.status,
27
+ message: this.message,
28
+ appReleaseHash: this.appReleaseHash
29
+ };
30
+ }
31
+ }
32
+
33
+ export class AppInstallStatusManager {
34
+ private appList: AppInstallStatus[] = [];
35
+
36
+ public update(appReleaseHash, status, message?) {
37
+ // Update status if existing
38
+ // TODO: Reimplement this as a map
39
+ for (const app of this.appList) {
40
+ if (app.getAppReleaseHash() === appReleaseHash) {
41
+ app.update(status, message);
42
+ return;
43
+ }
44
+ }
45
+ // App was not found, so add to list
46
+ const appInstallStatus = new AppInstallStatus(
47
+ appReleaseHash,
48
+ status,
49
+ message
50
+ );
51
+ this.appList.push(appInstallStatus);
52
+ }
53
+
54
+ public getStatusPacket(appReleaseHash) {
55
+ for (const app of this.appList) {
56
+ if (app.getAppReleaseHash() === appReleaseHash) {
57
+ return app.getStatusPacket();
58
+ }
59
+ }
60
+ throw new Error(`No status for ${appReleaseHash}`);
61
+ }
62
+ }
@@ -0,0 +1,40 @@
1
+ import { logger } from 'alwaysai/lib/util';
2
+ import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
3
+ import { rmBootstrapCertsAndClose } from '../util/clean-certs';
4
+ import {
5
+ BOOTSTRAP_DEVICE_PRIVATE_KEY_FILE_PATH,
6
+ BOOTSTRAP_DEVICE_CERTIFICATE_FILE_PATH,
7
+ AWS_ROOT_CERTIFICATE_FILE_PATH
8
+ } from '../util/directories';
9
+ import { getDeviceUuid } from '../util/get-device-id';
10
+ import { BootstrapAgent } from './device-agent';
11
+
12
+ export function bootstrapProvision() {
13
+ setTimeout(rmBootstrapCertsAndClose, 60000);
14
+
15
+ const clientId = getDeviceUuid();
16
+ const bootstrapConfig = {
17
+ keyPath: BOOTSTRAP_DEVICE_PRIVATE_KEY_FILE_PATH(),
18
+ certPath: BOOTSTRAP_DEVICE_CERTIFICATE_FILE_PATH(),
19
+ caPath: AWS_ROOT_CERTIFICATE_FILE_PATH,
20
+ clientId,
21
+ host: getIoTCoreEndpointUrl()
22
+ };
23
+
24
+ const bootstrapAgent = new BootstrapAgent(bootstrapConfig);
25
+ bootstrapAgent.subscribeToAllTopics();
26
+
27
+ bootstrapAgent.publishMessage('$aws/certificates/create/json', '');
28
+
29
+ bootstrapAgent.device.on('connect', () => {
30
+ logger.info('Your device is being provisioned');
31
+ });
32
+
33
+ bootstrapAgent.device.on('message', (topic: string, payload: string) => {
34
+ bootstrapAgent.handleAwsCertificateTopics(topic, payload);
35
+ });
36
+
37
+ bootstrapAgent.device.on('packetsend', (packet: any) => {
38
+ logger.debug(`Sending packet: ${JSON.stringify({ packet }, null, 2)}`);
39
+ });
40
+ }