@alwaysai/device-agent 0.0.8 → 0.0.10

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 (64) hide show
  1. package/lib/application-control/backup.d.ts.map +1 -1
  2. package/lib/application-control/backup.js +2 -0
  3. package/lib/application-control/backup.js.map +1 -1
  4. package/lib/application-control/config.d.ts +17 -0
  5. package/lib/application-control/config.d.ts.map +1 -0
  6. package/lib/application-control/config.js +62 -0
  7. package/lib/application-control/config.js.map +1 -0
  8. package/lib/application-control/environment-variables.d.ts.map +1 -1
  9. package/lib/application-control/environment-variables.js +4 -12
  10. package/lib/application-control/environment-variables.js.map +1 -1
  11. package/lib/application-control/index.d.ts +2 -1
  12. package/lib/application-control/index.d.ts.map +1 -1
  13. package/lib/application-control/index.js +6 -1
  14. package/lib/application-control/index.js.map +1 -1
  15. package/lib/application-control/install.d.ts +12 -10
  16. package/lib/application-control/install.d.ts.map +1 -1
  17. package/lib/application-control/install.js +79 -41
  18. package/lib/application-control/install.js.map +1 -1
  19. package/lib/application-control/models.d.ts +3 -0
  20. package/lib/application-control/models.d.ts.map +1 -1
  21. package/lib/application-control/models.js +92 -19
  22. package/lib/application-control/models.js.map +1 -1
  23. package/lib/application-control/utils.d.ts +4 -3
  24. package/lib/application-control/utils.d.ts.map +1 -1
  25. package/lib/application-control/utils.js +30 -10
  26. package/lib/application-control/utils.js.map +1 -1
  27. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +16 -9
  28. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  29. package/lib/cloud-connection/device-agent-cloud-connection.js +165 -89
  30. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  31. package/lib/infrastructure/agent-config.d.ts.map +1 -1
  32. package/lib/infrastructure/agent-config.js +7 -18
  33. package/lib/infrastructure/agent-config.js.map +1 -1
  34. package/lib/infrastructure/agent-config.test.js +47 -0
  35. package/lib/infrastructure/agent-config.test.js.map +1 -1
  36. package/lib/subcommands/login.d.ts.map +1 -1
  37. package/lib/subcommands/login.js +4 -3
  38. package/lib/subcommands/login.js.map +1 -1
  39. package/lib/util/copy-dir.d.ts.map +1 -1
  40. package/lib/util/copy-dir.js +3 -1
  41. package/lib/util/copy-dir.js.map +1 -1
  42. package/lib/util/run-in-dir.d.ts +2 -0
  43. package/lib/util/run-in-dir.d.ts.map +1 -0
  44. package/lib/util/run-in-dir.js +17 -0
  45. package/lib/util/run-in-dir.js.map +1 -0
  46. package/package.json +4 -3
  47. package/src/application-control/backup.ts +3 -0
  48. package/src/application-control/config.ts +61 -0
  49. package/src/application-control/environment-variables.ts +4 -10
  50. package/src/application-control/index.ts +5 -0
  51. package/src/application-control/install.ts +121 -52
  52. package/src/application-control/models.ts +132 -22
  53. package/src/application-control/utils.ts +37 -12
  54. package/src/cloud-connection/device-agent-cloud-connection.ts +197 -105
  55. package/src/infrastructure/agent-config.test.ts +56 -0
  56. package/src/infrastructure/agent-config.ts +10 -19
  57. package/src/subcommands/login.ts +6 -4
  58. package/src/util/copy-dir.ts +3 -1
  59. package/src/util/run-in-dir.ts +15 -0
  60. package/lib/util/run-cli-cmd.d.ts +0 -5
  61. package/lib/util/run-cli-cmd.d.ts.map +0 -1
  62. package/lib/util/run-cli-cmd.js +0 -24
  63. package/lib/util/run-cli-cmd.js.map +0 -1
  64. package/src/util/run-cli-cmd.ts +0 -18
@@ -1,13 +1,38 @@
1
1
  import * as rimraf from 'rimraf';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
- import { JsSpawner } from 'alwaysai/lib/util';
4
+ import { JsSpawner, Spawner } from 'alwaysai/lib/util';
5
5
  import { getAppDir, downloadPackageUsingPresignedUrl, buildApp } from './utils';
6
6
  import { AppDetailsPacket, ModelInstallPayload } from '@alwaysai/device-agent-schemas';
7
7
  import { BACKUP_EXT, createAppBackup } from './backup';
8
8
  import { stopApp } from './status';
9
9
  import { AgentConfigFile } from '../infrastructure/agent-config';
10
- import { runCliCmd } from '../util/run-cli-cmd';
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
+
26
+ type SignedUrlPayloadType = {
27
+ appInstallPayload: {
28
+ appSignedUrl: string;
29
+ };
30
+ modelsInstallPayload: {
31
+ id: string;
32
+ version: number;
33
+ modelSignedUrl: string;
34
+ }[];
35
+ };
11
36
 
12
37
  export async function getInstalledApps(): Promise<AppDetailsPacket[]> {
13
38
  const apps = await AgentConfigFile().getApps();
@@ -22,16 +47,7 @@ export async function getInstalledApps(): Promise<AppDetailsPacket[]> {
22
47
  export async function installApp(props: {
23
48
  projectId: string;
24
49
  appReleaseHash: string;
25
- signedUrlsPayload?: {
26
- appInstallPayload: {
27
- appSignedUrl: string;
28
- };
29
- modelsInstallPayload: {
30
- id: string;
31
- version: number;
32
- modelSignedUrl: string;
33
- }[];
34
- };
50
+ signedUrlsPayload?: SignedUrlPayloadType;
35
51
  }): Promise<void> {
36
52
  const { projectId, appReleaseHash, signedUrlsPayload } = props;
37
53
 
@@ -41,9 +57,8 @@ export async function installApp(props: {
41
57
  if (!(await AgentConfigFile().isAppReady({ projectId }))) {
42
58
  throw new Error('Application already has installation in progress!');
43
59
  }
44
- await AgentConfigFile().setAppInstalling({ projectId, version: appReleaseHash });
45
60
  console.log('Application is already installed, updating');
46
- await createAppBackup({ projectId });
61
+ await AgentConfigFile().setAppInstalling({ projectId, version: appReleaseHash });
47
62
  await spawner.rimraf(appDir);
48
63
  } else {
49
64
  await AgentConfigFile().setAppInstalling({ projectId, version: appReleaseHash });
@@ -54,56 +69,51 @@ export async function installApp(props: {
54
69
  // download app package
55
70
  const localDest = path.join(appDir, `${path.basename(appReleaseHash)}.tgz`);
56
71
  if (!signedUrlsPayload) {
57
- await runCliCmd({
58
- cmd: [
59
- 'release',
60
- 'pull',
61
- '--yes',
62
- '--project',
63
- projectId,
64
- '--releaseHash',
65
- appReleaseHash,
72
+ await runInDir(
73
+ appReleasePullComponent,
74
+ [
75
+ {
76
+ yes: true,
77
+ project: projectId,
78
+ releaseHash: appReleaseHash,
79
+ },
66
80
  ],
67
- cwd: appDir,
68
- });
81
+ appDir,
82
+ );
69
83
  } else {
70
84
  const { appSignedUrl } = signedUrlsPayload.appInstallPayload;
71
85
  await downloadPackageUsingPresignedUrl({ localDest, presignedUrl: appSignedUrl });
72
86
  }
73
87
 
74
88
  // Unpack app package and remove tar file
75
- await spawner.untar(fs.createReadStream(localDest), appDir);
76
- await spawner.rimraf(localDest);
89
+ await unPackApp({ spawner, localDest, appDir });
77
90
 
78
- // write target json
79
- await runCliCmd({
80
- cmd: ['app', 'configure', '--yes', '--protocol', 'docker:', '--skip-model-sync'],
81
- cwd: appDir,
82
- });
91
+ // The following changes replace the runCliCmd for 'app configure'
92
+ // This part replaces findOrWriteProjectJsonFile() due to the
93
+ // calls that check the user is logged in and also for project collaboration
94
+ // NOTE: this process no longer checks project collaboration
95
+ await checkValidProjectFiles({ appDir });
83
96
 
84
97
  // install models, python venv, and docker image
85
98
  if (!signedUrlsPayload) {
86
- await runCliCmd({
87
- cmd: ['app', 'install', '--yes', '--clean', '--pull'],
88
- cwd: appDir,
89
- });
99
+ await runInDir(
100
+ appInstallComponent,
101
+ [
102
+ {
103
+ yes: true,
104
+ pull: true,
105
+ clean: true,
106
+ skipModels: false,
107
+ source: false,
108
+ },
109
+ ],
110
+ appDir,
111
+ );
90
112
  } else {
91
- await runCliCmd({
92
- cmd: ['app', 'install', '--yes', '--clean', '--pull', '--skip-models'],
93
- cwd: appDir,
94
- });
95
- await Promise.all(
96
- signedUrlsPayload.modelsInstallPayload.map(async (payload: ModelInstallPayload) => {
97
- const modelDest = `${appDir}/models/${payload.id}`;
98
- await spawner.mkdirp(modelDest);
99
- const localDest = `${modelDest}/${payload.version}.tar.gz`;
100
- await downloadPackageUsingPresignedUrl({
101
- localDest,
102
- presignedUrl: payload.modelSignedUrl,
103
- });
104
- await spawner.untar(fs.createReadStream(localDest), path.dirname(modelDest));
105
- await spawner.rimraf(localDest);
106
- }),
113
+ await installAppBuildReqs({ appDir });
114
+ await installModelsWithPresignedURLs(
115
+ signedUrlsPayload.modelsInstallPayload,
116
+ path.join(appDir, 'models'),
107
117
  );
108
118
  }
109
119
 
@@ -116,6 +126,65 @@ export async function installApp(props: {
116
126
  );
117
127
  }
118
128
 
129
+ async function installAppBuildReqs(props: { appDir: string }) {
130
+ const { appDir } = props;
131
+ const targetJsonFile = TargetJsonFile(appDir);
132
+ const targetJson = targetJsonFile.readIfExists();
133
+ if (!targetJson) {
134
+ throw new Error('Target json file does not exist!');
135
+ }
136
+
137
+ await runInDir(
138
+ async () => {
139
+ const targetHostSpawner = targetJsonFile.readHostSpawner();
140
+
141
+ await appCleanDockerComponent({ targetHostSpawner });
142
+
143
+ await buildDocker({
144
+ targetJson,
145
+ targetJsonFile,
146
+ targetHostSpawner,
147
+ pull: true,
148
+ });
149
+
150
+ await installVenv({
151
+ targetJson,
152
+ sourceSpawner: targetHostSpawner,
153
+ targetJsonFile,
154
+ });
155
+ },
156
+ [],
157
+ appDir,
158
+ );
159
+ }
160
+
161
+ async function unPackApp(props: { spawner: Spawner; localDest: string; appDir: string }) {
162
+ const { spawner, localDest, appDir } = props;
163
+ await spawner.untar(fs.createReadStream(localDest), appDir);
164
+ await spawner.rimraf(localDest);
165
+ }
166
+
167
+ async function checkValidProjectFiles({ appDir }) {
168
+ if (!ProjectJsonFile(appDir).readIfExists()) {
169
+ throw new Error('Project JSON file does not exist!');
170
+ }
171
+
172
+ // check for app json file and app.py files
173
+ if (!AppJsonFile(appDir).readIfExists()) {
174
+ throw new Error('App JSON file does not exist!');
175
+ }
176
+
177
+ // write target json
178
+ if (!fs.existsSync(path.join(appDir, DOCKERFILE))) {
179
+ throw new Error('No Dockerfile found for application!');
180
+ }
181
+ TargetJsonFile(appDir).write({
182
+ targetProtocol: 'docker:',
183
+ dockerImageId: DOCKER_IMAGE_ID_INITIAL_VALUE,
184
+ targetHardware: await getTargetHardwareType({}),
185
+ });
186
+ }
187
+
119
188
  export async function uninstallApp(props: { projectId: string }): Promise<void> {
120
189
  const { projectId } = props;
121
190
  if (!(await AgentConfigFile().isAppPresent({ projectId }))) {
@@ -1,11 +1,26 @@
1
- import { JsSpawner } from 'alwaysai/lib/util';
2
- import { existsSync } from 'fs';
3
- import { join } from 'path';
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, logger } from 'alwaysai/lib/util';
10
+ import { existsSync, createReadStream } from 'fs';
11
+ import { join, dirname } from 'path';
4
12
  import { AgentConfigFile } from '../infrastructure/agent-config';
13
+ import { copyDir } from '../util/copy-dir';
14
+ import { runInDir } from '../util/run-in-dir';
15
+ import { getAppStatus, restartApp } from './status';
5
16
 
6
- import { runCliCmd } from '../util/run-cli-cmd';
7
17
  import { ModelDetails } from './types';
8
- import { buildApp, getAppDir, requireAppInstalled } from './utils';
18
+ import {
19
+ buildApp,
20
+ downloadPackageUsingPresignedUrl,
21
+ getAppDir,
22
+ requireAppInstalled,
23
+ } from './utils';
9
24
 
10
25
  export async function getAppModels(props: { projectId: string }) {
11
26
  const { projectId } = props;
@@ -34,9 +49,11 @@ export async function addModel(props: { projectId: string; modelId: string }) {
34
49
  await requireAppInstalled({ projectId });
35
50
 
36
51
  const appDir = getAppDir(projectId);
37
- await runCliCmd({
38
- cmd: ['app', 'models', 'add', modelId],
39
- cwd: appDir,
52
+ await appModelsAddComponent({
53
+ yes: false,
54
+ dir: appDir,
55
+ id: modelId,
56
+ addToProject: false,
40
57
  });
41
58
  await buildApp({ appDir });
42
59
  }
@@ -46,10 +63,18 @@ export async function removeModel(props: { projectId: string; modelId: string })
46
63
  await requireAppInstalled({ projectId });
47
64
 
48
65
  const appDir = getAppDir(projectId);
49
- await runCliCmd({
50
- cmd: ['app', 'models', 'remove', modelId, '--purge'],
51
- cwd: appDir,
52
- });
66
+
67
+ await runInDir(
68
+ appModelsRemoveComponent,
69
+ [
70
+ {
71
+ id: modelId,
72
+ purge: true,
73
+ removeFromProject: false,
74
+ },
75
+ ],
76
+ appDir,
77
+ );
53
78
  }
54
79
 
55
80
  export async function replaceModels(props: { projectId: string; modelIds: string[] }) {
@@ -57,14 +82,23 @@ export async function replaceModels(props: { projectId: string; modelIds: string
57
82
  await requireAppInstalled({ projectId });
58
83
 
59
84
  const appDir = getAppDir(projectId);
60
- await runCliCmd({
61
- cmd: ['app', 'models', 'remove-all', '--purge'],
62
- cwd: appDir,
63
- });
85
+
86
+ await runInDir(
87
+ appModelsRemoveAllComponent,
88
+ [
89
+ {
90
+ purge: true,
91
+ removeFromProject: false,
92
+ },
93
+ ],
94
+ appDir,
95
+ );
64
96
  for (const modelId of modelIds) {
65
- await runCliCmd({
66
- cmd: ['app', 'models', 'add', modelId],
67
- cwd: appDir,
97
+ await appModelsAddComponent({
98
+ yes: false,
99
+ dir: appDir,
100
+ id: modelId,
101
+ addToProject: false,
68
102
  });
69
103
  }
70
104
  await buildApp({ appDir });
@@ -75,9 +109,85 @@ export async function updateModels(props: { projectId: string }) {
75
109
  await requireAppInstalled({ projectId });
76
110
 
77
111
  const appDir = getAppDir(projectId);
78
- await runCliCmd({
79
- cmd: ['app', 'models', 'update'],
80
- cwd: appDir,
112
+ await appModelsUpdateComponent({
113
+ yes: false,
114
+ dir: appDir,
81
115
  });
82
116
  await buildApp({ appDir });
83
117
  }
118
+
119
+ export async function installModelsWithPresignedURLs(
120
+ modelPayloads: ModelInstallPayload[],
121
+ targetDir: string,
122
+ ) {
123
+ const spawner = JsSpawner();
124
+ await Promise.all(
125
+ modelPayloads.map(async (payload: ModelInstallPayload) => {
126
+ const modelDest = `${targetDir}/${payload.id}`;
127
+ await spawner.mkdirp(modelDest);
128
+ const localDest = `${modelDest}/${payload.version}.tar.gz`;
129
+ await downloadPackageUsingPresignedUrl({
130
+ localDest,
131
+ presignedUrl: payload.modelSignedUrl,
132
+ });
133
+ await spawner.untar(createReadStream(localDest), dirname(modelDest));
134
+ await spawner.rimraf(localDest);
135
+ }),
136
+ );
137
+ }
138
+
139
+ export async function updateModelsWithPresignedUrls(
140
+ project: string,
141
+ modelInstallPayloads: ModelInstallPayload[],
142
+ ) {
143
+ // create temp dir with all untouched models
144
+ const spawner = JsSpawner();
145
+ const appDir = getAppDir(project);
146
+ const ogDir = `${appDir}/models`;
147
+ const tmpDir = `${ogDir}.tmp`;
148
+ const restoreDir = `${ogDir}.restore`;
149
+ await copyDir({ srcPath: ogDir, destPath: restoreDir });
150
+ await spawner.rimraf(tmpDir);
151
+
152
+ try {
153
+ // Move unchanged models
154
+ const existingModels = await getAppModels({ projectId: project });
155
+ for (const model of existingModels) {
156
+ if (
157
+ !modelInstallPayloads
158
+ .map((newModel: ModelInstallPayload) => newModel.id)
159
+ .includes(model.modelId)
160
+ ) {
161
+ // model does not need to be updated
162
+ await copyDir({
163
+ srcPath: `${ogDir}/${model.modelId}`,
164
+ destPath: `${tmpDir}/${model.modelId}`,
165
+ });
166
+ }
167
+ }
168
+
169
+ // install model packages
170
+ await installModelsWithPresignedURLs(modelInstallPayloads, tmpDir);
171
+ await spawner.rimraf(ogDir);
172
+ await copyDir({ srcPath: tmpDir, destPath: ogDir });
173
+ await spawner.rimraf(tmpDir);
174
+
175
+ await buildApp({ appDir: getAppDir(project) });
176
+
177
+ const appStatus = await getAppStatus({ projectId: project });
178
+ const appIsRunning =
179
+ appStatus.services.length &&
180
+ appStatus.services[0].state !== keyMirrors.appState.stopped;
181
+
182
+ if (appIsRunning) {
183
+ restartApp({ projectId: project });
184
+ }
185
+
186
+ console.log(`Models installed for project ${project}`);
187
+ } catch (e) {
188
+ console.error(e, 'Error updating app models from presigned URL, restoring models.');
189
+ await spawner.rimraf(tmpDir);
190
+ await spawner.rimraf(ogDir);
191
+ await copyDir({ srcPath: restoreDir, destPath: ogDir });
192
+ }
193
+ }
@@ -1,14 +1,16 @@
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 } from 'alwaysai/lib/constants';
4
+ import { AAI_DIR, TARGET_JSON_FILE_NAME } from 'alwaysai/lib/constants';
5
5
 
6
- import { runCliCmd } from '../util/run-cli-cmd';
7
6
  import { AgentConfigFile } from '../infrastructure/agent-config';
8
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';
9
12
 
10
13
  export const APP_ROOT = path.join(AAI_DIR, 'applications');
11
- const DOCKER_COMPOSE_FILENAME = 'docker-compose.yaml';
12
14
 
13
15
  export function getAppDir(projectId: string): string {
14
16
  return path.join(APP_ROOT, projectId);
@@ -27,15 +29,27 @@ export async function requireAppInstalled(props: { projectId: string }) {
27
29
 
28
30
  export async function buildApp(props: { appDir: string }) {
29
31
  const { appDir } = props;
30
- console.log(appDir);
31
32
 
32
33
  // Build standalone image and docker-compose
33
- if (!fs.existsSync(path.join(appDir, DOCKER_COMPOSE_FILENAME))) {
34
- await runCliCmd({
35
- cmd: ['app', 'deploy', '--yes', '--generateDockerCompose'],
36
- cwd: appDir,
37
- });
34
+ const targetJson = TargetJsonFile(appDir);
35
+ const targetConfig = targetJson.read();
36
+ if (targetConfig.targetProtocol !== 'docker:') {
37
+ throw new Error(`${TARGET_JSON_FILE_NAME} is not properly configured!`);
38
38
  }
39
+ await runInDir(
40
+ appDeployLinuxAndRemoteDevice,
41
+ [
42
+ {
43
+ yes: true,
44
+ generateDockerCompose: true,
45
+ logs: false,
46
+ stop: false,
47
+ targetJson,
48
+ targetConfig,
49
+ },
50
+ ],
51
+ appDir,
52
+ );
39
53
 
40
54
  const buildOut = await compose.buildAll({ cwd: appDir });
41
55
  console.log(buildOut);
@@ -46,10 +60,12 @@ export async function buildApp(props: { appDir: string }) {
46
60
  }
47
61
  }
48
62
 
49
- export async function downloadPackageUsingPresignedUrl({
50
- localDest,
51
- presignedUrl,
63
+ export async function downloadPackageUsingPresignedUrl(props: {
64
+ localDest: string;
65
+ presignedUrl: string;
52
66
  }): Promise<void> {
67
+ console.log('downloading URL');
68
+ const { localDest, presignedUrl } = props;
53
69
  const response = await nodeFetch(presignedUrl);
54
70
  if (response.status !== 200) {
55
71
  // If the URL is invalid; I think we shouldn't get here with the new changes
@@ -67,3 +83,12 @@ export async function downloadPackageUsingPresignedUrl({
67
83
  stream.on('error', reject);
68
84
  });
69
85
  }
86
+
87
+ export async function getAppConfig(project: string) {
88
+ const appCfgPath = path.join(getAppDir(project), 'alwaysai.app.json');
89
+ if (!fs.existsSync(appCfgPath)) {
90
+ throw new Error(`Application config not found for project ${project}`);
91
+ }
92
+
93
+ return JSON.parse(await JsSpawner().readFile(appCfgPath));
94
+ }