@alwaysai/device-agent 0.0.7 → 0.0.9

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 (77) 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 +6 -14
  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 +16 -10
  16. package/lib/application-control/install.d.ts.map +1 -1
  17. package/lib/application-control/install.js +95 -57
  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 +96 -20
  22. package/lib/application-control/models.js.map +1 -1
  23. package/lib/application-control/status.d.ts +3 -2
  24. package/lib/application-control/status.d.ts.map +1 -1
  25. package/lib/application-control/status.js +8 -6
  26. package/lib/application-control/status.js.map +1 -1
  27. package/lib/application-control/utils.d.ts +5 -0
  28. package/lib/application-control/utils.d.ts.map +1 -1
  29. package/lib/application-control/utils.js +47 -13
  30. package/lib/application-control/utils.js.map +1 -1
  31. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +42 -15
  32. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  33. package/lib/cloud-connection/device-agent-cloud-connection.js +357 -195
  34. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  35. package/lib/infrastructure/agent-config.d.ts.map +1 -1
  36. package/lib/infrastructure/agent-config.js +7 -18
  37. package/lib/infrastructure/agent-config.js.map +1 -1
  38. package/lib/infrastructure/agent-config.test.js +47 -0
  39. package/lib/infrastructure/agent-config.test.js.map +1 -1
  40. package/lib/subcommands/app/app.js +1 -1
  41. package/lib/subcommands/app/app.js.map +1 -1
  42. package/lib/subcommands/login.d.ts.map +1 -1
  43. package/lib/subcommands/login.js +4 -3
  44. package/lib/subcommands/login.js.map +1 -1
  45. package/lib/util/copy-dir.d.ts.map +1 -1
  46. package/lib/util/copy-dir.js +3 -1
  47. package/lib/util/copy-dir.js.map +1 -1
  48. package/lib/util/run-in-dir.d.ts +2 -0
  49. package/lib/util/run-in-dir.d.ts.map +1 -0
  50. package/lib/util/run-in-dir.js +17 -0
  51. package/lib/util/run-in-dir.js.map +1 -0
  52. package/lib/util/sleep.d.ts +2 -0
  53. package/lib/util/sleep.d.ts.map +1 -0
  54. package/lib/util/sleep.js +9 -0
  55. package/lib/util/sleep.js.map +1 -0
  56. package/package.json +4 -3
  57. package/src/application-control/backup.ts +3 -0
  58. package/src/application-control/config.ts +61 -0
  59. package/src/application-control/environment-variables.ts +6 -12
  60. package/src/application-control/index.ts +5 -0
  61. package/src/application-control/install.ts +147 -68
  62. package/src/application-control/models.ts +136 -23
  63. package/src/application-control/status.ts +19 -9
  64. package/src/application-control/utils.ts +58 -13
  65. package/src/cloud-connection/device-agent-cloud-connection.ts +459 -216
  66. package/src/infrastructure/agent-config.test.ts +56 -0
  67. package/src/infrastructure/agent-config.ts +10 -19
  68. package/src/subcommands/app/app.ts +1 -1
  69. package/src/subcommands/login.ts +6 -4
  70. package/src/util/copy-dir.ts +3 -1
  71. package/src/util/run-in-dir.ts +15 -0
  72. package/src/util/sleep.ts +5 -0
  73. package/lib/util/run-cli-cmd.d.ts +0 -5
  74. package/lib/util/run-cli-cmd.d.ts.map +0 -1
  75. package/lib/util/run-cli-cmd.js +0 -24
  76. package/lib/util/run-cli-cmd.js.map +0 -1
  77. package/src/util/run-cli-cmd.ts +0 -18
@@ -1,19 +1,42 @@
1
1
  import * as rimraf from 'rimraf';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
- import nodeFetch, { Response } from 'node-fetch';
5
- import { JsSpawner } from 'alwaysai/lib/util';
6
-
7
- import { runCliCmd } from '../util/run-cli-cmd';
8
- import { buildApp, getAppDir } from './utils';
9
- import { AppDetails } from '@alwaysai/device-agent-schemas';
4
+ import { JsSpawner, Spawner } from 'alwaysai/lib/util';
5
+ import { getAppDir, downloadPackageUsingPresignedUrl, buildApp } from './utils';
6
+ import { AppDetailsPacket, ModelInstallPayload } from '@alwaysai/device-agent-schemas';
10
7
  import { BACKUP_EXT, createAppBackup } from './backup';
11
8
  import { stopApp } from './status';
12
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
+
26
+ type SignedUrlPayloadType = {
27
+ appInstallPayload: {
28
+ appSignedUrl: string;
29
+ };
30
+ modelsInstallPayload: {
31
+ id: string;
32
+ version: number;
33
+ modelSignedUrl: string;
34
+ }[];
35
+ };
13
36
 
14
- export async function getInstalledApps(): Promise<AppDetails[]> {
37
+ export async function getInstalledApps(): Promise<AppDetailsPacket[]> {
15
38
  const apps = await AgentConfigFile().getApps();
16
- const appDetails: AppDetails[] = [];
39
+ const appDetails: AppDetailsPacket[] = [];
17
40
  for (const app of apps) {
18
41
  const { projectId, version } = app;
19
42
  appDetails.push({ projectId, version });
@@ -21,89 +44,145 @@ export async function getInstalledApps(): Promise<AppDetails[]> {
21
44
  return appDetails;
22
45
  }
23
46
 
24
- export type InstallAppProps = {
47
+ export async function installApp(props: {
25
48
  projectId: string;
26
- releaseHash: string;
27
- presignedAppUrl?: string;
28
- };
29
-
30
- export async function installAppPackage({ localDest, presignedUrl }): Promise<void> {
31
- const response = await nodeFetch(presignedUrl);
32
- if (response.status !== 200) {
33
- // If the URL is invalid; I think we shouldn't get here with the new changes
34
- throw new Error(
35
- `Status Code: ${response.status}, ${response.statusText}. Response: ${response.body}`,
36
- );
37
- }
38
-
39
- /**
40
- * Write the app package to the hash directory
41
- */
42
- const stream = response.body.pipe(fs.createWriteStream(localDest));
43
- await new Promise((resolve, reject) => {
44
- stream.on('finish', resolve);
45
- stream.on('error', reject);
46
- });
47
- }
49
+ appReleaseHash: string;
50
+ signedUrlsPayload?: SignedUrlPayloadType;
51
+ }): Promise<void> {
52
+ const { projectId, appReleaseHash, signedUrlsPayload } = props;
48
53
 
49
- export async function installApp(props: InstallAppProps): Promise<void> {
50
- const { projectId, releaseHash, presignedAppUrl } = props;
51
54
  const appDir = getAppDir(projectId);
52
55
  const spawner = JsSpawner();
53
56
  if (await AgentConfigFile().isAppPresent({ projectId })) {
54
57
  if (!(await AgentConfigFile().isAppReady({ projectId }))) {
55
58
  throw new Error('Application already has installation in progress!');
56
59
  }
57
- await AgentConfigFile().setAppInstalling({ projectId, version: releaseHash });
58
60
  console.log('Application is already installed, updating');
59
- await createAppBackup({ projectId });
61
+ await AgentConfigFile().setAppInstalling({ projectId, version: appReleaseHash });
60
62
  await spawner.rimraf(appDir);
61
63
  } else {
62
- await AgentConfigFile().setAppInstalling({ projectId, version: releaseHash });
64
+ await AgentConfigFile().setAppInstalling({ projectId, version: appReleaseHash });
63
65
  }
64
66
 
65
- /**
66
- * Prep directory and fetch the app package
67
- */
68
67
  await spawner.mkdirp(appDir);
69
- const localDest = path.join(appDir, `${path.basename(releaseHash)}.tgz`);
70
- if (!presignedAppUrl) {
71
- // Download the application using the CLI
72
- await runCliCmd({
73
- cmd: [
74
- 'release',
75
- 'pull',
76
- '--yes',
77
- '--project',
78
- projectId,
79
- '--releaseHash',
80
- releaseHash,
68
+
69
+ // download app package
70
+ const localDest = path.join(appDir, `${path.basename(appReleaseHash)}.tgz`);
71
+ if (!signedUrlsPayload) {
72
+ await runInDir(
73
+ appReleasePullComponent,
74
+ [
75
+ {
76
+ yes: true,
77
+ project: projectId,
78
+ releaseHash: appReleaseHash,
79
+ },
81
80
  ],
82
- cwd: appDir,
83
- });
81
+ appDir,
82
+ );
84
83
  } else {
85
- await installAppPackage({ localDest, presignedUrl: presignedAppUrl });
84
+ const { appSignedUrl } = signedUrlsPayload.appInstallPayload;
85
+ await downloadPackageUsingPresignedUrl({ localDest, presignedUrl: appSignedUrl });
86
86
  }
87
87
 
88
- /**
89
- * Unpack app package
90
- */
91
- await spawner.untar(fs.createReadStream(localDest), appDir);
92
- console.log(`Unpacked application to ${appDir}`);
88
+ // Unpack app package and remove tar file
89
+ await unPackApp({ spawner, localDest, appDir });
90
+
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 });
96
+
97
+ // install models, python venv, and docker image
98
+ if (!signedUrlsPayload) {
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
+ );
112
+ } else {
113
+ await installAppBuildReqs({ appDir });
114
+ await installModelsWithPresignedURLs(
115
+ signedUrlsPayload.modelsInstallPayload,
116
+ path.join(appDir, 'models'),
117
+ );
118
+ }
93
119
 
94
- /**
95
- * Build app
96
- * ToDo: migrate the model pull to use MQTT workflow
97
- */
98
120
  await buildApp({ appDir });
99
121
 
100
- /**
101
- * Remove tar file
102
- */
122
+ await AgentConfigFile().setAppInstalled({ projectId, version: appReleaseHash });
123
+
124
+ console.log(
125
+ `Installed app version ${appReleaseHash} from project ${projectId} to ${appDir}.`,
126
+ );
127
+ }
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);
103
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
+ }
104
171
 
105
- await AgentConfigFile().setAppInstalled({ projectId, version: releaseHash });
106
- console.log(`Installed ${projectId} to ${appDir}`);
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
+ });
107
186
  }
108
187
 
109
188
  export async function uninstallApp(props: { projectId: string }): Promise<void> {
@@ -1,14 +1,32 @@
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';
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';
4
16
 
5
- import { runCliCmd } from '../util/run-cli-cmd';
6
17
  import { ModelDetails } from './types';
7
- import { buildApp, getAppDir, requireAppInstalled } from './utils';
18
+ import {
19
+ buildApp,
20
+ downloadPackageUsingPresignedUrl,
21
+ getAppDir,
22
+ requireAppInstalled,
23
+ } from './utils';
8
24
 
9
25
  export async function getAppModels(props: { projectId: string }) {
10
26
  const { projectId } = props;
11
- await requireAppInstalled({ projectId });
27
+ if (!(await AgentConfigFile().isAppPresent({ projectId }))) {
28
+ throw new Error('Application is not installed');
29
+ }
12
30
 
13
31
  const appDir = getAppDir(projectId);
14
32
  const appCfgPath = join(appDir, 'alwaysai.app.json');
@@ -31,9 +49,11 @@ export async function addModel(props: { projectId: string; modelId: string }) {
31
49
  await requireAppInstalled({ projectId });
32
50
 
33
51
  const appDir = getAppDir(projectId);
34
- await runCliCmd({
35
- cmd: ['app', 'models', 'add', modelId],
36
- cwd: appDir,
52
+ await appModelsAddComponent({
53
+ yes: false,
54
+ dir: appDir,
55
+ id: modelId,
56
+ addToProject: false,
37
57
  });
38
58
  await buildApp({ appDir });
39
59
  }
@@ -43,10 +63,18 @@ export async function removeModel(props: { projectId: string; modelId: string })
43
63
  await requireAppInstalled({ projectId });
44
64
 
45
65
  const appDir = getAppDir(projectId);
46
- await runCliCmd({
47
- cmd: ['app', 'models', 'remove', modelId, '--purge'],
48
- cwd: appDir,
49
- });
66
+
67
+ await runInDir(
68
+ appModelsRemoveComponent,
69
+ [
70
+ {
71
+ id: modelId,
72
+ purge: true,
73
+ removeFromProject: false,
74
+ },
75
+ ],
76
+ appDir,
77
+ );
50
78
  }
51
79
 
52
80
  export async function replaceModels(props: { projectId: string; modelIds: string[] }) {
@@ -54,14 +82,23 @@ export async function replaceModels(props: { projectId: string; modelIds: string
54
82
  await requireAppInstalled({ projectId });
55
83
 
56
84
  const appDir = getAppDir(projectId);
57
- await runCliCmd({
58
- cmd: ['app', 'models', 'remove-all', '--purge'],
59
- cwd: appDir,
60
- });
85
+
86
+ await runInDir(
87
+ appModelsRemoveAllComponent,
88
+ [
89
+ {
90
+ purge: true,
91
+ removeFromProject: false,
92
+ },
93
+ ],
94
+ appDir,
95
+ );
61
96
  for (const modelId of modelIds) {
62
- await runCliCmd({
63
- cmd: ['app', 'models', 'add', modelId],
64
- cwd: appDir,
97
+ await appModelsAddComponent({
98
+ yes: false,
99
+ dir: appDir,
100
+ id: modelId,
101
+ addToProject: false,
65
102
  });
66
103
  }
67
104
  await buildApp({ appDir });
@@ -72,9 +109,85 @@ export async function updateModels(props: { projectId: string }) {
72
109
  await requireAppInstalled({ projectId });
73
110
 
74
111
  const appDir = getAppDir(projectId);
75
- await runCliCmd({
76
- cmd: ['app', 'models', 'update'],
77
- cwd: appDir,
112
+ await appModelsUpdateComponent({
113
+ yes: false,
114
+ dir: appDir,
78
115
  });
79
116
  await buildApp({ appDir });
80
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
+ }
@@ -4,7 +4,12 @@ import { JsSpawner } from 'alwaysai/lib/util';
4
4
 
5
5
  import { runDockerLogin } from '../docker/docker-cmd';
6
6
  import { getAppDir, requireAppInstalled } from './utils';
7
- import { AppStateValue, ServiceStatus, AppState } from '@alwaysai/device-agent-schemas';
7
+ import {
8
+ ServiceStatusPacket,
9
+ AppStatePacket,
10
+ keyMirrors,
11
+ AppStateValue,
12
+ } from '@alwaysai/device-agent-schemas';
8
13
  import { AgentConfigFile } from '../infrastructure/agent-config';
9
14
 
10
15
  export async function listAppReleases(props: { projectId: string }) {
@@ -22,7 +27,9 @@ export async function listAppLatestRelease(props: { projectId: string }) {
22
27
  return undefined;
23
28
  }
24
29
 
25
- export async function getAppStatus(props: { projectId: string }): Promise<AppState> {
30
+ export async function getAppStatus(props: {
31
+ projectId: string;
32
+ }): Promise<AppStatePacket> {
26
33
  const { projectId } = props;
27
34
  if (!(await AgentConfigFile().isAppPresent({ projectId }))) {
28
35
  throw new Error('Application is not installed');
@@ -49,7 +56,7 @@ export async function getAppStatus(props: { projectId: string }): Promise<AppSta
49
56
  );
50
57
  }
51
58
 
52
- const services: ServiceStatus[] = [];
59
+ const services: ServiceStatusPacket[] = [];
53
60
  const spawner = JsSpawner({ path: appDir });
54
61
  for (const name of composeServices.data.services) {
55
62
  // Get container name for service
@@ -60,7 +67,7 @@ export async function getAppStatus(props: { projectId: string }): Promise<AppSta
60
67
  });
61
68
  if (containerId === '') {
62
69
  // The service was not yet started or failed to start
63
- services.push({ name, state: 'Stopped' });
70
+ services.push({ name, state: keyMirrors.appState.stopped });
64
71
  } else {
65
72
  const slashContainerName = await spawner.run({
66
73
  exe: 'docker',
@@ -75,14 +82,14 @@ export async function getAppStatus(props: { projectId: string }): Promise<AppSta
75
82
  }
76
83
  let state: AppStateValue;
77
84
  if (container.state.includes('Up')) {
78
- state = 'Up';
85
+ state = keyMirrors.appState.up;
79
86
  } else if (
80
87
  container.state.includes('Stopped') ||
81
88
  container.state.includes('Exit')
82
89
  ) {
83
- state = 'Stopped';
90
+ state = keyMirrors.appState.stopped;
84
91
  } else if (container.state.includes('Restarting')) {
85
- state = 'Restarting';
92
+ state = keyMirrors.appState.restarting;
86
93
  } else {
87
94
  throw new Error(`Unknown state for service! (${container.state})`);
88
95
  }
@@ -106,8 +113,9 @@ export async function getAppStatus(props: { projectId: string }): Promise<AppSta
106
113
  export async function getAppLogs(props: {
107
114
  projectId: string;
108
115
  services?: string[];
116
+ args?: string[];
109
117
  }): Promise<NodeJS.ReadableStream> {
110
- const { projectId, services } = props;
118
+ const { projectId, services, args } = props;
111
119
  await requireAppInstalled({ projectId });
112
120
 
113
121
  const appDir = getAppDir(projectId);
@@ -119,10 +127,12 @@ export async function getAppLogs(props: {
119
127
  return composeServices.data.services;
120
128
  })();
121
129
 
130
+ const argsList = args ? args : [];
131
+
122
132
  // Use direct command with spawner in order to get a readable stream
123
133
  return await JsSpawner().runStreaming({
124
134
  exe: 'docker-compose',
125
- args: ['logs', '-f', ...serviceList],
135
+ args: ['logs', '-f', ...argsList, ...serviceList],
126
136
  cwd: appDir,
127
137
  });
128
138
  }
@@ -1,13 +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';
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';
8
12
 
9
13
  export const APP_ROOT = path.join(AAI_DIR, 'applications');
10
- const DOCKER_COMPOSE_FILENAME = 'docker-compose.yaml';
11
14
 
12
15
  export function getAppDir(projectId: string): string {
13
16
  return path.join(APP_ROOT, projectId);
@@ -26,24 +29,66 @@ export async function requireAppInstalled(props: { projectId: string }) {
26
29
 
27
30
  export async function buildApp(props: { appDir: string }) {
28
31
  const { appDir } = props;
29
- await runCliCmd({
30
- cmd: ['app', 'configure', '--yes', '--protocol', 'docker:', '--skip-model-sync'],
31
- cwd: appDir,
32
- });
33
- await runCliCmd({ cmd: ['app', 'install', '--yes', '--clean', '--pull'], cwd: appDir });
34
32
 
35
33
  // Build standalone image and docker-compose
36
- if (!fs.existsSync(path.join(appDir, DOCKER_COMPOSE_FILENAME))) {
37
- await runCliCmd({
38
- cmd: ['app', 'deploy', '--yes', '--generateDockerCompose'],
39
- cwd: appDir,
40
- });
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!`);
41
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
+ );
42
53
 
43
54
  const buildOut = await compose.buildAll({ cwd: appDir });
55
+ console.log(buildOut);
44
56
  if (buildOut.exitCode !== 0) {
45
57
  throw new Error(
46
58
  `Failed to build application! stdout=${buildOut.out} stderr=${buildOut.err}`,
47
59
  );
48
60
  }
49
61
  }
62
+
63
+ export async function downloadPackageUsingPresignedUrl(props: {
64
+ localDest: string;
65
+ presignedUrl: string;
66
+ }): Promise<void> {
67
+ console.log('downloading URL');
68
+ const { localDest, presignedUrl } = props;
69
+ const response = await nodeFetch(presignedUrl);
70
+ if (response.status !== 200) {
71
+ // If the URL is invalid; I think we shouldn't get here with the new changes
72
+ throw new Error(
73
+ `Status Code: ${response.status}, ${response.statusText}. Response: ${response.body}`,
74
+ );
75
+ }
76
+
77
+ /**
78
+ * Write the app package to the hash directory
79
+ */
80
+ const stream = response.body.pipe(fs.createWriteStream(localDest));
81
+ await new Promise((resolve, reject) => {
82
+ stream.on('finish', resolve);
83
+ stream.on('error', reject);
84
+ });
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
+ }