@alwaysai/device-agent 0.0.3 → 0.0.5

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 (210) hide show
  1. package/lib/application-control/backup.d.ts +8 -0
  2. package/lib/application-control/backup.d.ts.map +1 -0
  3. package/lib/application-control/backup.js +34 -0
  4. package/lib/application-control/backup.js.map +1 -0
  5. package/lib/application-control/environment-variables.d.ts +9 -0
  6. package/lib/application-control/environment-variables.d.ts.map +1 -0
  7. package/lib/application-control/environment-variables.js +82 -0
  8. package/lib/application-control/environment-variables.js.map +1 -0
  9. package/lib/application-control/index.d.ts +9 -0
  10. package/lib/application-control/index.d.ts.map +1 -0
  11. package/lib/application-control/index.js +27 -0
  12. package/lib/application-control/index.js.map +1 -0
  13. package/lib/application-control/install.d.ts +16 -0
  14. package/lib/application-control/install.d.ts.map +1 -0
  15. package/lib/application-control/install.js +117 -0
  16. package/lib/application-control/install.js.map +1 -0
  17. package/lib/application-control/models.d.ts +8 -0
  18. package/lib/application-control/models.d.ts.map +1 -1
  19. package/lib/application-control/models.js +44 -11
  20. package/lib/application-control/models.js.map +1 -1
  21. package/lib/application-control/status.d.ts +26 -0
  22. package/lib/application-control/status.d.ts.map +1 -0
  23. package/lib/application-control/status.js +138 -0
  24. package/lib/application-control/status.js.map +1 -0
  25. package/lib/application-control/types.d.ts +5 -0
  26. package/lib/application-control/types.d.ts.map +1 -0
  27. package/lib/{util/spawner → application-control}/types.js +0 -0
  28. package/lib/{util/spawner → application-control}/types.js.map +1 -1
  29. package/lib/application-control/utils.d.ts +2 -9
  30. package/lib/application-control/utils.d.ts.map +1 -1
  31. package/lib/application-control/utils.js +14 -29
  32. package/lib/application-control/utils.js.map +1 -1
  33. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +16 -1
  34. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  35. package/lib/cloud-connection/device-agent-cloud-connection.js +239 -9
  36. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  37. package/lib/docker/docker-cmd.js +2 -2
  38. package/lib/docker/docker-cmd.js.map +1 -1
  39. package/lib/docker/docker-compose-cmd.js +2 -2
  40. package/lib/docker/docker-compose-cmd.js.map +1 -1
  41. package/lib/environment.d.ts +2 -0
  42. package/lib/environment.d.ts.map +1 -1
  43. package/lib/environment.js +3 -1
  44. package/lib/environment.js.map +1 -1
  45. package/lib/index.js +10 -8
  46. package/lib/index.js.map +1 -1
  47. package/lib/infrastructure/agent-config.d.ts +73 -0
  48. package/lib/infrastructure/agent-config.d.ts.map +1 -0
  49. package/lib/infrastructure/agent-config.js +186 -0
  50. package/lib/infrastructure/agent-config.js.map +1 -0
  51. package/lib/infrastructure/agent-config.test.d.ts +2 -0
  52. package/lib/infrastructure/agent-config.test.d.ts.map +1 -0
  53. package/lib/infrastructure/agent-config.test.js +135 -0
  54. package/lib/infrastructure/agent-config.test.js.map +1 -0
  55. package/lib/infrastructure/certificates-and-tokens.d.ts +6 -0
  56. package/lib/infrastructure/certificates-and-tokens.d.ts.map +1 -0
  57. package/lib/infrastructure/certificates-and-tokens.js +69 -0
  58. package/lib/infrastructure/certificates-and-tokens.js.map +1 -0
  59. package/lib/{util → infrastructure}/urls.d.ts +0 -0
  60. package/lib/infrastructure/urls.d.ts.map +1 -0
  61. package/lib/{util → infrastructure}/urls.js +3 -3
  62. package/lib/infrastructure/urls.js.map +1 -0
  63. package/lib/root.js +3 -3
  64. package/lib/root.js.map +1 -1
  65. package/lib/subcommands/app/app.d.ts +31 -14
  66. package/lib/subcommands/app/app.d.ts.map +1 -1
  67. package/lib/subcommands/app/app.js +117 -62
  68. package/lib/subcommands/app/app.js.map +1 -1
  69. package/lib/subcommands/app/index.d.ts.map +1 -1
  70. package/lib/subcommands/app/index.js +6 -1
  71. package/lib/subcommands/app/index.js.map +1 -1
  72. package/lib/subcommands/device/device.d.ts +6 -0
  73. package/lib/subcommands/device/device.d.ts.map +1 -0
  74. package/lib/subcommands/device/device.js +62 -0
  75. package/lib/subcommands/device/device.js.map +1 -0
  76. package/lib/subcommands/device/index.d.ts +2 -0
  77. package/lib/subcommands/device/index.d.ts.map +1 -0
  78. package/lib/subcommands/device/index.js +11 -0
  79. package/lib/subcommands/device/index.js.map +1 -0
  80. package/lib/subcommands/get-model-package.d.ts +5 -0
  81. package/lib/subcommands/get-model-package.d.ts.map +1 -0
  82. package/lib/subcommands/get-model-package.js +51 -0
  83. package/lib/subcommands/get-model-package.js.map +1 -0
  84. package/lib/subcommands/index.d.ts +8 -1
  85. package/lib/subcommands/index.d.ts.map +1 -1
  86. package/lib/subcommands/index.js +6 -6
  87. package/lib/subcommands/index.js.map +1 -1
  88. package/lib/subcommands/login.d.ts +3 -2
  89. package/lib/subcommands/login.d.ts.map +1 -1
  90. package/lib/subcommands/login.js +11 -4
  91. package/lib/subcommands/login.js.map +1 -1
  92. package/lib/util/copy-dir.js +3 -3
  93. package/lib/util/copy-dir.js.map +1 -1
  94. package/lib/util/directories.d.ts +1 -1
  95. package/lib/util/directories.d.ts.map +1 -1
  96. package/lib/util/directories.js +9 -14
  97. package/lib/util/directories.js.map +1 -1
  98. package/lib/util/get-device-id.d.ts +2 -0
  99. package/lib/util/get-device-id.d.ts.map +1 -0
  100. package/lib/util/get-device-id.js +24 -0
  101. package/lib/util/get-device-id.js.map +1 -0
  102. package/package.json +19 -14
  103. package/readme.md +176 -72
  104. package/src/application-control/backup.ts +32 -0
  105. package/src/application-control/environment-variables.ts +81 -0
  106. package/src/application-control/index.ts +40 -0
  107. package/src/application-control/install.ts +126 -0
  108. package/src/application-control/models.ts +51 -11
  109. package/src/application-control/status.ts +156 -0
  110. package/src/application-control/types.ts +1 -0
  111. package/src/application-control/utils.ts +12 -27
  112. package/src/cloud-connection/device-agent-cloud-connection.ts +280 -13
  113. package/src/docker/docker-cmd.ts +1 -1
  114. package/src/docker/docker-compose-cmd.ts +1 -1
  115. package/src/environment.ts +2 -0
  116. package/src/index.ts +10 -7
  117. package/src/infrastructure/agent-config.test.ts +143 -0
  118. package/src/infrastructure/agent-config.ts +217 -0
  119. package/src/infrastructure/certificates-and-tokens.ts +71 -0
  120. package/src/{util → infrastructure}/urls.ts +1 -1
  121. package/src/root.ts +3 -3
  122. package/src/subcommands/app/app.ts +135 -62
  123. package/src/subcommands/app/index.ts +11 -1
  124. package/src/subcommands/device/device.ts +63 -0
  125. package/src/subcommands/device/index.ts +8 -0
  126. package/src/subcommands/get-model-package.ts +60 -0
  127. package/src/subcommands/index.ts +5 -5
  128. package/src/subcommands/login.ts +11 -4
  129. package/src/util/copy-dir.ts +1 -1
  130. package/src/util/directories.ts +12 -17
  131. package/src/util/get-device-id.ts +22 -0
  132. package/lib/application-control/application-control.d.ts +0 -46
  133. package/lib/application-control/application-control.d.ts.map +0 -1
  134. package/lib/application-control/application-control.js +0 -234
  135. package/lib/application-control/application-control.js.map +0 -1
  136. package/lib/constants.d.ts +0 -17
  137. package/lib/constants.d.ts.map +0 -1
  138. package/lib/constants.js +0 -24
  139. package/lib/constants.js.map +0 -1
  140. package/lib/subcommands/device-control.d.ts +0 -2
  141. package/lib/subcommands/device-control.d.ts.map +0 -1
  142. package/lib/subcommands/device-control.js +0 -19
  143. package/lib/subcommands/device-control.js.map +0 -1
  144. package/lib/subcommands/test-app.d.ts +0 -2
  145. package/lib/subcommands/test-app.d.ts.map +0 -1
  146. package/lib/subcommands/test-app.js +0 -29
  147. package/lib/subcommands/test-app.js.map +0 -1
  148. package/lib/util/spawner/gnu-spawner.d.ts +0 -9
  149. package/lib/util/spawner/gnu-spawner.d.ts.map +0 -1
  150. package/lib/util/spawner/gnu-spawner.js +0 -102
  151. package/lib/util/spawner/gnu-spawner.js.map +0 -1
  152. package/lib/util/spawner/js-spawner.d.ts +0 -5
  153. package/lib/util/spawner/js-spawner.d.ts.map +0 -1
  154. package/lib/util/spawner/js-spawner.js +0 -89
  155. package/lib/util/spawner/js-spawner.js.map +0 -1
  156. package/lib/util/spawner/types.d.ts +0 -28
  157. package/lib/util/spawner/types.d.ts.map +0 -1
  158. package/lib/util/spawner-base/index.d.ts +0 -17
  159. package/lib/util/spawner-base/index.d.ts.map +0 -1
  160. package/lib/util/spawner-base/index.js +0 -30
  161. package/lib/util/spawner-base/index.js.map +0 -1
  162. package/lib/util/spawner-base/run-foreground-sync.d.ts +0 -3
  163. package/lib/util/spawner-base/run-foreground-sync.d.ts.map +0 -1
  164. package/lib/util/spawner-base/run-foreground-sync.js +0 -18
  165. package/lib/util/spawner-base/run-foreground-sync.js.map +0 -1
  166. package/lib/util/spawner-base/run-foreground.d.ts +0 -3
  167. package/lib/util/spawner-base/run-foreground.d.ts.map +0 -1
  168. package/lib/util/spawner-base/run-foreground.js +0 -49
  169. package/lib/util/spawner-base/run-foreground.js.map +0 -1
  170. package/lib/util/spawner-base/run-streaming.d.ts +0 -4
  171. package/lib/util/spawner-base/run-streaming.d.ts.map +0 -1
  172. package/lib/util/spawner-base/run-streaming.js +0 -35
  173. package/lib/util/spawner-base/run-streaming.js.map +0 -1
  174. package/lib/util/spawner-base/run.d.ts +0 -4
  175. package/lib/util/spawner-base/run.d.ts.map +0 -1
  176. package/lib/util/spawner-base/run.js +0 -56
  177. package/lib/util/spawner-base/run.js.map +0 -1
  178. package/lib/util/urls.d.ts.map +0 -1
  179. package/lib/util/urls.js.map +0 -1
  180. package/lib/web/index.html +0 -229
  181. package/lib/web/static/Karla.css +0 -18
  182. package/lib/web/static/bootstrap-4.3.1.min.css +0 -7
  183. package/lib/web/static/bootstrap-4.3.1.min.js +0 -7
  184. package/lib/web/static/favicon.ico +0 -0
  185. package/lib/web/static/jquery-3.3.1.slim.min.js +0 -2
  186. package/lib/web/static/popper-1.14.7.min.js +0 -5
  187. package/lib/web/web-interface.d.ts +0 -2
  188. package/lib/web/web-interface.d.ts.map +0 -1
  189. package/lib/web/web-interface.js +0 -74
  190. package/lib/web/web-interface.js.map +0 -1
  191. package/src/application-control/application-control.ts +0 -273
  192. package/src/constants.ts +0 -32
  193. package/src/subcommands/device-control.ts +0 -16
  194. package/src/subcommands/test-app.ts +0 -30
  195. package/src/util/spawner/gnu-spawner.ts +0 -114
  196. package/src/util/spawner/js-spawner.ts +0 -110
  197. package/src/util/spawner/types.ts +0 -28
  198. package/src/util/spawner-base/index.ts +0 -28
  199. package/src/util/spawner-base/run-foreground-sync.ts +0 -16
  200. package/src/util/spawner-base/run-foreground.ts +0 -49
  201. package/src/util/spawner-base/run-streaming.ts +0 -40
  202. package/src/util/spawner-base/run.ts +0 -60
  203. package/src/web/index.html +0 -229
  204. package/src/web/static/Karla.css +0 -18
  205. package/src/web/static/bootstrap-4.3.1.min.css +0 -7
  206. package/src/web/static/bootstrap-4.3.1.min.js +0 -7
  207. package/src/web/static/favicon.ico +0 -0
  208. package/src/web/static/jquery-3.3.1.slim.min.js +0 -2
  209. package/src/web/static/popper-1.14.7.min.js +0 -5
  210. package/src/web/web-interface.ts +0 -89
@@ -0,0 +1,156 @@
1
+ import compose from 'docker-compose';
2
+ import { fetchAppReleaseHistory } from 'alwaysai/lib/infrastructure';
3
+ import { JsSpawner } from 'alwaysai/lib/util';
4
+
5
+ import { runDockerLogin } from '../docker/docker-cmd';
6
+ import { getAppDir, requireAppInstalled } from './utils';
7
+ import { AppStateValue, ServiceStatus, AppState } from '@alwaysai/device-agent-schemas';
8
+ import { AgentConfigFile } from '../infrastructure/agent-config';
9
+
10
+ export async function listAppReleases(props: { projectId: string }) {
11
+ const { projectId } = props;
12
+ const releaseHistory = await fetchAppReleaseHistory(projectId);
13
+ return releaseHistory;
14
+ }
15
+
16
+ export async function listAppLatestRelease(props: { projectId: string }) {
17
+ const { projectId } = props;
18
+ const releaseHistory = await fetchAppReleaseHistory(projectId);
19
+ if (releaseHistory.length >= 1) {
20
+ return releaseHistory[0]['releaseHash'];
21
+ }
22
+ return undefined;
23
+ }
24
+
25
+ export async function getAppStatus(props: { projectId: string }): Promise<AppState> {
26
+ const { projectId } = props;
27
+ if (!(await AgentConfigFile().isAppPresent({ projectId }))) {
28
+ throw new Error('Application is not installed');
29
+ }
30
+
31
+ const appDetails = {
32
+ projectId,
33
+ version: await AgentConfigFile().getAppVersion({ projectId }),
34
+ };
35
+ if (!(await AgentConfigFile().isAppReady({ projectId }))) {
36
+ // App is being installed or updated
37
+ return { appDetails, services: [] };
38
+ }
39
+
40
+ const appDir = getAppDir(projectId);
41
+
42
+ // Check if app is running
43
+ const output = await compose.ps({ cwd: appDir });
44
+
45
+ if (output.exitCode !== 0) {
46
+ throw new Error(
47
+ `Failed to get application status! stdout=${output.out} stderr=${output.err}`,
48
+ );
49
+ }
50
+
51
+ const services: ServiceStatus[] = [];
52
+ if (output.data.services.length === 0) {
53
+ // If the app has never been started (or has an empty docker-compose.yaml)
54
+ // the list will be empty.
55
+ services.push({ name: '', state: 'Stopped' });
56
+ } else {
57
+ for (const service of output.data.services) {
58
+ // Container name has the format:
59
+ // <projectId>_name_<number>
60
+ const matchProjectId = new RegExp(`^${projectId}`);
61
+ const name = service.name
62
+ .replace(matchProjectId, '')
63
+ .replace(/\d+$/, '')
64
+ .replace(/_/g, '');
65
+
66
+ let state: AppStateValue;
67
+ if (service.state.includes('Up')) {
68
+ state = 'Up';
69
+ } else if (service.state.includes('Stopped') || service.state.includes('Exit')) {
70
+ state = 'Stopped';
71
+ } else if (service.state.includes('Restarting')) {
72
+ state = 'Restarting';
73
+ } else {
74
+ throw new Error(`Unknown state for service! (${service.state})`);
75
+ }
76
+ services.push({ name, state });
77
+ }
78
+ }
79
+ return { appDetails, services };
80
+ }
81
+
82
+ export async function getAppLogs(props: {
83
+ projectId: string;
84
+ services?: string[];
85
+ }): Promise<NodeJS.ReadableStream> {
86
+ const { projectId, services } = props;
87
+ await requireAppInstalled({ projectId });
88
+
89
+ const appDir = getAppDir(projectId);
90
+
91
+ const serviceList = services
92
+ ? services
93
+ : await (async function () {
94
+ const appStatus = await getAppStatus({ projectId });
95
+ const services: string[] = [];
96
+ for (const service of appStatus.services) {
97
+ services.push(service.name);
98
+ }
99
+ return services;
100
+ })();
101
+
102
+ // Use direct command with spawner in order to get a readable stream
103
+ return await JsSpawner().runStreaming({
104
+ exe: 'docker-compose',
105
+ args: ['logs', '-f', ...serviceList],
106
+ cwd: appDir,
107
+ });
108
+ }
109
+
110
+ export async function startApp(props: {
111
+ projectId: string;
112
+ dockerLoginToken?: string;
113
+ }): Promise<void> {
114
+ const { projectId, dockerLoginToken } = props;
115
+ await requireAppInstalled({ projectId });
116
+
117
+ const appDir = getAppDir(projectId);
118
+ if (dockerLoginToken !== undefined) {
119
+ const result = await runDockerLogin({ token: dockerLoginToken });
120
+ console.log(result);
121
+ }
122
+
123
+ // TODO: Check if app is running
124
+ // Start app
125
+ const upOut = await compose.upAll({ cwd: appDir });
126
+ console.log(upOut);
127
+ if (upOut.exitCode !== 0) {
128
+ throw new Error(
129
+ `Failed to start application! stdout=${upOut.out} stderr=${upOut.err}`,
130
+ );
131
+ }
132
+ console.log('Started', projectId);
133
+ }
134
+
135
+ export async function stopApp(props: { projectId: string }): Promise<void> {
136
+ const { projectId } = props;
137
+ await requireAppInstalled({ projectId });
138
+
139
+ const appDir = getAppDir(projectId);
140
+ // TODO: Check if app is running
141
+ // Stop app
142
+ const output = await compose.down({ cwd: appDir });
143
+ console.log(output);
144
+ if (output.exitCode !== 0) {
145
+ throw new Error(
146
+ `Failed to stop application! stdout=${output.out} stderr=${output.err}`,
147
+ );
148
+ }
149
+ console.log('Stopped', projectId);
150
+ }
151
+
152
+ export async function restartApp(props: { projectId: string }): Promise<void> {
153
+ const { projectId } = props;
154
+ await stopApp({ projectId });
155
+ await startApp({ projectId });
156
+ }
@@ -0,0 +1 @@
1
+ export type ModelDetails = { modelId: string; version: number };
@@ -1,47 +1,33 @@
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
5
 
5
- import { JsSpawner } from '../util/spawner/js-spawner';
6
- import { DOT_ALWAYSAI_DIR } from '../constants';
7
6
  import { runCliCmd } from '../util/run-cli-cmd';
7
+ import { AgentConfigFile } from '../infrastructure/agent-config';
8
8
 
9
- export const APP_ROOT = path.join(DOT_ALWAYSAI_DIR, 'applications');
10
- const RELEASE_FILENAME = 'alwaysai.release.json';
9
+ export const APP_ROOT = path.join(AAI_DIR, 'applications');
11
10
  const DOCKER_COMPOSE_FILENAME = 'docker-compose.yaml';
12
11
 
13
12
  export function getAppDir(projectId: string): string {
14
13
  return path.join(APP_ROOT, projectId);
15
14
  }
16
15
 
17
- export async function isAppInstalled(props: { appDir: string }): Promise<boolean> {
18
- const { appDir } = props;
19
- // TODO: Could do more sophisticated check
20
- if (fs.existsSync(appDir)) {
21
- return true;
16
+ export async function requireAppInstalled(props: { projectId: string }) {
17
+ const { projectId } = props;
18
+ // Ensure an app that is being installed or updated is not modified
19
+ if (!(await AgentConfigFile().isAppPresent({ projectId }))) {
20
+ throw new Error('Application is not installed');
21
+ }
22
+ if (!(await AgentConfigFile().isAppReady({ projectId }))) {
23
+ throw new Error('Application is not done installing or updating');
22
24
  }
23
- return false;
24
- }
25
-
26
- export async function setAppVersion(props: { appDir: string; version: string }) {
27
- const { appDir, version } = props;
28
- // TODO: Switch to node-config
29
- const versionPath = path.join(appDir, RELEASE_FILENAME);
30
- const versionData = { version };
31
- await JsSpawner().writeFile(versionPath, JSON.stringify(versionData, null, 2));
32
- }
33
-
34
- export async function getAppVersion(props: { appDir: string }) {
35
- const { appDir } = props;
36
- const versionPath = path.join(appDir, RELEASE_FILENAME);
37
- const releaseData = JSON.parse(await JsSpawner().readFile(versionPath));
38
- return releaseData.version;
39
25
  }
40
26
 
41
27
  export async function buildApp(props: { appDir: string }) {
42
28
  const { appDir } = props;
43
29
  await runCliCmd({
44
- cmd: ['app', 'configure', '--yes', '--protocol', 'docker:'],
30
+ cmd: ['app', 'configure', '--yes', '--protocol', 'docker:', '--skip-model-sync'],
45
31
  cwd: appDir,
46
32
  });
47
33
  await runCliCmd({ cmd: ['app', 'install', '--yes', '--clean', '--pull'], cwd: appDir });
@@ -55,7 +41,6 @@ export async function buildApp(props: { appDir: string }) {
55
41
  }
56
42
 
57
43
  const buildOut = await compose.buildAll({ cwd: appDir });
58
- console.log(buildOut);
59
44
  if (buildOut.exitCode !== 0) {
60
45
  throw new Error(
61
46
  `Failed to build application! stdout=${buildOut.out} stderr=${buildOut.err}`,
@@ -1,15 +1,46 @@
1
1
  const awsIot = require('aws-iot-device-sdk');
2
- import { getIoTCoreEndpointUrl } from '../util/urls';
2
+ import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
3
3
  import {
4
4
  getPrivateKeyFilePath,
5
5
  getCertificateFilePath,
6
6
  getRootCertificateFilePath,
7
7
  } from '../util/directories';
8
+ import {
9
+ startApp,
10
+ stopApp,
11
+ restartApp,
12
+ getAppStatus,
13
+ getAppLogs,
14
+ } from '../application-control/status';
15
+ import { installApp } from '../application-control/install';
16
+ import { getCpuUtil, getDiskUtil, getMemUtil } from '../device-control/device-control';
17
+ import {
18
+ DeviceAgentMessage,
19
+ DeviceStatsMessage,
20
+ AppState,
21
+ AppStateMessage,
22
+ ActionMessage,
23
+ AppLogs,
24
+ AppLogsMessage,
25
+ InstallationStatusMessage,
26
+ InstallationStatus,
27
+ InstallationStatusEnum,
28
+ } from '@alwaysai/device-agent-schemas';
29
+ import { getDeviceId } from '../util/get-device-id';
30
+ import { AgentConfigFile } from '../infrastructure/agent-config';
8
31
 
9
32
  export class DeviceAgentCloudConnection {
10
- public device = awsIot.device;
11
- private clientId = 'DEVICE_AGENT'; // TODO: use CLI to get either the username or the device ID? how do we determine the device ID in this case?
33
+ private clientId = getDeviceId();
12
34
  private host = getIoTCoreEndpointUrl();
35
+ private publishable = false;
36
+ private readonly publishInterval = 1000;
37
+ private readonly installationStatusInterval = 3000;
38
+ private installationStatus: InstallationStatus;
39
+ private publishableTimeout: ReturnType<typeof setTimeout>;
40
+
41
+ public device = awsIot.device;
42
+ public agentTopicPrefix = `destination/agent/device/${this.clientId}/topic/`;
43
+ public cloudTopicPrefix = `destination/cloud/device/${this.clientId}/topic/`;
13
44
 
14
45
  constructor() {
15
46
  this.device = awsIot.device({
@@ -20,34 +51,270 @@ export class DeviceAgentCloudConnection {
20
51
  host: this.host,
21
52
  });
22
53
 
23
- // TODO: subscribe to topics here
24
- this.device.subscribe('command');
54
+ this.device.subscribe(`${this.agentTopicPrefix}command`);
55
+ this.device.subscribe(`${this.agentTopicPrefix}response`);
56
+ }
57
+
58
+ public setInstallationStatus(installationStatus: InstallationStatus) {
59
+ this.installationStatus = installationStatus;
60
+ }
61
+
62
+ public getInstallationStatus(): InstallationStatus {
63
+ return this.installationStatus;
64
+ }
65
+
66
+ public getPublishable(): boolean {
67
+ return this.publishable;
68
+ }
69
+
70
+ public setPublishable(enabled: boolean) {
71
+ this.publishable = enabled;
72
+ }
73
+
74
+ public restartPublishableTimeout() {
75
+ clearTimeout(this.publishableTimeout);
76
+ this.publishableTimeout = setTimeout(() => {
77
+ this.setPublishable(false);
78
+ }, 600000); // 10 min
25
79
  }
26
80
 
27
- public getClientId() {
81
+ public getPublishInterval() {
82
+ return this.publishInterval;
83
+ }
84
+
85
+ public getInstallationStatusInterval() {
86
+ return this.installationStatusInterval;
87
+ }
88
+
89
+ public getClientId(): string {
28
90
  return this.clientId;
29
91
  }
30
92
 
31
93
  public publishMessage(topic: string, message: string) {
32
- console.log('publishing message ', message);
33
- this.device.publish(topic, JSON.stringify({ message }));
94
+ this.device.publish(topic, message);
34
95
  }
35
96
  }
36
97
 
37
98
  export function runDeviceAgentCloudInterface() {
38
99
  const deviceAgent = new DeviceAgentCloudConnection();
39
100
 
40
- deviceAgent.device.on('connect', function() {
101
+ async function buildMessagePacket(
102
+ topic: string,
103
+ payload:
104
+ | DeviceStatsMessage
105
+ | ActionMessage
106
+ | AppStateMessage
107
+ | AppLogsMessage
108
+ | InstallationStatusMessage,
109
+ ): Promise<DeviceAgentMessage> {
110
+ const packet = {
111
+ timestamp: new Date().toUTCString(),
112
+ deviceId: deviceAgent.getClientId(),
113
+ topic,
114
+ payload,
115
+ };
116
+ return packet;
117
+ }
118
+
119
+ async function getAppLogsMessage(): Promise<AppLogsMessage> {
120
+ const appLogsList: AppLogs[] = [];
121
+ const apps = await AgentConfigFile().getReadyApps();
122
+ for (const app of apps) {
123
+ const projectId = app.projectId;
124
+ const readable = await getAppLogs({ projectId });
125
+ const logs: any[] = [];
126
+ readable.setEncoding('utf8');
127
+ for await (const chunk of readable) {
128
+ logs.push(chunk);
129
+ }
130
+ const appLogs = {
131
+ projectId,
132
+ logs,
133
+ };
134
+ appLogsList.push(appLogs);
135
+ }
136
+ const applicationStatePackage = {
137
+ applicationLogs: appLogsList,
138
+ };
139
+ return applicationStatePackage;
140
+ }
141
+
142
+ const publishAppLogs = setInterval(async function () {
143
+ const appLogsMessage = await getAppLogsMessage();
144
+ const topic = `device/${deviceAgent.getClientId()}/topic/application-management`;
145
+ const appLogsPacket = await buildMessagePacket(topic, appLogsMessage);
146
+
147
+ deviceAgent.publishMessage(topic, JSON.stringify({ appLogsPacket }));
148
+ }, deviceAgent.getPublishInterval());
149
+
150
+ async function getAppStateMessage(): Promise<AppStateMessage> {
151
+ const appStateMessage: AppState[] = [];
152
+ const apps = await AgentConfigFile().getApps();
153
+ for (const app of apps) {
154
+ const projectId = app.projectId;
155
+ const status = await getAppStatus({ projectId });
156
+ appStateMessage.push(status);
157
+ }
158
+ const applicationStatePackage = {
159
+ applicationState: appStateMessage,
160
+ };
161
+ return applicationStatePackage;
162
+ }
163
+
164
+ const publishAppState = setInterval(async function () {
165
+ if (deviceAgent.getPublishable()) {
166
+ const topic = `${deviceAgent.cloudTopicPrefix}application-management`;
167
+ const appStateMessage = await getAppStateMessage();
168
+ const appStatePacket = await buildMessagePacket(topic, appStateMessage);
169
+ deviceAgent.publishMessage(topic, JSON.stringify({ appStatePacket }));
170
+ }
171
+ }, deviceAgent.getPublishInterval());
172
+
173
+ async function getDeviceStatsMessage(): Promise<DeviceStatsMessage> {
174
+ const cpuUsage = await getCpuUtil();
175
+ const diskUtil = await getDiskUtil();
176
+ const memUtil = await getMemUtil();
177
+
178
+ const deviceStatsMessage = {
179
+ deviceStats: {
180
+ cpuUsage,
181
+ diskUtil,
182
+ usedMemoryPercentage: memUtil,
183
+ },
184
+ };
185
+ return deviceStatsMessage;
186
+ }
187
+
188
+ const publishDeviceStats = setInterval(async function () {
189
+ if (deviceAgent.getPublishable()) {
190
+ const topic = `${deviceAgent.cloudTopicPrefix}device-management`;
191
+ const deviceStatsMessage = await getDeviceStatsMessage();
192
+ const deviceStatsPacket = await buildMessagePacket(topic, deviceStatsMessage);
193
+ deviceAgent.publishMessage(topic, JSON.stringify({ deviceStatsPacket }));
194
+ }
195
+ }, deviceAgent.getPublishInterval());
196
+
197
+ const publishDeviceRequest = async ({ projectId }) => {
198
+ const topic = `${deviceAgent.cloudTopicPrefix}request`;
199
+ const deviceRequestPackage = {
200
+ timestamp: new Date().toUTCString(),
201
+ deviceId: deviceAgent.getClientId(),
202
+ projectId,
203
+ releaseHash: '',
204
+ topic,
205
+ };
206
+ deviceAgent.publishMessage(
207
+ topic,
208
+ JSON.stringify({ device_request: deviceRequestPackage }),
209
+ );
210
+ };
211
+
212
+ const publishInstallationStatus = async (interval: NodeJS.Timeout) => {
213
+ const topic = `${deviceAgent.cloudTopicPrefix}installation-status`;
214
+ const installationStatus = deviceAgent.getInstallationStatus();
215
+ const installationStatusPacket = await buildMessagePacket(topic, {
216
+ installationStatus,
217
+ });
218
+ deviceAgent.device.publish(topic, JSON.stringify({ installationStatusPacket }));
219
+ if (installationStatus.status !== InstallationStatusEnum.IN_PROGRESS) {
220
+ clearInterval(interval);
221
+ }
222
+ };
223
+
224
+ const handleMessageTopic = async ({ topic, payload }) => {
225
+ const action = payload['action'];
226
+ const actionPayload: any = payload[action];
227
+ const type = topic.split('/').slice(-1)[0];
228
+
229
+ if (!type) {
230
+ return false;
231
+ }
232
+
233
+ switch (action) {
234
+ /**
235
+ * Install app package based on the given project ID
236
+ */
237
+ case 'install':
238
+ /**
239
+ {
240
+ "action": "install",
241
+ "install": {
242
+ "releaseHash": "7fb2a812f9e7aa193208dac353521965da50d755085162066c125592f1ed760b",
243
+ "projectId": "786e4686-a681-4cff-9e17-1e7d385c0fdb"
244
+ }
245
+ }
246
+ */
247
+ if (type === 'response') {
248
+ deviceAgent.setInstallationStatus({
249
+ status: InstallationStatusEnum.IN_PROGRESS,
250
+ });
251
+
252
+ const installationStatusPing = setInterval(
253
+ () => publishInstallationStatus(installationStatusPing),
254
+ deviceAgent.getInstallationStatusInterval(),
255
+ );
256
+
257
+ // Install the app with the given url
258
+ await (async () => {
259
+ try {
260
+ await installApp(actionPayload);
261
+ deviceAgent.setInstallationStatus({
262
+ status: InstallationStatusEnum.SUCCESS,
263
+ });
264
+ } catch (e) {
265
+ const reason: string = e.message;
266
+ deviceAgent.setInstallationStatus({
267
+ status: InstallationStatusEnum.FAILURE,
268
+ reason,
269
+ });
270
+ }
271
+ })();
272
+ break;
273
+ }
274
+ publishDeviceRequest(actionPayload);
275
+ break;
276
+ case 'start':
277
+ startApp({ projectId: actionPayload.projectId });
278
+ break;
279
+ case 'stop':
280
+ stopApp({ projectId: actionPayload.projectId });
281
+ break;
282
+ case 'restart':
283
+ restartApp({ projectId: actionPayload.projectId });
284
+ break;
285
+ /**
286
+ * Allow/disallow publishing on this device. By default, (publishable = false)
287
+ */
288
+ case 'publishable':
289
+ /**
290
+ {
291
+ "action": "publishable",
292
+ "publishable": true | false
293
+ }
294
+ */
295
+ deviceAgent.setPublishable(actionPayload);
296
+ deviceAgent.restartPublishableTimeout();
297
+ break;
298
+ default:
299
+ break;
300
+ }
301
+ };
302
+
303
+ deviceAgent.device.on('connect', function () {
41
304
  deviceAgent.publishMessage('connection', deviceAgent.getClientId());
42
305
  console.log('Device Agent has connected to the cloud');
43
306
  });
44
307
 
45
- deviceAgent.device.on('disconnect', function() {
308
+ deviceAgent.device.on('disconnect', function () {
46
309
  console.log('Device Agent has been disconnected from the cloud');
47
310
  });
48
311
 
49
- deviceAgent.device.on('message', function(topic, payload) {
50
- // TODO: parse/determine action based on topic/payload here
51
- console.log('message', topic, payload.toString());
312
+ deviceAgent.device.on('message', function (topic, payload) {
313
+ // ToDo: insert valdiation here for incoming messages. Maybe we will use a JSON schema for the message structure.
314
+ try {
315
+ handleMessageTopic({ topic, payload: JSON.parse(payload) });
316
+ } catch (error) {
317
+ console.error(error);
318
+ }
52
319
  });
53
320
  }
@@ -1,4 +1,4 @@
1
- import { JsSpawner } from '../util/spawner/js-spawner';
1
+ import { JsSpawner } from 'alwaysai/lib/util';
2
2
 
3
3
  export async function runDockerLogin(props: { token: string }) {
4
4
  const { token } = props;
@@ -1,4 +1,4 @@
1
- import { JsSpawner } from '../util/spawner/js-spawner';
1
+ import { JsSpawner } from 'alwaysai/lib/util';
2
2
 
3
3
  export async function runDockerComposeCmd(props: { args: string[]; dir: string }) {
4
4
  const { args, dir } = props;
@@ -2,6 +2,8 @@ import { platform } from 'os';
2
2
 
3
3
  export const ALWAYSAI_OS_PLATFORM = parseOsPlatform(process.env.ALWAYSAI_OS_PLATFORM);
4
4
  export const ALWAYSAI_SHOW_HIDDEN = parseBoolean(process.env.ALWAYSAI_SHOW_HIDDEN);
5
+ export const ALWAYSAI_DEVICE_AGENT_MODE = process.env.ALWAYSAI_DEVICE_AGENT_MODE;
6
+ export const ALWAYSAI_LOG_LEVEL = process.env.AAI_LOG_LEVEL;
5
7
 
6
8
  function parseOsPlatform(str: string | undefined): NodeJS.Platform {
7
9
  switch (str) {
package/src/index.ts CHANGED
@@ -1,21 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // This file is the entry point for when this package is run as a CLI. Though
4
- // this file is not executable, the above "shebang" line is necessary as an
5
- // indicator to npm that this file is a Node.js script, not a shell script e.g.
3
+ /*
4
+ Though this file is not executable, the above "shebang" line is necessary as an
5
+ indicator to npm that this file is a Node.js script, not a shell script e.g.
6
+ */
6
7
 
7
8
  import { runCliAndExit } from '@alwaysai/alwayscli';
8
9
  import { root } from './root';
9
- import { runWebInterface } from './web/web-interface';
10
10
  import { runDeviceAgentCloudInterface } from './cloud-connection/device-agent-cloud-connection';
11
+ import { AgentConfigFile } from './infrastructure/agent-config';
12
+ import { ALWAYSAI_DEVICE_AGENT_MODE } from './environment';
11
13
 
12
14
  if (module === require.main) {
13
15
  console.log('Starting alwaysAI Device Agent');
16
+ if (!AgentConfigFile().exists()) {
17
+ AgentConfigFile().initialize();
18
+ }
14
19
 
15
- if (process.env.ALWAYSAI_DEVICE_AGENT_MODE === 'cloud') {
20
+ if (ALWAYSAI_DEVICE_AGENT_MODE === 'cloud') {
16
21
  runDeviceAgentCloudInterface();
17
- } else if (process.env.ALWAYSAI_DEVICE_AGENT_MODE === 'web') {
18
- runWebInterface();
19
22
  } else {
20
23
  runCliAndExit(root, {});
21
24
  }