@alwaysai/device-agent 0.0.6 → 0.0.8
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/environment-variables.js +2 -2
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/install.d.ts +14 -10
- package/lib/application-control/install.d.ts.map +1 -1
- package/lib/application-control/install.js +44 -44
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +4 -1
- 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 +4 -0
- package/lib/application-control/utils.d.ts.map +1 -1
- package/lib/application-control/utils.js +20 -6
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +35 -15
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +282 -196
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/subcommands/app/app.js +1 -1
- package/lib/subcommands/app/app.js.map +1 -1
- 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/environment-variables.ts +2 -2
- package/src/application-control/install.ts +66 -56
- package/src/application-control/models.ts +4 -1
- package/src/application-control/status.ts +19 -9
- package/src/application-control/utils.ts +25 -5
- package/src/cloud-connection/device-agent-cloud-connection.ts +374 -223
- package/src/subcommands/app/app.ts +1 -1
- package/src/util/sleep.ts +5 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.d.ts","sourceRoot":"","sources":["../../src/util/sleep.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,EAAE,EAAE,MAAM,oBAIvC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.js","sourceRoot":"","sources":["../../src/util/sleep.ts"],"names":[],"mappings":";;AAAA,SAAwB,KAAK,CAAC,EAAU;IACtC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC;AAJD,wBAIC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alwaysai/device-agent",
|
|
3
3
|
"description": "The alwaysAI Device Agent",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.8",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
7
7
|
"publishConfig": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@alwaysai/alwayscli": "0.3.1",
|
|
33
33
|
"@alwaysai/config-nodejs": "0.1.0",
|
|
34
|
-
"@alwaysai/device-agent-schemas": "0.0.
|
|
34
|
+
"@alwaysai/device-agent-schemas": "0.0.9",
|
|
35
35
|
"@carnesen/coded-error": "0.4.0",
|
|
36
36
|
"@types/mkdirp": "1.0.2",
|
|
37
37
|
"@types/pump": "1.1.1",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@types/signal-exit": "3.0.1",
|
|
40
40
|
"@types/tar": "6.1.1",
|
|
41
41
|
"ajv": "8.11.0",
|
|
42
|
-
"alwaysai": "1.
|
|
42
|
+
"alwaysai": "1.9.0",
|
|
43
43
|
"aws-iot-device-sdk": "2.2.12",
|
|
44
44
|
"docker-compose": "0.23.17",
|
|
45
45
|
"express": "4.17.3",
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"@alwaysai/tsconfig": "0.0.1",
|
|
61
61
|
"@alwaysai/tslint-config": "0.0.4",
|
|
62
62
|
"@carnesen/run-and-catch": "0.4.3",
|
|
63
|
+
"@types/destroy": "1.0.0",
|
|
63
64
|
"@types/jest": "27.0.3",
|
|
64
65
|
"@types/node": "16.11.12",
|
|
65
66
|
"@types/sinon": "10.0.6",
|
|
@@ -10,7 +10,7 @@ export async function setEnv(props: {
|
|
|
10
10
|
service?: string;
|
|
11
11
|
}) {
|
|
12
12
|
const { projectId, vars } = props;
|
|
13
|
-
if (!AgentConfigFile().isAppReady({ projectId })) {
|
|
13
|
+
if (!(await AgentConfigFile().isAppReady({ projectId }))) {
|
|
14
14
|
throw new Error(`App ${projectId} is not ready!`);
|
|
15
15
|
}
|
|
16
16
|
const appDir = getAppDir(projectId);
|
|
@@ -46,7 +46,7 @@ export async function setEnv(props: {
|
|
|
46
46
|
|
|
47
47
|
export async function getAllEnvs(props: { projectId: string }) {
|
|
48
48
|
const { projectId } = props;
|
|
49
|
-
if (!AgentConfigFile().isAppReady({ projectId })) {
|
|
49
|
+
if (!(await AgentConfigFile().isAppReady({ projectId }))) {
|
|
50
50
|
throw new Error(`App ${projectId} is not ready!`);
|
|
51
51
|
}
|
|
52
52
|
const appDir = getAppDir(projectId);
|
|
@@ -1,19 +1,17 @@
|
|
|
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
4
|
import { JsSpawner } from 'alwaysai/lib/util';
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
import { buildApp, getAppDir } from './utils';
|
|
9
|
-
import { AppDetails } from '@alwaysai/device-agent-schemas';
|
|
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 { runCliCmd } from '../util/run-cli-cmd';
|
|
13
11
|
|
|
14
|
-
export async function getInstalledApps(): Promise<
|
|
12
|
+
export async function getInstalledApps(): Promise<AppDetailsPacket[]> {
|
|
15
13
|
const apps = await AgentConfigFile().getApps();
|
|
16
|
-
const appDetails:
|
|
14
|
+
const appDetails: AppDetailsPacket[] = [];
|
|
17
15
|
for (const app of apps) {
|
|
18
16
|
const { projectId, version } = app;
|
|
19
17
|
appDetails.push({ projectId, version });
|
|
@@ -21,54 +19,41 @@ export async function getInstalledApps(): Promise<AppDetails[]> {
|
|
|
21
19
|
return appDetails;
|
|
22
20
|
}
|
|
23
21
|
|
|
24
|
-
export
|
|
22
|
+
export async function installApp(props: {
|
|
25
23
|
projectId: string;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
24
|
+
appReleaseHash: string;
|
|
25
|
+
signedUrlsPayload?: {
|
|
26
|
+
appInstallPayload: {
|
|
27
|
+
appSignedUrl: string;
|
|
28
|
+
};
|
|
29
|
+
modelsInstallPayload: {
|
|
30
|
+
id: string;
|
|
31
|
+
version: number;
|
|
32
|
+
modelSignedUrl: string;
|
|
33
|
+
}[];
|
|
34
|
+
};
|
|
35
|
+
}): Promise<void> {
|
|
36
|
+
const { projectId, appReleaseHash, signedUrlsPayload } = props;
|
|
38
37
|
|
|
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
|
-
}
|
|
48
|
-
|
|
49
|
-
export async function installApp(props: InstallAppProps): Promise<void> {
|
|
50
|
-
const { projectId, releaseHash, presignedAppUrl } = props;
|
|
51
38
|
const appDir = getAppDir(projectId);
|
|
52
39
|
const spawner = JsSpawner();
|
|
53
40
|
if (await AgentConfigFile().isAppPresent({ projectId })) {
|
|
54
41
|
if (!(await AgentConfigFile().isAppReady({ projectId }))) {
|
|
55
42
|
throw new Error('Application already has installation in progress!');
|
|
56
43
|
}
|
|
57
|
-
await AgentConfigFile().setAppInstalling({ projectId, version:
|
|
44
|
+
await AgentConfigFile().setAppInstalling({ projectId, version: appReleaseHash });
|
|
58
45
|
console.log('Application is already installed, updating');
|
|
59
46
|
await createAppBackup({ projectId });
|
|
60
47
|
await spawner.rimraf(appDir);
|
|
61
48
|
} else {
|
|
62
|
-
await AgentConfigFile().setAppInstalling({ projectId, version:
|
|
49
|
+
await AgentConfigFile().setAppInstalling({ projectId, version: appReleaseHash });
|
|
63
50
|
}
|
|
64
51
|
|
|
65
|
-
/**
|
|
66
|
-
* Prep directory and fetch the app package
|
|
67
|
-
*/
|
|
68
52
|
await spawner.mkdirp(appDir);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
53
|
+
|
|
54
|
+
// download app package
|
|
55
|
+
const localDest = path.join(appDir, `${path.basename(appReleaseHash)}.tgz`);
|
|
56
|
+
if (!signedUrlsPayload) {
|
|
72
57
|
await runCliCmd({
|
|
73
58
|
cmd: [
|
|
74
59
|
'release',
|
|
@@ -77,33 +62,58 @@ export async function installApp(props: InstallAppProps): Promise<void> {
|
|
|
77
62
|
'--project',
|
|
78
63
|
projectId,
|
|
79
64
|
'--releaseHash',
|
|
80
|
-
|
|
65
|
+
appReleaseHash,
|
|
81
66
|
],
|
|
82
67
|
cwd: appDir,
|
|
83
68
|
});
|
|
84
69
|
} else {
|
|
85
|
-
|
|
70
|
+
const { appSignedUrl } = signedUrlsPayload.appInstallPayload;
|
|
71
|
+
await downloadPackageUsingPresignedUrl({ localDest, presignedUrl: appSignedUrl });
|
|
86
72
|
}
|
|
87
73
|
|
|
88
|
-
|
|
89
|
-
* Unpack app package
|
|
90
|
-
*/
|
|
74
|
+
// Unpack app package and remove tar file
|
|
91
75
|
await spawner.untar(fs.createReadStream(localDest), appDir);
|
|
92
|
-
|
|
76
|
+
await spawner.rimraf(localDest);
|
|
77
|
+
|
|
78
|
+
// write target json
|
|
79
|
+
await runCliCmd({
|
|
80
|
+
cmd: ['app', 'configure', '--yes', '--protocol', 'docker:', '--skip-model-sync'],
|
|
81
|
+
cwd: appDir,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// install models, python venv, and docker image
|
|
85
|
+
if (!signedUrlsPayload) {
|
|
86
|
+
await runCliCmd({
|
|
87
|
+
cmd: ['app', 'install', '--yes', '--clean', '--pull'],
|
|
88
|
+
cwd: appDir,
|
|
89
|
+
});
|
|
90
|
+
} 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
|
+
}),
|
|
107
|
+
);
|
|
108
|
+
}
|
|
93
109
|
|
|
94
|
-
/**
|
|
95
|
-
* Build app
|
|
96
|
-
* ToDo: migrate the model pull to use MQTT workflow
|
|
97
|
-
*/
|
|
98
110
|
await buildApp({ appDir });
|
|
99
111
|
|
|
100
|
-
|
|
101
|
-
* Remove tar file
|
|
102
|
-
*/
|
|
103
|
-
await spawner.rimraf(localDest);
|
|
112
|
+
await AgentConfigFile().setAppInstalled({ projectId, version: appReleaseHash });
|
|
104
113
|
|
|
105
|
-
|
|
106
|
-
|
|
114
|
+
console.log(
|
|
115
|
+
`Installed app version ${appReleaseHash} from project ${projectId} to ${appDir}.`,
|
|
116
|
+
);
|
|
107
117
|
}
|
|
108
118
|
|
|
109
119
|
export async function uninstallApp(props: { projectId: string }): Promise<void> {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { JsSpawner } from 'alwaysai/lib/util';
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
|
+
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
4
5
|
|
|
5
6
|
import { runCliCmd } from '../util/run-cli-cmd';
|
|
6
7
|
import { ModelDetails } from './types';
|
|
@@ -8,7 +9,9 @@ import { buildApp, getAppDir, requireAppInstalled } from './utils';
|
|
|
8
9
|
|
|
9
10
|
export async function getAppModels(props: { projectId: string }) {
|
|
10
11
|
const { projectId } = props;
|
|
11
|
-
await
|
|
12
|
+
if (!(await AgentConfigFile().isAppPresent({ projectId }))) {
|
|
13
|
+
throw new Error('Application is not installed');
|
|
14
|
+
}
|
|
12
15
|
|
|
13
16
|
const appDir = getAppDir(projectId);
|
|
14
17
|
const appCfgPath = join(appDir, 'alwaysai.app.json');
|
|
@@ -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
|
}
|
|
@@ -5,6 +5,7 @@ import { AAI_DIR } from 'alwaysai/lib/constants';
|
|
|
5
5
|
|
|
6
6
|
import { runCliCmd } from '../util/run-cli-cmd';
|
|
7
7
|
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
8
|
+
import nodeFetch from 'node-fetch';
|
|
8
9
|
|
|
9
10
|
export const APP_ROOT = path.join(AAI_DIR, 'applications');
|
|
10
11
|
const DOCKER_COMPOSE_FILENAME = 'docker-compose.yaml';
|
|
@@ -26,11 +27,7 @@ export async function requireAppInstalled(props: { projectId: string }) {
|
|
|
26
27
|
|
|
27
28
|
export async function buildApp(props: { appDir: string }) {
|
|
28
29
|
const { appDir } = props;
|
|
29
|
-
|
|
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 });
|
|
30
|
+
console.log(appDir);
|
|
34
31
|
|
|
35
32
|
// Build standalone image and docker-compose
|
|
36
33
|
if (!fs.existsSync(path.join(appDir, DOCKER_COMPOSE_FILENAME))) {
|
|
@@ -41,9 +38,32 @@ export async function buildApp(props: { appDir: string }) {
|
|
|
41
38
|
}
|
|
42
39
|
|
|
43
40
|
const buildOut = await compose.buildAll({ cwd: appDir });
|
|
41
|
+
console.log(buildOut);
|
|
44
42
|
if (buildOut.exitCode !== 0) {
|
|
45
43
|
throw new Error(
|
|
46
44
|
`Failed to build application! stdout=${buildOut.out} stderr=${buildOut.err}`,
|
|
47
45
|
);
|
|
48
46
|
}
|
|
49
47
|
}
|
|
48
|
+
|
|
49
|
+
export async function downloadPackageUsingPresignedUrl({
|
|
50
|
+
localDest,
|
|
51
|
+
presignedUrl,
|
|
52
|
+
}): Promise<void> {
|
|
53
|
+
const response = await nodeFetch(presignedUrl);
|
|
54
|
+
if (response.status !== 200) {
|
|
55
|
+
// If the URL is invalid; I think we shouldn't get here with the new changes
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Status Code: ${response.status}, ${response.statusText}. Response: ${response.body}`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Write the app package to the hash directory
|
|
63
|
+
*/
|
|
64
|
+
const stream = response.body.pipe(fs.createWriteStream(localDest));
|
|
65
|
+
await new Promise((resolve, reject) => {
|
|
66
|
+
stream.on('finish', resolve);
|
|
67
|
+
stream.on('error', reject);
|
|
68
|
+
});
|
|
69
|
+
}
|