@alwaysai/device-agent 1.4.0 → 1.5.0
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/config.js +2 -2
- package/lib/application-control/config.js.map +1 -1
- package/lib/application-control/install.d.ts.map +1 -1
- package/lib/application-control/install.js +1 -0
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/models.d.ts +5 -0
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +24 -12
- package/lib/application-control/models.js.map +1 -1
- package/lib/application-control/status.d.ts.map +1 -1
- package/lib/application-control/status.js +10 -12
- package/lib/application-control/status.js.map +1 -1
- package/lib/application-control/utils.js +2 -2
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +2 -2
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +35 -15
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts +3 -2
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +24 -21
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.test.js +132 -16
- package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
- package/lib/cloud-connection/passthrough-handler.d.ts +5 -3
- package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -1
- package/lib/cloud-connection/passthrough-handler.js +76 -62
- package/lib/cloud-connection/passthrough-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.d.ts +2 -0
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +5 -5
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.d.ts +3 -0
- package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
- package/lib/cloud-connection/transaction-manager.js +11 -0
- package/lib/cloud-connection/transaction-manager.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.test.js +102 -0
- package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
- package/lib/device-control/device-control.d.ts +10 -2
- package/lib/device-control/device-control.d.ts.map +1 -1
- package/lib/device-control/device-control.js +86 -10
- package/lib/device-control/device-control.js.map +1 -1
- package/lib/docker/docker-compose.d.ts +14 -0
- package/lib/docker/docker-compose.d.ts.map +1 -0
- package/lib/docker/docker-compose.js +56 -0
- package/lib/docker/docker-compose.js.map +1 -0
- package/lib/index.js +2 -5
- package/lib/index.js.map +1 -1
- package/lib/infrastructure/agent-config.d.ts +45 -14
- package/lib/infrastructure/agent-config.d.ts.map +1 -1
- package/lib/infrastructure/agent-config.js +30 -15
- package/lib/infrastructure/agent-config.js.map +1 -1
- package/lib/infrastructure/agent-config.test.js +3 -0
- package/lib/infrastructure/agent-config.test.js.map +1 -1
- package/lib/local-connection/rabbitmq-connection.js +11 -11
- package/lib/local-connection/rabbitmq-connection.js.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.d.ts +14 -22
- package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.js +34 -34
- package/lib/secure-tunneling/secure-tunneling.js.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.test.js +18 -18
- package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -1
- package/lib/subcommands/device/clean.js +5 -5
- package/lib/subcommands/device/clean.js.map +1 -1
- package/lib/subcommands/device/get-info.d.ts +2 -0
- package/lib/subcommands/device/get-info.d.ts.map +1 -0
- package/lib/subcommands/device/get-info.js +36 -0
- package/lib/subcommands/device/get-info.js.map +1 -0
- package/lib/subcommands/device/index.d.ts.map +1 -1
- package/lib/subcommands/device/index.js +11 -2
- package/lib/subcommands/device/index.js.map +1 -1
- package/lib/subcommands/device/init.d.ts +5 -0
- package/lib/subcommands/device/init.d.ts.map +1 -0
- package/lib/subcommands/device/{device.js → init.js} +2 -41
- package/lib/subcommands/device/init.js.map +1 -0
- package/lib/subcommands/device/refresh.d.ts +2 -0
- package/lib/subcommands/device/refresh.d.ts.map +1 -0
- package/lib/subcommands/device/refresh.js +24 -0
- package/lib/subcommands/device/refresh.js.map +1 -0
- package/lib/subcommands/device/restart.d.ts +2 -0
- package/lib/subcommands/device/restart.d.ts.map +1 -0
- package/lib/subcommands/device/restart.js +14 -0
- package/lib/subcommands/device/restart.js.map +1 -0
- package/lib/util/check-for-updates.d.ts +3 -0
- package/lib/util/check-for-updates.d.ts.map +1 -0
- package/lib/util/check-for-updates.js +69 -0
- package/lib/util/check-for-updates.js.map +1 -0
- package/lib/util/file.d.ts +7 -0
- package/lib/util/file.d.ts.map +1 -0
- package/lib/util/file.js +66 -0
- package/lib/util/file.js.map +1 -0
- package/lib/util/file.test.d.ts +2 -0
- package/lib/util/file.test.d.ts.map +1 -0
- package/lib/util/file.test.js +87 -0
- package/lib/util/file.test.js.map +1 -0
- package/package.json +8 -7
- package/readme.md +3 -3
- package/src/application-control/config.ts +1 -1
- package/src/application-control/install.ts +1 -0
- package/src/application-control/models.ts +36 -13
- package/src/application-control/status.ts +9 -7
- package/src/application-control/utils.ts +1 -1
- package/src/cloud-connection/device-agent-cloud-connection.ts +54 -30
- package/src/cloud-connection/live-updates-handler.test.ts +161 -20
- package/src/cloud-connection/live-updates-handler.ts +30 -26
- package/src/cloud-connection/passthrough-handler.ts +98 -76
- package/src/cloud-connection/shadow-handler.ts +19 -7
- package/src/cloud-connection/transaction-manager.test.ts +124 -0
- package/src/cloud-connection/transaction-manager.ts +15 -0
- package/src/device-control/device-control.ts +86 -11
- package/src/docker/docker-compose.ts +60 -0
- package/src/index.ts +2 -6
- package/src/infrastructure/agent-config.test.ts +3 -0
- package/src/infrastructure/agent-config.ts +38 -40
- package/src/local-connection/rabbitmq-connection.ts +8 -8
- package/src/secure-tunneling/secure-tunneling.test.ts +26 -26
- package/src/secure-tunneling/secure-tunneling.ts +48 -55
- package/src/subcommands/device/clean.ts +1 -1
- package/src/subcommands/device/get-info.ts +49 -0
- package/src/subcommands/device/index.ts +11 -2
- package/src/subcommands/device/{device.ts → init.ts} +0 -58
- package/src/subcommands/device/refresh.ts +22 -0
- package/src/subcommands/device/restart.ts +11 -0
- package/src/util/check-for-updates.ts +69 -0
- package/src/util/file.test.ts +90 -0
- package/src/util/file.ts +76 -0
- package/lib/docker/docker-compose-cmd.d.ts +0 -5
- package/lib/docker/docker-compose-cmd.d.ts.map +0 -1
- package/lib/docker/docker-compose-cmd.js +0 -16
- package/lib/docker/docker-compose-cmd.js.map +0 -1
- package/lib/subcommands/device/device.d.ts +0 -7
- package/lib/subcommands/device/device.d.ts.map +0 -1
- package/lib/subcommands/device/device.js.map +0 -1
- package/lib/util/safe-rimraf.d.ts +0 -2
- package/lib/util/safe-rimraf.d.ts.map +0 -1
- package/lib/util/safe-rimraf.js +0 -16
- package/lib/util/safe-rimraf.js.map +0 -1
- package/src/docker/docker-compose-cmd.ts +0 -15
- package/src/util/safe-rimraf.ts +0 -14
|
@@ -58,26 +58,7 @@ const ALWAYSAI_CONFIG_DIR = join(homedir(), '.config', 'alwaysai');
|
|
|
58
58
|
|
|
59
59
|
const AGENT_CONFIG_FILE_NAME = 'alwaysai.agent.json';
|
|
60
60
|
|
|
61
|
-
export
|
|
62
|
-
extends ConfigFileSchemaReturnType<AgentConfig> {
|
|
63
|
-
name: string;
|
|
64
|
-
getApps: () => Promise<InstalledAppConfig[]>;
|
|
65
|
-
getReadyApps;
|
|
66
|
-
getApp;
|
|
67
|
-
isAppPresent;
|
|
68
|
-
isAppReady;
|
|
69
|
-
removeApp;
|
|
70
|
-
setAppInstalling;
|
|
71
|
-
setAppInstalled;
|
|
72
|
-
setAppUninstalled;
|
|
73
|
-
getAppVersion: (props: { projectId: string }) => Promise<string>;
|
|
74
|
-
setAppBackup;
|
|
75
|
-
getAppBackup;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function AgentConfigFile(
|
|
79
|
-
dir = ALWAYSAI_CONFIG_DIR
|
|
80
|
-
): AgentJsonFileReturnType {
|
|
61
|
+
export function AgentConfigFile(dir = ALWAYSAI_CONFIG_DIR) {
|
|
81
62
|
const path = join(dir, AGENT_CONFIG_FILE_NAME);
|
|
82
63
|
const initialValue: AgentConfig = {
|
|
83
64
|
applications: []
|
|
@@ -88,31 +69,14 @@ export function AgentConfigFile(
|
|
|
88
69
|
initialValue
|
|
89
70
|
});
|
|
90
71
|
|
|
91
|
-
return {
|
|
92
|
-
...configFile,
|
|
93
|
-
name: AGENT_CONFIG_FILE_NAME,
|
|
94
|
-
getApps,
|
|
95
|
-
getReadyApps,
|
|
96
|
-
getApp,
|
|
97
|
-
isAppPresent,
|
|
98
|
-
isAppReady,
|
|
99
|
-
removeApp,
|
|
100
|
-
setAppInstalling,
|
|
101
|
-
setAppInstalled,
|
|
102
|
-
setAppUninstalled,
|
|
103
|
-
getAppVersion,
|
|
104
|
-
setAppBackup,
|
|
105
|
-
getAppBackup
|
|
106
|
-
};
|
|
107
|
-
|
|
108
72
|
async function getApps() {
|
|
109
73
|
const config = configFile.read();
|
|
110
|
-
return config
|
|
74
|
+
return config?.applications ? config.applications : [];
|
|
111
75
|
}
|
|
112
76
|
|
|
113
77
|
async function getReadyApps() {
|
|
114
78
|
const config = configFile.read();
|
|
115
|
-
return config
|
|
79
|
+
return config?.applications
|
|
116
80
|
? config.applications.filter((app) => {
|
|
117
81
|
return app.ready;
|
|
118
82
|
})
|
|
@@ -205,6 +169,23 @@ export function AgentConfigFile(
|
|
|
205
169
|
}
|
|
206
170
|
}
|
|
207
171
|
|
|
172
|
+
async function setAppUninstalling(props: { projectId: string }) {
|
|
173
|
+
const { projectId } = props;
|
|
174
|
+
const app = await getApp({ projectId });
|
|
175
|
+
if (app) {
|
|
176
|
+
await removeApp({ projectId });
|
|
177
|
+
const config = configFile.read();
|
|
178
|
+
config.applications.push({ ...app, ...{ ready: false } });
|
|
179
|
+
configFile.write(config);
|
|
180
|
+
} else {
|
|
181
|
+
// NOTE: we should never be setting an app as installed
|
|
182
|
+
// if it doesn't exist (setAppInstalling was never called)
|
|
183
|
+
throw new Error(
|
|
184
|
+
`App ${projectId} was not previously configured and could not be set to installed!`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
208
189
|
async function setAppUninstalled(props: { projectId: string }) {
|
|
209
190
|
const { projectId } = props;
|
|
210
191
|
await removeApp({ projectId });
|
|
@@ -235,9 +216,26 @@ export function AgentConfigFile(
|
|
|
235
216
|
async function getAppBackup(props: { projectId: string }) {
|
|
236
217
|
const { projectId } = props;
|
|
237
218
|
const app = await getApp({ projectId });
|
|
238
|
-
if (app
|
|
219
|
+
if (app?.backup) {
|
|
239
220
|
return app.backup;
|
|
240
221
|
}
|
|
241
222
|
return null;
|
|
242
223
|
}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
...configFile,
|
|
227
|
+
name: AGENT_CONFIG_FILE_NAME,
|
|
228
|
+
getApps,
|
|
229
|
+
getReadyApps,
|
|
230
|
+
getApp,
|
|
231
|
+
isAppPresent,
|
|
232
|
+
isAppReady,
|
|
233
|
+
setAppInstalling,
|
|
234
|
+
setAppInstalled,
|
|
235
|
+
setAppUninstalling,
|
|
236
|
+
setAppUninstalled,
|
|
237
|
+
getAppVersion,
|
|
238
|
+
setAppBackup,
|
|
239
|
+
getAppBackup
|
|
240
|
+
};
|
|
243
241
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { logger } from '../util/logger';
|
|
2
2
|
import sleep from '../util/sleep';
|
|
3
|
-
import compose from 'docker-compose';
|
|
3
|
+
import { compose } from '../docker/docker-compose';
|
|
4
4
|
import * as YAML from 'yaml';
|
|
5
5
|
|
|
6
6
|
import {
|
|
@@ -23,19 +23,19 @@ export async function checkRabbitMQContainerRunning() {
|
|
|
23
23
|
return false;
|
|
24
24
|
}
|
|
25
25
|
const containerData = await compose.ps({ cwd: DEVICE_AGENT_CFG_PATH });
|
|
26
|
-
if (
|
|
26
|
+
if (containerData !== undefined) {
|
|
27
27
|
const rabbitmqService = containerData.data.services[0];
|
|
28
28
|
if (
|
|
29
|
-
rabbitmqService
|
|
30
|
-
rabbitmqService.state
|
|
29
|
+
rabbitmqService?.name === rabbitMQContainerName &&
|
|
30
|
+
rabbitmqService.state.includes('Up')
|
|
31
31
|
) {
|
|
32
|
-
logger.debug('alwaysAI Local Connection is
|
|
32
|
+
logger.debug('alwaysAI Local Connection Container is up');
|
|
33
33
|
return true;
|
|
34
34
|
}
|
|
35
|
-
logger.debug('alwaysAI Local Connection is not
|
|
35
|
+
logger.debug('alwaysAI Local Connection Container is not up');
|
|
36
36
|
return false;
|
|
37
37
|
}
|
|
38
|
-
logger.warn('alwaysAI Local Connection
|
|
38
|
+
logger.warn('alwaysAI Local Connection Container is undefined');
|
|
39
39
|
return false;
|
|
40
40
|
}
|
|
41
41
|
|
|
@@ -97,7 +97,7 @@ export async function setupRabbitMQContainer() {
|
|
|
97
97
|
}
|
|
98
98
|
} catch (e) {
|
|
99
99
|
logger.error(
|
|
100
|
-
`Unable to start alwaysAI Device Agent Local Connection Container:\n${e
|
|
100
|
+
`Unable to start alwaysAI Device Agent Local Connection Container:\n${e}`
|
|
101
101
|
);
|
|
102
102
|
}
|
|
103
103
|
}
|
|
@@ -11,14 +11,15 @@ import { downloadFile } from '../util/download-file';
|
|
|
11
11
|
import { getArch, getDistribution, getOsVersion } from '../util/system-info';
|
|
12
12
|
import {
|
|
13
13
|
SecureTunnelHandlerSingleton,
|
|
14
|
-
SecureTunnelPortInfo,
|
|
15
|
-
SecureTunnelShadowDesRep,
|
|
16
14
|
SecureTunnelShadowUpdateDelta
|
|
17
15
|
} from './secure-tunneling';
|
|
18
16
|
// import { JsSpawner } from 'alwaysai/lib/util/spawner';
|
|
19
17
|
import { ChildProcess } from 'child_process';
|
|
20
18
|
import { killDetachedProcess, runDetachedProcess } from './spawner-detached';
|
|
21
|
-
|
|
19
|
+
import {
|
|
20
|
+
SecureTunnelPortInfo,
|
|
21
|
+
SecureTunnelShadowDescriptionReported
|
|
22
|
+
} from '@alwaysai/device-agent-schemas';
|
|
22
23
|
//-----------------------------------------------------------------------------
|
|
23
24
|
// mocks
|
|
24
25
|
//-----------------------------------------------------------------------------
|
|
@@ -173,11 +174,10 @@ describe('SecureTunnelHandlerSingleton', () => {
|
|
|
173
174
|
|
|
174
175
|
function transformDeltaToUpdateReported(
|
|
175
176
|
deltaMsg: SecureTunnelShadowUpdateDelta
|
|
176
|
-
):
|
|
177
|
+
): SecureTunnelShadowDescriptionReported {
|
|
177
178
|
const { version, state } = deltaMsg;
|
|
178
|
-
const reportedStateReported:
|
|
179
|
-
JSON.stringify(state)
|
|
180
|
-
);
|
|
179
|
+
const reportedStateReported: SecureTunnelShadowDescriptionReported =
|
|
180
|
+
JSON.parse(JSON.stringify(state));
|
|
181
181
|
return reportedStateReported;
|
|
182
182
|
}
|
|
183
183
|
|
|
@@ -671,16 +671,16 @@ describe('SecureTunnelHandlerSingleton', () => {
|
|
|
671
671
|
//---------------------------------------------------------------------------
|
|
672
672
|
// test secureTunnelNotifyHandler function
|
|
673
673
|
//---------------------------------------------------------------------------
|
|
674
|
-
it('should start Secure Tunnel, given
|
|
674
|
+
it('should start Secure Tunnel, given local proxy already downloaded and default shadow is not enabled, that is default SSH connection', async () => {
|
|
675
675
|
// Arrange
|
|
676
676
|
// --------------------------------------------------------------------
|
|
677
|
-
mockJsSpawner.exists.mockResolvedValueOnce(true); // mock that
|
|
677
|
+
mockJsSpawner.exists.mockResolvedValueOnce(true); // mock that local proxy already exists on file system
|
|
678
678
|
const message: SecureTunnelNotifyMsg = {
|
|
679
679
|
clientAccessToken: 'DefaultSSHConnection_001',
|
|
680
680
|
region: 'us-west-2',
|
|
681
681
|
services: ['SSH']
|
|
682
682
|
};
|
|
683
|
-
const
|
|
683
|
+
const expectedLocalProxyArgs = [
|
|
684
684
|
'--destination-app',
|
|
685
685
|
'22',
|
|
686
686
|
'--region',
|
|
@@ -709,12 +709,12 @@ describe('SecureTunnelHandlerSingleton', () => {
|
|
|
709
709
|
expect(runDetachedProcess).toHaveBeenCalledTimes(1);
|
|
710
710
|
expect(runDetachedProcess).toHaveBeenCalledWith(
|
|
711
711
|
SECURE_TUNNEL_BIN_PATH,
|
|
712
|
-
|
|
712
|
+
expectedLocalProxyArgs
|
|
713
713
|
);
|
|
714
714
|
expect(killDetachedProcess).not.toHaveBeenCalled();
|
|
715
715
|
});
|
|
716
716
|
|
|
717
|
-
const
|
|
717
|
+
const testCasesForDIfferentLocalProxyEnv: [string, string, string][] = [
|
|
718
718
|
// linuxDistro, osVersion, arch
|
|
719
719
|
['debian', '12', 'aarch64'],
|
|
720
720
|
['debian', '12', 'arm64'],
|
|
@@ -734,12 +734,12 @@ describe('SecureTunnelHandlerSingleton', () => {
|
|
|
734
734
|
['ubuntu', '23.10', 'amd64'],
|
|
735
735
|
['ubuntu', '23.10', 'arm64']
|
|
736
736
|
];
|
|
737
|
-
test.each(
|
|
738
|
-
'should start Secure Tunnel, given
|
|
737
|
+
test.each(testCasesForDIfferentLocalProxyEnv)(
|
|
738
|
+
'should start Secure Tunnel, given local proxy downloaded needed and default shadow is not enabled, that is default SSH connection',
|
|
739
739
|
async (linuxDistro: string, osVersion: string, arch: string) => {
|
|
740
740
|
// Arrange
|
|
741
741
|
// --------------------------------------------------------------------
|
|
742
|
-
mockJsSpawner.exists.mockResolvedValueOnce(false); // mock that
|
|
742
|
+
mockJsSpawner.exists.mockResolvedValueOnce(false); // mock that local proxy does not exist on file system
|
|
743
743
|
jest.mocked(getArch).mockResolvedValueOnce(arch);
|
|
744
744
|
jest.mocked(getOsVersion).mockResolvedValueOnce(osVersion);
|
|
745
745
|
jest.mocked(getDistribution).mockResolvedValueOnce(linuxDistro);
|
|
@@ -749,7 +749,7 @@ describe('SecureTunnelHandlerSingleton', () => {
|
|
|
749
749
|
region: 'us-west-2',
|
|
750
750
|
services: ['SSH']
|
|
751
751
|
};
|
|
752
|
-
const
|
|
752
|
+
const expectedLocalProxyArgs = [
|
|
753
753
|
'--destination-app',
|
|
754
754
|
'22',
|
|
755
755
|
'--region',
|
|
@@ -791,7 +791,7 @@ describe('SecureTunnelHandlerSingleton', () => {
|
|
|
791
791
|
expect(runDetachedProcess).toHaveBeenCalledTimes(1);
|
|
792
792
|
expect(runDetachedProcess).toHaveBeenCalledWith(
|
|
793
793
|
SECURE_TUNNEL_BIN_PATH,
|
|
794
|
-
|
|
794
|
+
expectedLocalProxyArgs
|
|
795
795
|
);
|
|
796
796
|
expect(killDetachedProcess).not.toHaveBeenCalled();
|
|
797
797
|
}
|
|
@@ -891,8 +891,8 @@ describe('SecureTunnelHandlerSingleton', () => {
|
|
|
891
891
|
async (message) => {
|
|
892
892
|
// Arrange
|
|
893
893
|
// --------------------------------------------------------------------
|
|
894
|
-
mockJsSpawner.exists.mockResolvedValueOnce(true); // mock that
|
|
895
|
-
const
|
|
894
|
+
mockJsSpawner.exists.mockResolvedValueOnce(true); // mock that local proxy already exists on file system
|
|
895
|
+
const expectedLocalProxyArgs = [
|
|
896
896
|
'--destination-app',
|
|
897
897
|
'22',
|
|
898
898
|
'--region',
|
|
@@ -1003,8 +1003,8 @@ describe('SecureTunnelHandlerSingleton', () => {
|
|
|
1003
1003
|
expect(actualReportedShadow).toEqual(updateReported);
|
|
1004
1004
|
jest.clearAllMocks(); // after setting up the shadow, we need to reset mocks again
|
|
1005
1005
|
jest.resetModules();
|
|
1006
|
-
mockJsSpawner.exists.mockResolvedValueOnce(true); // mock that
|
|
1007
|
-
const
|
|
1006
|
+
mockJsSpawner.exists.mockResolvedValueOnce(true); // mock that local proxy already exists on file system
|
|
1007
|
+
const expectedLocalProxyArgs = [
|
|
1008
1008
|
'--destination-app',
|
|
1009
1009
|
'22',
|
|
1010
1010
|
'--region',
|
|
@@ -1128,8 +1128,8 @@ describe('SecureTunnelHandlerSingleton', () => {
|
|
|
1128
1128
|
expect(actualReportedShadow).toEqual(updateReported);
|
|
1129
1129
|
jest.clearAllMocks(); // after setting up the shadow, we need to reset mocks again
|
|
1130
1130
|
jest.resetModules();
|
|
1131
|
-
mockJsSpawner.exists.mockResolvedValueOnce(true); // mock that
|
|
1132
|
-
const
|
|
1131
|
+
mockJsSpawner.exists.mockResolvedValueOnce(true); // mock that local proxy already exists on file system
|
|
1132
|
+
const expectedLocalProxyArgs = [
|
|
1133
1133
|
'--destination-app',
|
|
1134
1134
|
expectedPortMapping,
|
|
1135
1135
|
'--region',
|
|
@@ -1158,7 +1158,7 @@ describe('SecureTunnelHandlerSingleton', () => {
|
|
|
1158
1158
|
expect(runDetachedProcess).toHaveBeenCalledTimes(1);
|
|
1159
1159
|
expect(runDetachedProcess).toHaveBeenCalledWith(
|
|
1160
1160
|
SECURE_TUNNEL_BIN_PATH,
|
|
1161
|
-
|
|
1161
|
+
expectedLocalProxyArgs
|
|
1162
1162
|
);
|
|
1163
1163
|
expect(killDetachedProcess).toHaveBeenCalledTimes(0);
|
|
1164
1164
|
}
|
|
@@ -1192,8 +1192,8 @@ describe('SecureTunnelHandlerSingleton', () => {
|
|
|
1192
1192
|
expect(beforeReportedShadow).toEqual(updateReported);
|
|
1193
1193
|
jest.clearAllMocks(); // after setting up the shadow, we need to reset mocks again
|
|
1194
1194
|
jest.resetModules();
|
|
1195
|
-
mockJsSpawner.exists.mockResolvedValueOnce(true); // mock that
|
|
1196
|
-
const
|
|
1195
|
+
mockJsSpawner.exists.mockResolvedValueOnce(true); // mock that local proxy already exists on file system
|
|
1196
|
+
const expectedLocalProxyArgs = [
|
|
1197
1197
|
'--destination-app',
|
|
1198
1198
|
expectedPortMapping,
|
|
1199
1199
|
'--region',
|
|
@@ -14,32 +14,25 @@ import { downloadFile } from '../util/download-file';
|
|
|
14
14
|
import { logger } from '../util/logger';
|
|
15
15
|
import { getArch, getDistribution, getOsVersion } from '../util/system-info';
|
|
16
16
|
import { killDetachedProcess, runDetachedProcess } from './spawner-detached';
|
|
17
|
+
import {
|
|
18
|
+
SecureTunnelPortInfo,
|
|
19
|
+
SecureTunnelShadowDescriptionReported
|
|
20
|
+
} from '@alwaysai/device-agent-schemas';
|
|
17
21
|
|
|
18
22
|
enum SecureTunnelServiceType {
|
|
19
23
|
SSH = 'SSH',
|
|
20
24
|
HTTP = 'HTTP'
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
export type SecureTunnelPortInfo = {
|
|
24
|
-
enabled: boolean;
|
|
25
|
-
type: string;
|
|
26
|
-
ip: string;
|
|
27
|
-
port: number;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export type SecureTunnelShadowDesRep = {
|
|
31
|
-
st_ports: SecureTunnelPortInfo[];
|
|
32
|
-
};
|
|
33
|
-
|
|
34
27
|
export type SecureTunnelShadowState = {
|
|
35
|
-
reported?:
|
|
36
|
-
desired?:
|
|
28
|
+
reported?: SecureTunnelShadowDescriptionReported;
|
|
29
|
+
desired?: SecureTunnelShadowDescriptionReported;
|
|
37
30
|
};
|
|
38
31
|
|
|
39
32
|
export type SecureTunnelShadowUpdateDelta = {
|
|
40
33
|
version: number;
|
|
41
34
|
timestamp: number;
|
|
42
|
-
state:
|
|
35
|
+
state: SecureTunnelShadowDescriptionReported;
|
|
43
36
|
metadata?: any;
|
|
44
37
|
};
|
|
45
38
|
|
|
@@ -54,7 +47,7 @@ type SecureTunnelProxyMap = {
|
|
|
54
47
|
mapProcess: ChildProcess;
|
|
55
48
|
};
|
|
56
49
|
|
|
57
|
-
type
|
|
50
|
+
type SecureTunnelLocalProxyInfo = {
|
|
58
51
|
lpDstAccessKey: string;
|
|
59
52
|
lpRegion: string;
|
|
60
53
|
lpServices: string;
|
|
@@ -83,9 +76,9 @@ const ST_START_PORT_NUMBER = 5010;
|
|
|
83
76
|
*/
|
|
84
77
|
export class SecureTunnelHandlerSingleton {
|
|
85
78
|
private static instance: SecureTunnelHandlerSingleton;
|
|
86
|
-
private reported:
|
|
79
|
+
private reported: SecureTunnelShadowDescriptionReported;
|
|
87
80
|
private httpProxyMap: SecureTunnelProxyMap[];
|
|
88
|
-
private
|
|
81
|
+
private localProxyInfo: SecureTunnelLocalProxyInfo;
|
|
89
82
|
|
|
90
83
|
/**
|
|
91
84
|
* Initializes private variables of SecureTunnel handler.
|
|
@@ -97,7 +90,7 @@ export class SecureTunnelHandlerSingleton {
|
|
|
97
90
|
st_ports: [JSON.parse(JSON.stringify(defaultSecureTunnelPortInfo))]
|
|
98
91
|
};
|
|
99
92
|
this.httpProxyMap = [];
|
|
100
|
-
this.
|
|
93
|
+
this.localProxyInfo = {
|
|
101
94
|
lpDstAccessKey: '',
|
|
102
95
|
lpRegion: '',
|
|
103
96
|
lpServices: '',
|
|
@@ -147,15 +140,15 @@ export class SecureTunnelHandlerSingleton {
|
|
|
147
140
|
};
|
|
148
141
|
logger.debug(`httpProxyMap after : ${JSON.stringify(this.httpProxyMap)}`);
|
|
149
142
|
logger.debug(`reported after : ${JSON.stringify(this.reported)}`);
|
|
150
|
-
await this.
|
|
143
|
+
await this.stopLocalProxy();
|
|
151
144
|
logger.debug('<- SecureTunnelHandlerSingleton.destroy');
|
|
152
145
|
}
|
|
153
146
|
|
|
154
147
|
/**
|
|
155
148
|
* Returns current state of SecureTunnel shadow
|
|
156
|
-
* @returns {
|
|
149
|
+
* @returns {SecureTunnelShadowDescriptionReported} - reported state of the SecureTunnel shadow
|
|
157
150
|
*/
|
|
158
|
-
public getSecureTunnelShadow():
|
|
151
|
+
public getSecureTunnelShadow(): SecureTunnelShadowDescriptionReported {
|
|
159
152
|
logger.debug('-> SecureTunnelHandlerSingleton.getSecureTunnelShadow');
|
|
160
153
|
logger.debug(`reported: ${JSON.stringify(this.reported)}`);
|
|
161
154
|
logger.debug('<- SecureTunnelHandlerSingleton.getSecureTunnelShadow');
|
|
@@ -165,11 +158,11 @@ export class SecureTunnelHandlerSingleton {
|
|
|
165
158
|
/**
|
|
166
159
|
* Updates current state of SecureTunnel shadow
|
|
167
160
|
* @param {SecureTunnelShadowUpdateDelta} deltaMsg - delta message, which includes desired state of the SecureTunnel shadow
|
|
168
|
-
* @return {
|
|
161
|
+
* @return {SecureTunnelShadowDescriptionReported} update reported message to send back to AWS IoT device shadow
|
|
169
162
|
*/
|
|
170
163
|
public async syncShadowToDeviceState(
|
|
171
164
|
deltaMsg: SecureTunnelShadowUpdateDelta
|
|
172
|
-
): Promise<
|
|
165
|
+
): Promise<SecureTunnelShadowDescriptionReported> {
|
|
173
166
|
logger.debug('-> SecureTunnelHandlerSingleton.syncShadowToDeviceState');
|
|
174
167
|
const { version, state } = deltaMsg;
|
|
175
168
|
if (!state || typeof state.st_ports === 'undefined') {
|
|
@@ -216,7 +209,7 @@ export class SecureTunnelHandlerSingleton {
|
|
|
216
209
|
|
|
217
210
|
// if all entries are disabled, we need also to kill local proxy
|
|
218
211
|
if (this.reported.st_ports.every((portInfo) => !portInfo.enabled)) {
|
|
219
|
-
await this.
|
|
212
|
+
await this.stopLocalProxy();
|
|
220
213
|
}
|
|
221
214
|
|
|
222
215
|
// need to order list in the same order as desired before sending back
|
|
@@ -237,15 +230,15 @@ export class SecureTunnelHandlerSingleton {
|
|
|
237
230
|
logger.debug('-> SecureTunnelHandlerSingleton.secureTunnelNotifyHandler');
|
|
238
231
|
|
|
239
232
|
try {
|
|
240
|
-
await this.
|
|
233
|
+
await this.stopLocalProxy();
|
|
241
234
|
this.processNotifyMessage(message);
|
|
242
235
|
await this.downloadSecureTunnel();
|
|
243
|
-
await this.
|
|
236
|
+
await this.startLocalProxy();
|
|
244
237
|
} catch (error) {
|
|
245
238
|
logger.error(error);
|
|
246
239
|
}
|
|
247
240
|
|
|
248
|
-
logger.info(`Local Proxy Started: ${JSON.stringify(this.
|
|
241
|
+
logger.info(`Local Proxy Started: ${JSON.stringify(this.localProxyInfo)}`);
|
|
249
242
|
logger.debug('<- SecureTunnelHandlerSingleton.secureTunnelNotifyHandler');
|
|
250
243
|
}
|
|
251
244
|
|
|
@@ -389,60 +382,60 @@ export class SecureTunnelHandlerSingleton {
|
|
|
389
382
|
}
|
|
390
383
|
|
|
391
384
|
/**
|
|
392
|
-
* Starts SecureTunnel
|
|
385
|
+
* Starts SecureTunnel localProxy process
|
|
393
386
|
*/
|
|
394
|
-
private async
|
|
395
|
-
logger.debug('-> SecureTunnelHandlerSingleton.
|
|
387
|
+
private async startLocalProxy(): Promise<void> {
|
|
388
|
+
logger.debug('-> SecureTunnelHandlerSingleton.startLocalProxy');
|
|
396
389
|
|
|
397
390
|
const args = [
|
|
398
391
|
'--destination-app',
|
|
399
|
-
this.
|
|
392
|
+
this.localProxyInfo.lpServices,
|
|
400
393
|
'--region',
|
|
401
|
-
this.
|
|
394
|
+
this.localProxyInfo.lpRegion,
|
|
402
395
|
'--capath',
|
|
403
396
|
AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
404
397
|
'--local-bind-address',
|
|
405
398
|
'0.0.0.0',
|
|
406
399
|
'-t',
|
|
407
|
-
this.
|
|
400
|
+
this.localProxyInfo.lpDstAccessKey
|
|
408
401
|
];
|
|
409
402
|
|
|
410
|
-
this.
|
|
403
|
+
this.localProxyInfo.lpProcess = await runDetachedProcess(
|
|
411
404
|
SECURE_TUNNEL_BIN_PATH,
|
|
412
405
|
args
|
|
413
406
|
);
|
|
414
|
-
logger.debug('<- SecureTunnelHandlerSingleton.
|
|
407
|
+
logger.debug('<- SecureTunnelHandlerSingleton.startLocalProxy');
|
|
415
408
|
}
|
|
416
409
|
|
|
417
410
|
/**
|
|
418
|
-
* Stops SecureTunnel
|
|
411
|
+
* Stops SecureTunnel local proxy process
|
|
419
412
|
*/
|
|
420
|
-
private async
|
|
421
|
-
logger.debug('-> SecureTunnelHandlerSingleton.
|
|
422
|
-
if (!this.
|
|
423
|
-
logger.debug('No
|
|
424
|
-
logger.debug('<- SecureTunnelHandlerSingleton.
|
|
413
|
+
private async stopLocalProxy(): Promise<void> {
|
|
414
|
+
logger.debug('-> SecureTunnelHandlerSingleton.stopLocalProxy');
|
|
415
|
+
if (!this.localProxyInfo.lpProcess) {
|
|
416
|
+
logger.debug('No local proxy process running');
|
|
417
|
+
logger.debug('<- SecureTunnelHandlerSingleton.stopLocalProxy');
|
|
425
418
|
return;
|
|
426
419
|
}
|
|
427
420
|
|
|
428
421
|
try {
|
|
429
422
|
logger.debug(
|
|
430
|
-
`About to kill
|
|
423
|
+
`About to kill local proxy: ${this.localProxyInfo.lpDstAccessKey}`
|
|
431
424
|
);
|
|
432
|
-
await killDetachedProcess(this.
|
|
425
|
+
await killDetachedProcess(this.localProxyInfo.lpProcess, [
|
|
433
426
|
SECURE_TUNNEL_BIN_PATH
|
|
434
427
|
]);
|
|
435
|
-
logger.debug('SUCCESS: killing
|
|
428
|
+
logger.debug('SUCCESS: killing local proxy process');
|
|
436
429
|
} catch (e) {
|
|
437
|
-
logger.error('ERROR: killing
|
|
430
|
+
logger.error('ERROR: killing local proxy process:', e);
|
|
438
431
|
}
|
|
439
|
-
this.
|
|
432
|
+
this.localProxyInfo = {
|
|
440
433
|
lpDstAccessKey: '',
|
|
441
434
|
lpRegion: '',
|
|
442
435
|
lpServices: '',
|
|
443
436
|
lpProcess: null
|
|
444
437
|
};
|
|
445
|
-
logger.debug('<- SecureTunnelHandlerSingleton.
|
|
438
|
+
logger.debug('<- SecureTunnelHandlerSingleton.stopLocalProxy');
|
|
446
439
|
}
|
|
447
440
|
|
|
448
441
|
/**
|
|
@@ -524,16 +517,16 @@ export class SecureTunnelHandlerSingleton {
|
|
|
524
517
|
});
|
|
525
518
|
}
|
|
526
519
|
|
|
527
|
-
this.
|
|
528
|
-
this.
|
|
529
|
-
this.
|
|
520
|
+
this.localProxyInfo.lpDstAccessKey = clientAccessToken;
|
|
521
|
+
this.localProxyInfo.lpRegion = region;
|
|
522
|
+
this.localProxyInfo.lpServices = portMappingList.join(',');
|
|
530
523
|
logger.debug(`reported = ${JSON.stringify(this.reported.st_ports)}`);
|
|
531
|
-
logger.debug(`
|
|
524
|
+
logger.debug(`localProxyInfo = ${JSON.stringify(this.localProxyInfo)}`);
|
|
532
525
|
logger.debug('<- SecureTunnelHandlerSingleton.processNotifyMessage');
|
|
533
526
|
}
|
|
534
527
|
|
|
535
528
|
/**
|
|
536
|
-
* Downloads SecureTunnel
|
|
529
|
+
* Downloads SecureTunnel local proxy, if it was not downloaded before
|
|
537
530
|
*/
|
|
538
531
|
private async downloadSecureTunnel(): Promise<void> {
|
|
539
532
|
logger.debug('-> SecureTunnelHandlerSingleton.downloadSecureTunnel');
|
|
@@ -549,7 +542,7 @@ export class SecureTunnelHandlerSingleton {
|
|
|
549
542
|
getOsVersion()
|
|
550
543
|
]);
|
|
551
544
|
|
|
552
|
-
logger.info('Downloading SecureTunnel
|
|
545
|
+
logger.info('Downloading SecureTunnel local proxy ...');
|
|
553
546
|
const url = `${aaiArtifactsBucketUrl}/securetunnel/${linuxDistro}/${osVersion}/${arch}/${SECURE_TUNNEL_BIN_NAME}`;
|
|
554
547
|
await JsSpawner().mkdirp(join(AAI_DIR, SECURE_TUNNEL_BIN_DIR));
|
|
555
548
|
await downloadFile({
|
|
@@ -585,8 +578,8 @@ export class SecureTunnelHandlerSingleton {
|
|
|
585
578
|
}
|
|
586
579
|
|
|
587
580
|
private sortPorts(
|
|
588
|
-
desired:
|
|
589
|
-
):
|
|
581
|
+
desired: SecureTunnelShadowDescriptionReported
|
|
582
|
+
): SecureTunnelShadowDescriptionReported {
|
|
590
583
|
logger.debug('-> SecureTunnelHandlerSingleton.sortPorts');
|
|
591
584
|
const sortedPorts = JSON.parse(JSON.stringify(this.reported));
|
|
592
585
|
sortedPorts.st_ports.sort((a, b) => {
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from '../../local-connection/rabbitmq-connection';
|
|
15
15
|
import { stopApp } from '../../application-control';
|
|
16
16
|
import { LOCAL_CERT_AND_KEY_DIR } from 'alwaysai/lib/paths';
|
|
17
|
-
import safeRimraf from '../../util/
|
|
17
|
+
import { safeRimraf } from '../../util/file';
|
|
18
18
|
|
|
19
19
|
export const cleanCliLeaf = CliLeaf({
|
|
20
20
|
name: 'clean',
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { CliLeaf } from '@alwaysai/alwayscli';
|
|
2
|
+
import {
|
|
3
|
+
getCpuDetails,
|
|
4
|
+
getDiskDetails,
|
|
5
|
+
getMemDetails,
|
|
6
|
+
getSystemInformation
|
|
7
|
+
} from '../../device-control/device-control';
|
|
8
|
+
|
|
9
|
+
export const getInfoCliLeaf = CliLeaf({
|
|
10
|
+
name: 'get-info',
|
|
11
|
+
description: 'Get device info',
|
|
12
|
+
namedInputs: {},
|
|
13
|
+
async action(_, opts) {
|
|
14
|
+
const cpuDetails = await getCpuDetails();
|
|
15
|
+
const diskDetails = await getDiskDetails();
|
|
16
|
+
const memDetails = await getMemDetails();
|
|
17
|
+
const out = {
|
|
18
|
+
'CPU Utilization': `${
|
|
19
|
+
cpuDetails?.usedPerc && cpuDetails.temperature
|
|
20
|
+
? `Used ${cpuDetails?.usedPerc?.toFixed(2)}%, Temperature ${
|
|
21
|
+
cpuDetails?.temperature
|
|
22
|
+
} °C`
|
|
23
|
+
: 'Unavailable'
|
|
24
|
+
}`,
|
|
25
|
+
'Disk Utilization': `${
|
|
26
|
+
diskDetails?.usedGb && diskDetails.freeGb
|
|
27
|
+
? `${diskDetails?.usedGb} GB / ${
|
|
28
|
+
diskDetails?.usedGb + diskDetails?.freeGb
|
|
29
|
+
} GB`
|
|
30
|
+
: 'Unavailable'
|
|
31
|
+
}`,
|
|
32
|
+
'Memory Utilization': `${
|
|
33
|
+
memDetails?.usedMb && memDetails.freeMb
|
|
34
|
+
? `${memDetails.usedMb} MB / ${
|
|
35
|
+
memDetails.usedMb + memDetails.freeMb
|
|
36
|
+
} MB`
|
|
37
|
+
: 'Unavailable'
|
|
38
|
+
}`
|
|
39
|
+
};
|
|
40
|
+
console.table(out);
|
|
41
|
+
const systemInfo = await getSystemInformation();
|
|
42
|
+
console.table(systemInfo.os);
|
|
43
|
+
console.table(systemInfo.cpu);
|
|
44
|
+
console.table(systemInfo.disk?.drives);
|
|
45
|
+
console.table(systemInfo.device);
|
|
46
|
+
console.table(systemInfo.network);
|
|
47
|
+
console.table(systemInfo.versions);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { CliBranch } from '@alwaysai/alwayscli';
|
|
2
|
-
import { getInfoCliLeaf
|
|
2
|
+
import { getInfoCliLeaf } from './get-info';
|
|
3
3
|
import { cleanCliLeaf } from './clean';
|
|
4
|
+
import { initCliLeaf } from './init';
|
|
5
|
+
import { restartCliLeaf } from './restart';
|
|
6
|
+
import { refreshCliLeaf } from './refresh';
|
|
4
7
|
|
|
5
8
|
export const deviceCliBranch = CliBranch({
|
|
6
9
|
name: 'device',
|
|
7
10
|
description: 'Manage current device',
|
|
8
|
-
subcommands: [
|
|
11
|
+
subcommands: [
|
|
12
|
+
initCliLeaf,
|
|
13
|
+
getInfoCliLeaf,
|
|
14
|
+
refreshCliLeaf,
|
|
15
|
+
cleanCliLeaf,
|
|
16
|
+
restartCliLeaf
|
|
17
|
+
]
|
|
9
18
|
});
|