@alwaysai/device-agent 0.0.1-2.1-beta-provision

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 (192) hide show
  1. package/lib/application-control/backup.d.ts +8 -0
  2. package/lib/application-control/backup.d.ts.map +1 -0
  3. package/lib/application-control/backup.js +37 -0
  4. package/lib/application-control/backup.js.map +1 -0
  5. package/lib/application-control/config.d.ts +17 -0
  6. package/lib/application-control/config.d.ts.map +1 -0
  7. package/lib/application-control/config.js +62 -0
  8. package/lib/application-control/config.js.map +1 -0
  9. package/lib/application-control/environment-variables.d.ts +9 -0
  10. package/lib/application-control/environment-variables.d.ts.map +1 -0
  11. package/lib/application-control/environment-variables.js +73 -0
  12. package/lib/application-control/environment-variables.js.map +1 -0
  13. package/lib/application-control/index.d.ts +10 -0
  14. package/lib/application-control/index.d.ts.map +1 -0
  15. package/lib/application-control/index.js +32 -0
  16. package/lib/application-control/index.js.map +1 -0
  17. package/lib/application-control/install.d.ts +22 -0
  18. package/lib/application-control/install.d.ts.map +1 -0
  19. package/lib/application-control/install.js +156 -0
  20. package/lib/application-control/install.js.map +1 -0
  21. package/lib/application-control/models.d.ts +23 -0
  22. package/lib/application-control/models.d.ts.map +1 -0
  23. package/lib/application-control/models.js +154 -0
  24. package/lib/application-control/models.js.map +1 -0
  25. package/lib/application-control/status.d.ts +27 -0
  26. package/lib/application-control/status.d.ts.map +1 -0
  27. package/lib/application-control/status.js +153 -0
  28. package/lib/application-control/status.js.map +1 -0
  29. package/lib/application-control/types.d.ts +5 -0
  30. package/lib/application-control/types.d.ts.map +1 -0
  31. package/lib/application-control/types.js +3 -0
  32. package/lib/application-control/types.js.map +1 -0
  33. package/lib/application-control/utils.d.ts +14 -0
  34. package/lib/application-control/utils.d.ts.map +1 -0
  35. package/lib/application-control/utils.js +82 -0
  36. package/lib/application-control/utils.js.map +1 -0
  37. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +51 -0
  38. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -0
  39. package/lib/cloud-connection/device-agent-cloud-connection.js +490 -0
  40. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -0
  41. package/lib/cloud-connection/device-agent.d.ts +21 -0
  42. package/lib/cloud-connection/device-agent.d.ts.map +1 -0
  43. package/lib/cloud-connection/device-agent.js +65 -0
  44. package/lib/cloud-connection/device-agent.js.map +1 -0
  45. package/lib/device-control/device-control.d.ts +4 -0
  46. package/lib/device-control/device-control.d.ts.map +1 -0
  47. package/lib/device-control/device-control.js +20 -0
  48. package/lib/device-control/device-control.js.map +1 -0
  49. package/lib/docker/docker-cmd.d.ts +4 -0
  50. package/lib/docker/docker-cmd.d.ts.map +1 -0
  51. package/lib/docker/docker-cmd.js +16 -0
  52. package/lib/docker/docker-cmd.js.map +1 -0
  53. package/lib/docker/docker-compose-cmd.d.ts +5 -0
  54. package/lib/docker/docker-compose-cmd.d.ts.map +1 -0
  55. package/lib/docker/docker-compose-cmd.js +16 -0
  56. package/lib/docker/docker-compose-cmd.js.map +1 -0
  57. package/lib/endpoints.d.ts +3 -0
  58. package/lib/endpoints.d.ts.map +1 -0
  59. package/lib/endpoints.js +28 -0
  60. package/lib/endpoints.js.map +1 -0
  61. package/lib/environment.d.ts +7 -0
  62. package/lib/environment.d.ts.map +1 -0
  63. package/lib/environment.js +24 -0
  64. package/lib/environment.js.map +1 -0
  65. package/lib/index.d.ts +3 -0
  66. package/lib/index.d.ts.map +1 -0
  67. package/lib/index.js +26 -0
  68. package/lib/index.js.map +1 -0
  69. package/lib/infrastructure/agent-config.d.ts +73 -0
  70. package/lib/infrastructure/agent-config.d.ts.map +1 -0
  71. package/lib/infrastructure/agent-config.js +175 -0
  72. package/lib/infrastructure/agent-config.js.map +1 -0
  73. package/lib/infrastructure/agent-config.test.d.ts +2 -0
  74. package/lib/infrastructure/agent-config.test.d.ts.map +1 -0
  75. package/lib/infrastructure/agent-config.test.js +182 -0
  76. package/lib/infrastructure/agent-config.test.js.map +1 -0
  77. package/lib/infrastructure/certificates-and-tokens.d.ts +6 -0
  78. package/lib/infrastructure/certificates-and-tokens.d.ts.map +1 -0
  79. package/lib/infrastructure/certificates-and-tokens.js +37 -0
  80. package/lib/infrastructure/certificates-and-tokens.js.map +1 -0
  81. package/lib/infrastructure/urls.d.ts +2 -0
  82. package/lib/infrastructure/urls.d.ts.map +1 -0
  83. package/lib/infrastructure/urls.js +25 -0
  84. package/lib/infrastructure/urls.js.map +1 -0
  85. package/lib/root.d.ts +2 -0
  86. package/lib/root.d.ts.map +1 -0
  87. package/lib/root.js +12 -0
  88. package/lib/root.js.map +1 -0
  89. package/lib/subcommands/app/app.d.ts +59 -0
  90. package/lib/subcommands/app/app.d.ts.map +1 -0
  91. package/lib/subcommands/app/app.js +317 -0
  92. package/lib/subcommands/app/app.js.map +1 -0
  93. package/lib/subcommands/app/index.d.ts +2 -0
  94. package/lib/subcommands/app/index.d.ts.map +1 -0
  95. package/lib/subcommands/app/index.js +30 -0
  96. package/lib/subcommands/app/index.js.map +1 -0
  97. package/lib/subcommands/device/clean.d.ts +2 -0
  98. package/lib/subcommands/device/clean.d.ts.map +1 -0
  99. package/lib/subcommands/device/clean.js +20 -0
  100. package/lib/subcommands/device/clean.js.map +1 -0
  101. package/lib/subcommands/device/device.d.ts +6 -0
  102. package/lib/subcommands/device/device.d.ts.map +1 -0
  103. package/lib/subcommands/device/device.js +92 -0
  104. package/lib/subcommands/device/device.js.map +1 -0
  105. package/lib/subcommands/device/index.d.ts +2 -0
  106. package/lib/subcommands/device/index.d.ts.map +1 -0
  107. package/lib/subcommands/device/index.js +12 -0
  108. package/lib/subcommands/device/index.js.map +1 -0
  109. package/lib/subcommands/get-model-package.d.ts +5 -0
  110. package/lib/subcommands/get-model-package.d.ts.map +1 -0
  111. package/lib/subcommands/get-model-package.js +35 -0
  112. package/lib/subcommands/get-model-package.js.map +1 -0
  113. package/lib/subcommands/index.d.ts +9 -0
  114. package/lib/subcommands/index.d.ts.map +1 -0
  115. package/lib/subcommands/index.js +14 -0
  116. package/lib/subcommands/index.js.map +1 -0
  117. package/lib/subcommands/login.d.ts +6 -0
  118. package/lib/subcommands/login.d.ts.map +1 -0
  119. package/lib/subcommands/login.js +35 -0
  120. package/lib/subcommands/login.js.map +1 -0
  121. package/lib/util/clean-certs.d.ts +2 -0
  122. package/lib/util/clean-certs.d.ts.map +1 -0
  123. package/lib/util/clean-certs.js +16 -0
  124. package/lib/util/clean-certs.js.map +1 -0
  125. package/lib/util/copy-dir.d.ts +5 -0
  126. package/lib/util/copy-dir.d.ts.map +1 -0
  127. package/lib/util/copy-dir.js +16 -0
  128. package/lib/util/copy-dir.js.map +1 -0
  129. package/lib/util/directories.d.ts +23 -0
  130. package/lib/util/directories.d.ts.map +1 -0
  131. package/lib/util/directories.js +50 -0
  132. package/lib/util/directories.js.map +1 -0
  133. package/lib/util/get-device-id.d.ts +2 -0
  134. package/lib/util/get-device-id.d.ts.map +1 -0
  135. package/lib/util/get-device-id.js +24 -0
  136. package/lib/util/get-device-id.js.map +1 -0
  137. package/lib/util/http-client.d.ts +3 -0
  138. package/lib/util/http-client.d.ts.map +1 -0
  139. package/lib/util/http-client.js +30 -0
  140. package/lib/util/http-client.js.map +1 -0
  141. package/lib/util/logger.d.ts +4 -0
  142. package/lib/util/logger.d.ts.map +1 -0
  143. package/lib/util/logger.js +24 -0
  144. package/lib/util/logger.js.map +1 -0
  145. package/lib/util/run-in-dir.d.ts +2 -0
  146. package/lib/util/run-in-dir.d.ts.map +1 -0
  147. package/lib/util/run-in-dir.js +17 -0
  148. package/lib/util/run-in-dir.js.map +1 -0
  149. package/lib/util/sleep.d.ts +2 -0
  150. package/lib/util/sleep.d.ts.map +1 -0
  151. package/lib/util/sleep.js +9 -0
  152. package/lib/util/sleep.js.map +1 -0
  153. package/package.json +98 -0
  154. package/readme.md +219 -0
  155. package/src/application-control/backup.ts +36 -0
  156. package/src/application-control/config.ts +61 -0
  157. package/src/application-control/environment-variables.ts +74 -0
  158. package/src/application-control/index.ts +45 -0
  159. package/src/application-control/install.ts +206 -0
  160. package/src/application-control/models.ts +194 -0
  161. package/src/application-control/status.ts +187 -0
  162. package/src/application-control/types.ts +1 -0
  163. package/src/application-control/utils.ts +95 -0
  164. package/src/cloud-connection/device-agent-cloud-connection.ts +673 -0
  165. package/src/cloud-connection/device-agent.ts +120 -0
  166. package/src/device-control/device-control.ts +16 -0
  167. package/src/docker/docker-cmd.ts +12 -0
  168. package/src/docker/docker-compose-cmd.ts +12 -0
  169. package/src/endpoints.ts +24 -0
  170. package/src/environment.ts +28 -0
  171. package/src/index.ts +26 -0
  172. package/src/infrastructure/agent-config.test.ts +199 -0
  173. package/src/infrastructure/agent-config.ts +208 -0
  174. package/src/infrastructure/certificates-and-tokens.ts +47 -0
  175. package/src/infrastructure/urls.ts +21 -0
  176. package/src/root.ts +11 -0
  177. package/src/subcommands/app/app.ts +337 -0
  178. package/src/subcommands/app/index.ts +46 -0
  179. package/src/subcommands/device/clean.ts +16 -0
  180. package/src/subcommands/device/device.ts +126 -0
  181. package/src/subcommands/device/index.ts +9 -0
  182. package/src/subcommands/get-model-package.ts +33 -0
  183. package/src/subcommands/index.ts +11 -0
  184. package/src/subcommands/login.ts +33 -0
  185. package/src/util/clean-certs.ts +12 -0
  186. package/src/util/copy-dir.ts +12 -0
  187. package/src/util/directories.ts +82 -0
  188. package/src/util/get-device-id.ts +22 -0
  189. package/src/util/http-client.ts +35 -0
  190. package/src/util/logger.ts +28 -0
  191. package/src/util/run-in-dir.ts +15 -0
  192. package/src/util/sleep.ts +5 -0
@@ -0,0 +1,206 @@
1
+ import * as rimraf from 'rimraf';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { JsSpawner, Spawner } from 'alwaysai/lib/util';
5
+ import { getAppDir, downloadPackageUsingPresignedUrl, buildApp } from './utils';
6
+ import { AppDetailsPacket, ModelInstallPayload } from '@alwaysai/device-agent-schemas';
7
+ import { BACKUP_EXT, createAppBackup } from './backup';
8
+ import { stopApp } from './status';
9
+ import { AgentConfigFile } from '../infrastructure/agent-config';
10
+ import { ProjectJsonFile } from 'alwaysai/lib/core/project';
11
+ import {
12
+ getTargetHardwareType,
13
+ AppJsonFile,
14
+ TargetJsonFile,
15
+ } from 'alwaysai/lib/core/app';
16
+ import { appCleanDockerComponent, buildDocker } from 'alwaysai/lib/components/app';
17
+ import {
18
+ appInstallComponent,
19
+ installVenv,
20
+ } from 'alwaysai/lib/components/app/app-install-component';
21
+ import { DOCKERFILE, DOCKER_IMAGE_ID_INITIAL_VALUE } from 'alwaysai/lib/constants';
22
+ import { appReleasePullComponent } from 'alwaysai/lib/components/release';
23
+ import { runInDir } from '../util/run-in-dir';
24
+ import { installModelsWithPresignedURLs } from './models';
25
+ import { logger } from '../util/logger';
26
+
27
+ type SignedUrlPayloadType = {
28
+ appInstallPayload: {
29
+ appSignedUrl: string;
30
+ };
31
+ modelsInstallPayload: {
32
+ id: string;
33
+ version: number;
34
+ modelSignedUrl: string;
35
+ }[];
36
+ };
37
+
38
+ export async function getInstalledApps(): Promise<AppDetailsPacket[]> {
39
+ const apps = await AgentConfigFile().getApps();
40
+ const appDetails: AppDetailsPacket[] = [];
41
+ for (const app of apps) {
42
+ const { projectId, version } = app;
43
+ appDetails.push({ projectId, version });
44
+ }
45
+ return appDetails;
46
+ }
47
+
48
+ export async function installApp(props: {
49
+ projectId: string;
50
+ appReleaseHash: string;
51
+ signedUrlsPayload?: SignedUrlPayloadType;
52
+ }): Promise<void> {
53
+ const { projectId, appReleaseHash, signedUrlsPayload } = props;
54
+
55
+ const appDir = getAppDir(projectId);
56
+ const spawner = JsSpawner();
57
+ if (await AgentConfigFile().isAppPresent({ projectId })) {
58
+ if (!(await AgentConfigFile().isAppReady({ projectId }))) {
59
+ throw new Error('Application already has installation in progress!');
60
+ }
61
+ logger.info('Application is already installed, updating');
62
+ await AgentConfigFile().setAppInstalling({ projectId, version: appReleaseHash });
63
+ await spawner.rimraf(appDir);
64
+ } else {
65
+ await AgentConfigFile().setAppInstalling({ projectId, version: appReleaseHash });
66
+ }
67
+
68
+ await spawner.mkdirp(appDir);
69
+
70
+ // download app package
71
+ const localDest = path.join(appDir, `${path.basename(appReleaseHash)}.tgz`);
72
+ if (!signedUrlsPayload) {
73
+ await runInDir(
74
+ appReleasePullComponent,
75
+ [
76
+ {
77
+ yes: true,
78
+ project: projectId,
79
+ releaseHash: appReleaseHash,
80
+ },
81
+ ],
82
+ appDir,
83
+ );
84
+ } else {
85
+ const { appSignedUrl } = signedUrlsPayload.appInstallPayload;
86
+ await downloadPackageUsingPresignedUrl({ localDest, presignedUrl: appSignedUrl });
87
+ }
88
+
89
+ // Unpack app package and remove tar file
90
+ await unPackApp({ spawner, localDest, appDir });
91
+
92
+ // The following changes replace the runCliCmd for 'app configure'
93
+ // This part replaces findOrWriteProjectJsonFile() due to the
94
+ // calls that check the user is logged in and also for project collaboration
95
+ // NOTE: this process no longer checks project collaboration
96
+ await checkValidProjectFiles({ appDir });
97
+
98
+ // install models, python venv, and docker image
99
+ if (!signedUrlsPayload) {
100
+ await runInDir(
101
+ appInstallComponent,
102
+ [
103
+ {
104
+ yes: true,
105
+ pull: true,
106
+ clean: true,
107
+ skipModels: false,
108
+ source: false,
109
+ },
110
+ ],
111
+ appDir,
112
+ );
113
+ } else {
114
+ await installAppBuildReqs({ appDir });
115
+ await installModelsWithPresignedURLs(
116
+ signedUrlsPayload.modelsInstallPayload,
117
+ path.join(appDir, 'models'),
118
+ );
119
+ }
120
+
121
+ await buildApp({ appDir });
122
+
123
+ await AgentConfigFile().setAppInstalled({ projectId, version: appReleaseHash });
124
+
125
+ logger.info(
126
+ `Installed app version ${appReleaseHash} from project ${projectId} to ${appDir}.`,
127
+ );
128
+ }
129
+
130
+ async function installAppBuildReqs(props: { appDir: string }) {
131
+ const { appDir } = props;
132
+ const targetJsonFile = TargetJsonFile(appDir);
133
+ const targetJson = targetJsonFile.readIfExists();
134
+ if (!targetJson) {
135
+ throw new Error('Target json file does not exist!');
136
+ }
137
+
138
+ await runInDir(
139
+ async () => {
140
+ const targetHostSpawner = targetJsonFile.readHostSpawner();
141
+
142
+ await appCleanDockerComponent({ targetHostSpawner });
143
+
144
+ await buildDocker({
145
+ targetJson,
146
+ targetJsonFile,
147
+ targetHostSpawner,
148
+ pull: true,
149
+ });
150
+
151
+ await installVenv({
152
+ targetJson,
153
+ sourceSpawner: targetHostSpawner,
154
+ targetJsonFile,
155
+ });
156
+ },
157
+ [],
158
+ appDir,
159
+ );
160
+ }
161
+
162
+ async function unPackApp(props: { spawner: Spawner; localDest: string; appDir: string }) {
163
+ const { spawner, localDest, appDir } = props;
164
+ await spawner.untar(fs.createReadStream(localDest), appDir);
165
+ await spawner.rimraf(localDest);
166
+ }
167
+
168
+ async function checkValidProjectFiles({ appDir }) {
169
+ if (!ProjectJsonFile(appDir).readIfExists()) {
170
+ throw new Error('Project JSON file does not exist!');
171
+ }
172
+
173
+ // check for app json file and app.py files
174
+ if (!AppJsonFile(appDir).readIfExists()) {
175
+ throw new Error('App JSON file does not exist!');
176
+ }
177
+
178
+ // write target json
179
+ if (!fs.existsSync(path.join(appDir, DOCKERFILE))) {
180
+ throw new Error('No Dockerfile found for application!');
181
+ }
182
+ TargetJsonFile(appDir).write({
183
+ targetProtocol: 'docker:',
184
+ dockerImageId: DOCKER_IMAGE_ID_INITIAL_VALUE,
185
+ targetHardware: await getTargetHardwareType({}),
186
+ });
187
+ }
188
+
189
+ export async function uninstallApp(props: { projectId: string }): Promise<void> {
190
+ const { projectId } = props;
191
+ if (!(await AgentConfigFile().isAppPresent({ projectId }))) {
192
+ logger.info(`Application ${projectId} not installed`);
193
+ return;
194
+ }
195
+ try {
196
+ await stopApp({ projectId });
197
+ } catch {
198
+ logger.info('Failed to stop app, may be left running...');
199
+ }
200
+ await AgentConfigFile().setAppUninstalled({ projectId });
201
+ // Delete application directory and backup
202
+ const appDir = getAppDir(projectId);
203
+ rimraf.sync(appDir);
204
+ rimraf.sync(`${appDir}${BACKUP_EXT}`);
205
+ logger.info('Uninstalled', projectId);
206
+ }
@@ -0,0 +1,194 @@
1
+ import { keyMirrors, ModelInstallPayload } from '@alwaysai/device-agent-schemas';
2
+ import { app } from 'alwaysai/lib/components';
3
+ import {
4
+ appModelsAddComponent,
5
+ appModelsRemoveAllComponent,
6
+ appModelsRemoveComponent,
7
+ appModelsUpdateComponent,
8
+ } from 'alwaysai/lib/components/app';
9
+ import { JsSpawner } from 'alwaysai/lib/util';
10
+ import { logger } from '../util/logger';
11
+ import { existsSync, createReadStream } from 'fs';
12
+ import { join, dirname } from 'path';
13
+ import { AgentConfigFile } from '../infrastructure/agent-config';
14
+ import { copyDir } from '../util/copy-dir';
15
+ import { runInDir } from '../util/run-in-dir';
16
+ import { getAppStatus, restartApp } from './status';
17
+
18
+ import { ModelDetails } from './types';
19
+ import {
20
+ buildApp,
21
+ downloadPackageUsingPresignedUrl,
22
+ getAppDir,
23
+ requireAppInstalled,
24
+ } from './utils';
25
+
26
+ export async function getAppModels(props: { projectId: string }) {
27
+ const { projectId } = props;
28
+ if (!(await AgentConfigFile().isAppPresent({ projectId }))) {
29
+ throw new Error('Application is not installed');
30
+ }
31
+
32
+ const appDir = getAppDir(projectId);
33
+ const appCfgPath = join(appDir, 'alwaysai.app.json');
34
+ if (!existsSync(appCfgPath)) {
35
+ throw new Error('Application config not found!');
36
+ }
37
+ const appCfg = JSON.parse(await JsSpawner().readFile(appCfgPath));
38
+ const modelDetails: ModelDetails[] = [];
39
+ if (!('models' in appCfg)) {
40
+ return modelDetails;
41
+ }
42
+ for (const model in appCfg['models']) {
43
+ modelDetails.push({ modelId: model, version: appCfg['models'][model] });
44
+ }
45
+ return modelDetails;
46
+ }
47
+
48
+ export async function addModel(props: { projectId: string; modelId: string }) {
49
+ 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 });
65
+
66
+ const appDir = getAppDir(projectId);
67
+
68
+ await runInDir(
69
+ appModelsRemoveComponent,
70
+ [
71
+ {
72
+ id: modelId,
73
+ purge: true,
74
+ removeFromProject: false,
75
+ },
76
+ ],
77
+ appDir,
78
+ );
79
+ }
80
+
81
+ export async function replaceModels(props: { projectId: string; modelIds: string[] }) {
82
+ const { projectId, modelIds } = props;
83
+ await requireAppInstalled({ projectId });
84
+
85
+ const appDir = getAppDir(projectId);
86
+
87
+ await runInDir(
88
+ appModelsRemoveAllComponent,
89
+ [
90
+ {
91
+ purge: true,
92
+ removeFromProject: false,
93
+ },
94
+ ],
95
+ appDir,
96
+ );
97
+ for (const modelId of modelIds) {
98
+ await appModelsAddComponent({
99
+ yes: false,
100
+ dir: appDir,
101
+ id: modelId,
102
+ addToProject: false,
103
+ });
104
+ }
105
+ await buildApp({ appDir });
106
+ }
107
+
108
+ export async function updateModels(props: { projectId: string }) {
109
+ const { projectId } = props;
110
+ await requireAppInstalled({ projectId });
111
+
112
+ const appDir = getAppDir(projectId);
113
+ await appModelsUpdateComponent({
114
+ yes: false,
115
+ dir: appDir,
116
+ });
117
+ await buildApp({ appDir });
118
+ }
119
+
120
+ export async function installModelsWithPresignedURLs(
121
+ modelPayloads: ModelInstallPayload[],
122
+ targetDir: string,
123
+ ) {
124
+ const spawner = JsSpawner();
125
+ await Promise.all(
126
+ modelPayloads.map(async (payload: ModelInstallPayload) => {
127
+ const modelDest = `${targetDir}/${payload.id}`;
128
+ await spawner.mkdirp(modelDest);
129
+ const localDest = `${modelDest}/${payload.version}.tar.gz`;
130
+ await downloadPackageUsingPresignedUrl({
131
+ localDest,
132
+ presignedUrl: payload.modelSignedUrl,
133
+ });
134
+ await spawner.untar(createReadStream(localDest), dirname(modelDest));
135
+ await spawner.rimraf(localDest);
136
+ }),
137
+ );
138
+ }
139
+
140
+ export async function updateModelsWithPresignedUrls(
141
+ project: string,
142
+ modelInstallPayloads: ModelInstallPayload[],
143
+ ) {
144
+ // create temp dir with all untouched models
145
+ const spawner = JsSpawner();
146
+ const appDir = getAppDir(project);
147
+ const ogDir = `${appDir}/models`;
148
+ const tmpDir = `${ogDir}.tmp`;
149
+ const restoreDir = `${ogDir}.restore`;
150
+ await copyDir({ srcPath: ogDir, destPath: restoreDir });
151
+ await spawner.rimraf(tmpDir);
152
+
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
+ }
169
+
170
+ // install model packages
171
+ await installModelsWithPresignedURLs(modelInstallPayloads, tmpDir);
172
+ await spawner.rimraf(ogDir);
173
+ await copyDir({ srcPath: tmpDir, destPath: ogDir });
174
+ await spawner.rimraf(tmpDir);
175
+
176
+ await buildApp({ appDir: getAppDir(project) });
177
+
178
+ const appStatus = await getAppStatus({ projectId: project });
179
+ const appIsRunning =
180
+ appStatus.services.length &&
181
+ appStatus.services[0].state !== keyMirrors.appState.stopped;
182
+
183
+ if (appIsRunning) {
184
+ restartApp({ projectId: project });
185
+ }
186
+
187
+ logger.info(`Models installed for project ${project}`);
188
+ } catch (e) {
189
+ logger.error(e, 'Error updating app models from presigned URL, restoring models.');
190
+ await spawner.rimraf(tmpDir);
191
+ await spawner.rimraf(ogDir);
192
+ await copyDir({ srcPath: restoreDir, destPath: ogDir });
193
+ }
194
+ }
@@ -0,0 +1,187 @@
1
+ import compose from 'docker-compose';
2
+ import { fetchAppReleaseHistory } from 'alwaysai/lib/infrastructure';
3
+ import { JsSpawner } from 'alwaysai/lib/util';
4
+
5
+ import { runDockerLogin } from '../docker/docker-cmd';
6
+ import { getAppDir, requireAppInstalled } from './utils';
7
+ import {
8
+ ServiceStatusPacket,
9
+ AppStatePacket,
10
+ keyMirrors,
11
+ AppStateValue,
12
+ } from '@alwaysai/device-agent-schemas';
13
+ import { AgentConfigFile } from '../infrastructure/agent-config';
14
+ import { logger } from '../util/logger';
15
+
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
+ export async function getAppStatus(props: {
32
+ projectId: string;
33
+ }): Promise<AppStatePacket> {
34
+ const { projectId } = props;
35
+ if (!(await AgentConfigFile().isAppPresent({ projectId }))) {
36
+ throw new Error('Application is not installed');
37
+ }
38
+
39
+ const appDetails = {
40
+ projectId,
41
+ version: await AgentConfigFile().getAppVersion({ projectId }),
42
+ };
43
+ if (!(await AgentConfigFile().isAppReady({ projectId }))) {
44
+ // App is being installed or updated
45
+ return { appDetails, services: [] };
46
+ }
47
+
48
+ const appDir = getAppDir(projectId);
49
+ const composeServices = await compose.configServices({ cwd: appDir });
50
+
51
+ // Check if app is running
52
+ const status = await compose.ps({ cwd: appDir });
53
+
54
+ if (status.exitCode !== 0) {
55
+ throw new Error(
56
+ `Failed to get application status! stdout=${status.out} stderr=${status.err}`,
57
+ );
58
+ }
59
+
60
+ const services: ServiceStatusPacket[] = [];
61
+ const spawner = JsSpawner({ path: appDir });
62
+ for (const name of composeServices.data.services) {
63
+ // Get container name for service
64
+ const containerId = await spawner.run({
65
+ exe: 'docker-compose',
66
+ cwd: appDir,
67
+ args: ['ps', '-q', name],
68
+ });
69
+ if (containerId === '') {
70
+ // The service was not yet started or failed to start
71
+ services.push({ name, state: keyMirrors.appState.stopped });
72
+ } else {
73
+ const slashContainerName = await spawner.run({
74
+ exe: 'docker',
75
+ args: ['inspect', '-f', '{{.Name}}', containerId],
76
+ });
77
+ const containerName = slashContainerName.substring(1);
78
+
79
+ let foundContainer = false;
80
+ for (const container of status.data.services) {
81
+ if (container.name !== containerName) {
82
+ continue;
83
+ }
84
+ let state: AppStateValue;
85
+ if (container.state.includes('Up')) {
86
+ state = keyMirrors.appState.up;
87
+ } else if (
88
+ container.state.includes('Stopped') ||
89
+ container.state.includes('Exit')
90
+ ) {
91
+ state = keyMirrors.appState.stopped;
92
+ } else if (container.state.includes('Restarting')) {
93
+ state = keyMirrors.appState.restarting;
94
+ } else {
95
+ throw new Error(`Unknown state for service! (${container.state})`);
96
+ }
97
+ services.push({ name, state });
98
+ foundContainer = true;
99
+ }
100
+ if (!foundContainer) {
101
+ throw new Error(
102
+ `Unable to find container for ${name} in ${JSON.stringify(
103
+ status.data.services,
104
+ null,
105
+ 2,
106
+ )}`,
107
+ );
108
+ }
109
+ }
110
+ }
111
+ return { appDetails, services };
112
+ }
113
+
114
+ export async function getAppLogs(props: {
115
+ projectId: string;
116
+ services?: string[];
117
+ args?: string[];
118
+ }): Promise<NodeJS.ReadableStream> {
119
+ const { projectId, services, args } = props;
120
+ await requireAppInstalled({ projectId });
121
+
122
+ const appDir = getAppDir(projectId);
123
+
124
+ const serviceList = services
125
+ ? services
126
+ : await (async function () {
127
+ const composeServices = await compose.configServices({ cwd: appDir });
128
+ return composeServices.data.services;
129
+ })();
130
+
131
+ const argsList = args ? args : [];
132
+
133
+ // Use direct command with spawner in order to get a readable stream
134
+ return await JsSpawner().runStreaming({
135
+ exe: 'docker-compose',
136
+ args: ['logs', '-f', ...argsList, ...serviceList],
137
+ cwd: appDir,
138
+ });
139
+ }
140
+
141
+ export async function startApp(props: {
142
+ projectId: string;
143
+ dockerLoginToken?: string;
144
+ }): Promise<void> {
145
+ const { projectId, dockerLoginToken } = props;
146
+ await requireAppInstalled({ projectId });
147
+
148
+ const appDir = getAppDir(projectId);
149
+ if (dockerLoginToken !== undefined) {
150
+ const result = await runDockerLogin({ token: dockerLoginToken });
151
+ logger.info(result);
152
+ }
153
+
154
+ // TODO: Check if app is running
155
+ // Start app
156
+ const upOut = await compose.upAll({ cwd: appDir });
157
+ logger.info(upOut);
158
+ if (upOut.exitCode !== 0) {
159
+ throw new Error(
160
+ `Failed to start application! stdout=${upOut.out} stderr=${upOut.err}`,
161
+ );
162
+ }
163
+ logger.info('Started', projectId);
164
+ }
165
+
166
+ export async function stopApp(props: { projectId: string }): Promise<void> {
167
+ const { projectId } = props;
168
+ await requireAppInstalled({ projectId });
169
+
170
+ 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
+ );
179
+ }
180
+ logger.info('Stopped', projectId);
181
+ }
182
+
183
+ export async function restartApp(props: { projectId: string }): Promise<void> {
184
+ const { projectId } = props;
185
+ await stopApp({ projectId });
186
+ await startApp({ projectId });
187
+ }
@@ -0,0 +1 @@
1
+ export type ModelDetails = { modelId: string; version: number };
@@ -0,0 +1,95 @@
1
+ import compose from 'docker-compose';
2
+ import * as path from 'path';
3
+ import * as fs from 'fs';
4
+ import { AAI_DIR, TARGET_JSON_FILE_NAME } from 'alwaysai/lib/constants';
5
+
6
+ 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 { runInDir } from '../util/run-in-dir';
11
+ import { JsSpawner } from 'alwaysai/lib/util';
12
+ import { logger } from '../util/logger';
13
+
14
+ export const APP_ROOT = path.join(AAI_DIR, 'applications');
15
+
16
+ export function getAppDir(projectId: string): string {
17
+ return path.join(APP_ROOT, projectId);
18
+ }
19
+
20
+ export async function requireAppInstalled(props: { projectId: string }) {
21
+ const { projectId } = props;
22
+ // Ensure an app that is being installed or updated is not modified
23
+ if (!(await AgentConfigFile().isAppPresent({ projectId }))) {
24
+ throw new Error('Application is not installed');
25
+ }
26
+ if (!(await AgentConfigFile().isAppReady({ projectId }))) {
27
+ throw new Error('Application is not done installing or updating');
28
+ }
29
+ }
30
+
31
+ export async function buildApp(props: { appDir: string }) {
32
+ const { appDir } = props;
33
+
34
+ // Build standalone image and docker-compose
35
+ const targetJson = TargetJsonFile(appDir);
36
+ const targetConfig = targetJson.read();
37
+ if (targetConfig.targetProtocol !== 'docker:') {
38
+ throw new Error(`${TARGET_JSON_FILE_NAME} is not properly configured!`);
39
+ }
40
+ 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
+ );
54
+
55
+ const buildOut = await compose.buildAll({ cwd: appDir });
56
+ logger.info(buildOut);
57
+ if (buildOut.exitCode !== 0) {
58
+ throw new Error(
59
+ `Failed to build application! stdout=${buildOut.out} stderr=${buildOut.err}`,
60
+ );
61
+ }
62
+ }
63
+
64
+ export async function downloadPackageUsingPresignedUrl(props: {
65
+ localDest: string;
66
+ presignedUrl: string;
67
+ }): Promise<void> {
68
+ logger.info('downloading URL');
69
+ 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
73
+ throw new Error(
74
+ `Status Code: ${response.status}, ${response.statusText}. Response: ${response.body}`,
75
+ );
76
+ }
77
+
78
+ /**
79
+ * Write the app package to the hash directory
80
+ */
81
+ const stream = response.body.pipe(fs.createWriteStream(localDest));
82
+ await new Promise((resolve, reject) => {
83
+ stream.on('finish', resolve);
84
+ stream.on('error', reject);
85
+ });
86
+ }
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
+ }