@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.
Files changed (36) hide show
  1. package/lib/application-control/environment-variables.js +2 -2
  2. package/lib/application-control/environment-variables.js.map +1 -1
  3. package/lib/application-control/install.d.ts +14 -10
  4. package/lib/application-control/install.d.ts.map +1 -1
  5. package/lib/application-control/install.js +44 -44
  6. package/lib/application-control/install.js.map +1 -1
  7. package/lib/application-control/models.d.ts.map +1 -1
  8. package/lib/application-control/models.js +4 -1
  9. package/lib/application-control/models.js.map +1 -1
  10. package/lib/application-control/status.d.ts +3 -2
  11. package/lib/application-control/status.d.ts.map +1 -1
  12. package/lib/application-control/status.js +8 -6
  13. package/lib/application-control/status.js.map +1 -1
  14. package/lib/application-control/utils.d.ts +4 -0
  15. package/lib/application-control/utils.d.ts.map +1 -1
  16. package/lib/application-control/utils.js +20 -6
  17. package/lib/application-control/utils.js.map +1 -1
  18. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +35 -15
  19. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  20. package/lib/cloud-connection/device-agent-cloud-connection.js +282 -196
  21. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  22. package/lib/subcommands/app/app.js +1 -1
  23. package/lib/subcommands/app/app.js.map +1 -1
  24. package/lib/util/sleep.d.ts +2 -0
  25. package/lib/util/sleep.d.ts.map +1 -0
  26. package/lib/util/sleep.js +9 -0
  27. package/lib/util/sleep.js.map +1 -0
  28. package/package.json +4 -3
  29. package/src/application-control/environment-variables.ts +2 -2
  30. package/src/application-control/install.ts +66 -56
  31. package/src/application-control/models.ts +4 -1
  32. package/src/application-control/status.ts +19 -9
  33. package/src/application-control/utils.ts +25 -5
  34. package/src/cloud-connection/device-agent-cloud-connection.ts +374 -223
  35. package/src/subcommands/app/app.ts +1 -1
  36. 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,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ function sleep(ms) {
4
+ return new Promise((resolve) => {
5
+ setTimeout(resolve, ms);
6
+ });
7
+ }
8
+ exports.default = sleep;
9
+ //# sourceMappingURL=sleep.js.map
@@ -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.6",
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.3",
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.8.0",
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 { runCliCmd } from '../util/run-cli-cmd';
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<AppDetails[]> {
12
+ export async function getInstalledApps(): Promise<AppDetailsPacket[]> {
15
13
  const apps = await AgentConfigFile().getApps();
16
- const appDetails: 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 type InstallAppProps = {
22
+ export async function installApp(props: {
25
23
  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
- }
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: releaseHash });
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: releaseHash });
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
- const localDest = path.join(appDir, `${path.basename(releaseHash)}.tgz`);
70
- if (!presignedAppUrl) {
71
- // Download the application using the CLI
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
- releaseHash,
65
+ appReleaseHash,
81
66
  ],
82
67
  cwd: appDir,
83
68
  });
84
69
  } else {
85
- await installAppPackage({ localDest, presignedUrl: presignedAppUrl });
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
- console.log(`Unpacked application to ${appDir}`);
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
- await AgentConfigFile().setAppInstalled({ projectId, version: releaseHash });
106
- console.log(`Installed ${projectId} to ${appDir}`);
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 requireAppInstalled({ projectId });
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 { 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
  }
@@ -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
- 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 });
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
+ }