@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.
- package/lib/application-control/backup.d.ts.map +1 -1
- package/lib/application-control/backup.js +2 -0
- package/lib/application-control/backup.js.map +1 -1
- package/lib/application-control/config.d.ts +17 -0
- package/lib/application-control/config.d.ts.map +1 -0
- package/lib/application-control/config.js +62 -0
- package/lib/application-control/config.js.map +1 -0
- package/lib/application-control/environment-variables.d.ts.map +1 -1
- package/lib/application-control/environment-variables.js +6 -14
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/index.d.ts +2 -1
- package/lib/application-control/index.d.ts.map +1 -1
- package/lib/application-control/index.js +6 -1
- package/lib/application-control/index.js.map +1 -1
- package/lib/application-control/install.d.ts +16 -10
- package/lib/application-control/install.d.ts.map +1 -1
- package/lib/application-control/install.js +95 -57
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/models.d.ts +3 -0
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +96 -20
- package/lib/application-control/models.js.map +1 -1
- package/lib/application-control/status.d.ts +3 -2
- package/lib/application-control/status.d.ts.map +1 -1
- package/lib/application-control/status.js +8 -6
- package/lib/application-control/status.js.map +1 -1
- package/lib/application-control/utils.d.ts +5 -0
- package/lib/application-control/utils.d.ts.map +1 -1
- package/lib/application-control/utils.js +47 -13
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +42 -15
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +357 -195
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/infrastructure/agent-config.d.ts.map +1 -1
- package/lib/infrastructure/agent-config.js +7 -18
- package/lib/infrastructure/agent-config.js.map +1 -1
- package/lib/infrastructure/agent-config.test.js +47 -0
- package/lib/infrastructure/agent-config.test.js.map +1 -1
- package/lib/subcommands/app/app.js +1 -1
- package/lib/subcommands/app/app.js.map +1 -1
- package/lib/subcommands/login.d.ts.map +1 -1
- package/lib/subcommands/login.js +4 -3
- package/lib/subcommands/login.js.map +1 -1
- package/lib/util/copy-dir.d.ts.map +1 -1
- package/lib/util/copy-dir.js +3 -1
- package/lib/util/copy-dir.js.map +1 -1
- package/lib/util/run-in-dir.d.ts +2 -0
- package/lib/util/run-in-dir.d.ts.map +1 -0
- package/lib/util/run-in-dir.js +17 -0
- package/lib/util/run-in-dir.js.map +1 -0
- package/lib/util/sleep.d.ts +2 -0
- package/lib/util/sleep.d.ts.map +1 -0
- package/lib/util/sleep.js +9 -0
- package/lib/util/sleep.js.map +1 -0
- package/package.json +4 -3
- package/src/application-control/backup.ts +3 -0
- package/src/application-control/config.ts +61 -0
- package/src/application-control/environment-variables.ts +6 -12
- package/src/application-control/index.ts +5 -0
- package/src/application-control/install.ts +147 -68
- package/src/application-control/models.ts +136 -23
- package/src/application-control/status.ts +19 -9
- package/src/application-control/utils.ts +58 -13
- package/src/cloud-connection/device-agent-cloud-connection.ts +459 -216
- package/src/infrastructure/agent-config.test.ts +56 -0
- package/src/infrastructure/agent-config.ts +10 -19
- package/src/subcommands/app/app.ts +1 -1
- package/src/subcommands/login.ts +6 -4
- package/src/util/copy-dir.ts +3 -1
- package/src/util/run-in-dir.ts +15 -0
- package/src/util/sleep.ts +5 -0
- package/lib/util/run-cli-cmd.d.ts +0 -5
- package/lib/util/run-cli-cmd.d.ts.map +0 -1
- package/lib/util/run-cli-cmd.js +0 -24
- package/lib/util/run-cli-cmd.js.map +0 -1
- 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
|
|
5
|
-
import {
|
|
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<
|
|
37
|
+
export async function getInstalledApps(): Promise<AppDetailsPacket[]> {
|
|
15
38
|
const apps = await AgentConfigFile().getApps();
|
|
16
|
-
const 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
|
|
47
|
+
export async function installApp(props: {
|
|
25
48
|
projectId: string;
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
61
|
+
await AgentConfigFile().setAppInstalling({ projectId, version: appReleaseHash });
|
|
60
62
|
await spawner.rimraf(appDir);
|
|
61
63
|
} else {
|
|
62
|
-
await AgentConfigFile().setAppInstalling({ projectId, version:
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
83
|
-
|
|
81
|
+
appDir,
|
|
82
|
+
);
|
|
84
83
|
} else {
|
|
85
|
-
|
|
84
|
+
const { appSignedUrl } = signedUrlsPayload.appInstallPayload;
|
|
85
|
+
await downloadPackageUsingPresignedUrl({ localDest, presignedUrl: appSignedUrl });
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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 {
|
|
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
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
76
|
-
|
|
77
|
-
|
|
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 {
|
|
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: {
|
|
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:
|
|
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:
|
|
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 =
|
|
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 =
|
|
90
|
+
state = keyMirrors.appState.stopped;
|
|
84
91
|
} else if (container.state.includes('Restarting')) {
|
|
85
|
-
state =
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
+
}
|