@alwaysai/device-agent 2.0.2 → 2.0.3
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 +1 -1
- 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 +2 -2
- package/lib/application-control/install.js.map +1 -1
- package/lib/cloud-connection/base-message-handler.d.ts.map +1 -1
- package/lib/cloud-connection/base-message-handler.js +5 -4
- package/lib/cloud-connection/base-message-handler.js.map +1 -1
- package/lib/cloud-connection/connection-manager.js +2 -2
- package/lib/cloud-connection/connection-manager.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +3 -8
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/device-agent-message-handler.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-message-handler.js +19 -18
- package/lib/cloud-connection/device-agent-message-handler.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +11 -4
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/passthrough-handler.d.ts +3 -3
- package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -1
- package/lib/cloud-connection/passthrough-handler.js +97 -74
- package/lib/cloud-connection/passthrough-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +3 -3
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/shadow.d.ts.map +1 -1
- package/lib/cloud-connection/shadow.js +1 -1
- package/lib/cloud-connection/shadow.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
- package/lib/cloud-connection/transaction-manager.js +17 -7
- package/lib/cloud-connection/transaction-manager.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.test.js +52 -44
- package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
- package/lib/device-control/device-control.d.ts.map +1 -1
- package/lib/device-control/device-control.js +13 -9
- package/lib/device-control/device-control.js.map +1 -1
- package/lib/docker/docker-compose.d.ts.map +1 -1
- package/lib/docker/docker-compose.js +1 -1
- package/lib/docker/docker-compose.js.map +1 -1
- package/lib/environment.d.ts.map +1 -1
- package/lib/environment.js +1 -1
- package/lib/environment.js.map +1 -1
- package/lib/index.js +12 -0
- package/lib/index.js.map +1 -1
- package/lib/infrastructure/config-check-utility.js +2 -2
- package/lib/infrastructure/config-check-utility.js.map +1 -1
- package/lib/infrastructure/legacy-migration/legacy-migration.d.ts.map +1 -1
- package/lib/infrastructure/legacy-migration/legacy-migration.js +6 -10
- package/lib/infrastructure/legacy-migration/legacy-migration.js.map +1 -1
- package/lib/local-connection/rabbitmq-container.d.ts +6 -0
- package/lib/local-connection/rabbitmq-container.d.ts.map +1 -0
- package/lib/local-connection/rabbitmq-container.js +111 -0
- package/lib/local-connection/rabbitmq-container.js.map +1 -0
- package/lib/local-connection/rabbitmq-container.test.d.ts +2 -0
- package/lib/local-connection/rabbitmq-container.test.d.ts.map +1 -0
- package/lib/local-connection/rabbitmq-container.test.js +219 -0
- package/lib/local-connection/rabbitmq-container.test.js.map +1 -0
- package/lib/subcommands/device/clean.d.ts.map +1 -1
- package/lib/subcommands/device/clean.js +15 -17
- package/lib/subcommands/device/clean.js.map +1 -1
- package/lib/subcommands/device/index.d.ts.map +1 -1
- package/lib/subcommands/device/index.js +3 -1
- package/lib/subcommands/device/index.js.map +1 -1
- package/lib/subcommands/device/local-connection.d.ts +2 -0
- package/lib/subcommands/device/local-connection.d.ts.map +1 -0
- package/lib/subcommands/device/local-connection.js +17 -0
- package/lib/subcommands/device/local-connection.js.map +1 -0
- package/lib/subcommands/index.d.ts +4 -1
- package/lib/subcommands/index.d.ts.map +1 -1
- package/lib/subcommands/index.js +1 -3
- package/lib/subcommands/index.js.map +1 -1
- package/lib/util/check-for-updates.d.ts.map +1 -1
- package/lib/util/check-for-updates.js +2 -2
- package/lib/util/check-for-updates.js.map +1 -1
- package/lib/util/file.d.ts.map +1 -1
- package/lib/util/file.js +6 -1
- package/lib/util/file.js.map +1 -1
- package/lib/util/file.test.js +1 -1
- package/lib/util/file.test.js.map +1 -1
- package/lib/util/get-device-id.d.ts.map +1 -1
- package/lib/util/get-device-id.js +1 -1
- package/lib/util/get-device-id.js.map +1 -1
- package/package.json +2 -2
- package/src/application-control/config.ts +1 -1
- package/src/application-control/install.ts +3 -2
- package/src/cloud-connection/base-message-handler.ts +10 -5
- package/src/cloud-connection/connection-manager.ts +2 -2
- package/src/cloud-connection/device-agent-cloud-connection.ts +6 -10
- package/src/cloud-connection/device-agent-message-handler.ts +10 -7
- package/src/cloud-connection/live-updates-handler.ts +12 -5
- package/src/cloud-connection/passthrough-handler.ts +79 -38
- package/src/cloud-connection/shadow-handler.ts +3 -3
- package/src/cloud-connection/shadow.ts +3 -1
- package/src/cloud-connection/transaction-manager.test.ts +60 -41
- package/src/cloud-connection/transaction-manager.ts +26 -12
- package/src/device-control/device-control.ts +23 -13
- package/src/docker/docker-compose.ts +3 -1
- package/src/environment.ts +1 -2
- package/src/index.ts +19 -0
- package/src/infrastructure/config-check-utility.ts +2 -2
- package/src/infrastructure/legacy-migration/legacy-migration.ts +8 -13
- package/src/local-connection/rabbitmq-container.test.ts +255 -0
- package/src/local-connection/rabbitmq-container.ts +151 -0
- package/src/subcommands/device/clean.ts +20 -19
- package/src/subcommands/device/index.ts +3 -1
- package/src/subcommands/device/local-connection.ts +16 -0
- package/src/subcommands/index.ts +1 -3
- package/src/util/check-for-updates.ts +4 -2
- package/src/util/file.test.ts +1 -1
- package/src/util/file.ts +7 -1
- package/src/util/get-device-id.ts +3 -1
- package/lib/local-connection/rabbitmq-connection.d.ts +0 -7
- package/lib/local-connection/rabbitmq-connection.d.ts.map +0 -1
- package/lib/local-connection/rabbitmq-connection.js +0 -95
- package/lib/local-connection/rabbitmq-connection.js.map +0 -1
- package/lib/subcommands/rabbitmq-connection.d.ts +0 -2
- package/lib/subcommands/rabbitmq-connection.d.ts.map +0 -1
- package/lib/subcommands/rabbitmq-connection.js +0 -14
- package/lib/subcommands/rabbitmq-connection.js.map +0 -1
- package/src/local-connection/rabbitmq-connection.ts +0 -124
- package/src/subcommands/rabbitmq-connection.ts +0 -11
|
@@ -137,30 +137,40 @@ export async function getNetworkInfo() {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
export async function getDockerVersion() {
|
|
140
|
+
let result: string | undefined;
|
|
140
141
|
try {
|
|
141
142
|
const spawner = JsSpawner();
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
);
|
|
148
|
-
return String(result.ClientInfo.Version);
|
|
143
|
+
result = await spawner.run({
|
|
144
|
+
exe: 'docker',
|
|
145
|
+
args: ['info', '--format', 'json']
|
|
146
|
+
});
|
|
147
|
+
const parsedResult = JSON.parse(result);
|
|
148
|
+
return String(parsedResult.ClientInfo.Version);
|
|
149
149
|
} catch (e) {
|
|
150
|
-
logger.warn(
|
|
150
|
+
logger.warn(
|
|
151
|
+
`Cannot get Docker version! Result: ${result} Error:\n${stringifyError(
|
|
152
|
+
e
|
|
153
|
+
)}`
|
|
154
|
+
);
|
|
151
155
|
return 'Not found';
|
|
152
156
|
}
|
|
153
157
|
}
|
|
154
158
|
|
|
155
159
|
export async function getDockerComposeVersion() {
|
|
160
|
+
let result: string | undefined;
|
|
156
161
|
try {
|
|
157
162
|
const spawner = JsSpawner();
|
|
158
|
-
|
|
163
|
+
result = await spawner.run({
|
|
159
164
|
exe: 'docker',
|
|
160
165
|
args: ['compose', 'version']
|
|
161
166
|
});
|
|
167
|
+
return result;
|
|
162
168
|
} catch (e) {
|
|
163
|
-
logger.warn(
|
|
169
|
+
logger.warn(
|
|
170
|
+
`Cannot get Docker Compose version! Results: ${result} Error:\n${stringifyError(
|
|
171
|
+
e
|
|
172
|
+
)}`
|
|
173
|
+
);
|
|
164
174
|
return 'Not found';
|
|
165
175
|
}
|
|
166
176
|
}
|
|
@@ -173,7 +183,7 @@ export async function getNpmVersion() {
|
|
|
173
183
|
args: ['--version']
|
|
174
184
|
});
|
|
175
185
|
} catch (e) {
|
|
176
|
-
logger.warn(`Cannot get npm version
|
|
186
|
+
logger.warn(`Cannot get npm version! Error:\n${stringifyError(e)}`);
|
|
177
187
|
return 'Not found';
|
|
178
188
|
}
|
|
179
189
|
}
|
|
@@ -186,7 +196,7 @@ export async function getNodeVersion() {
|
|
|
186
196
|
args: ['-v']
|
|
187
197
|
});
|
|
188
198
|
} catch (e) {
|
|
189
|
-
logger.warn(`Cannot get Node version
|
|
199
|
+
logger.warn(`Cannot get Node version! Error:\n${stringifyError(e)}`);
|
|
190
200
|
return 'Not found';
|
|
191
201
|
}
|
|
192
202
|
}
|
|
@@ -230,7 +240,7 @@ export async function getLastBootTime() {
|
|
|
230
240
|
|
|
231
241
|
return String(new Date(`${tokens[2]} ${tokens[3]} ${tokens[4]}`));
|
|
232
242
|
} catch (e) {
|
|
233
|
-
logger.error(`Issue getting last boot time
|
|
243
|
+
logger.error(`Issue getting last boot time! Error:\n${stringifyError(e)}`);
|
|
234
244
|
return undefined;
|
|
235
245
|
}
|
|
236
246
|
}
|
|
@@ -19,7 +19,9 @@ export function importDockerCompose():
|
|
|
19
19
|
execSync('docker-compose -v').toString();
|
|
20
20
|
logger.warn('Using docker-compose V1. Please consider updating to V2.');
|
|
21
21
|
} catch (e) {
|
|
22
|
-
logger.warn(
|
|
22
|
+
logger.warn(
|
|
23
|
+
`Could not determine compose version! Error:\n${stringifyError(e)}`
|
|
24
|
+
);
|
|
23
25
|
}
|
|
24
26
|
}
|
|
25
27
|
dockerCmd = 'docker-compose';
|
package/src/environment.ts
CHANGED
|
@@ -24,8 +24,7 @@ export const ALWAYSAI_LOCAL_CONNECTION_PORT =
|
|
|
24
24
|
process.env.ALWAYSAI_LOCAL_CONNECTION_PORT;
|
|
25
25
|
export const ALWAYSAI_LOCAL_CONNECTION_ANALYTICS_ROUTING_KEY =
|
|
26
26
|
process.env.ALWAYSAI_LOCAL_CONNECTION_ANALYTICS_ROUTING_KEY;
|
|
27
|
-
export const ALWAYSAI_TARGET_HW_OVERRIDE =
|
|
28
|
-
process.env.ALWAYSAI_TARGET_HW_OVERRIDE;
|
|
27
|
+
export const ALWAYSAI_TARGET_HW_OVERRIDE = process.env.ALWAYSAI_TARGET_HW;
|
|
29
28
|
|
|
30
29
|
export function parseTargetHW(
|
|
31
30
|
hardwareType?: string
|
package/src/index.ts
CHANGED
|
@@ -11,11 +11,30 @@ import { runDeviceAgentCloudInterface } from './cloud-connection/device-agent-cl
|
|
|
11
11
|
import { AgentConfigFile } from './infrastructure/agent-config';
|
|
12
12
|
import { ALWAYSAI_DEVICE_AGENT_MODE } from './environment';
|
|
13
13
|
import { checkForUpdatesAndPrompt } from './util/check-for-updates';
|
|
14
|
+
import { logger } from './util/logger';
|
|
15
|
+
import { getSystemId } from './infrastructure/system-id';
|
|
16
|
+
import { stringifyError } from 'alwaysai/lib/util';
|
|
14
17
|
|
|
15
18
|
if (module === require.main) {
|
|
16
19
|
if (!AgentConfigFile().exists()) {
|
|
17
20
|
AgentConfigFile().initialize();
|
|
18
21
|
}
|
|
22
|
+
logger.debug(`Starting Device Agent with system ID: ${getSystemId()}`);
|
|
23
|
+
|
|
24
|
+
// Catch any unhandled errors to prevent Device Agent crash
|
|
25
|
+
process.on('unhandledRejection', (reason: Error, p: Promise<any>) => {
|
|
26
|
+
logger.error(
|
|
27
|
+
`Unhandled Rejection at: ${JSON.stringify(p)} reason: ${stringifyError(
|
|
28
|
+
reason
|
|
29
|
+
)}`
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
process.on('uncaughtException', (error: Error) => {
|
|
33
|
+
logger.error(
|
|
34
|
+
`Caught exception: ${stringifyError(error)}\n` +
|
|
35
|
+
`Exception origin: ${error.stack}`
|
|
36
|
+
);
|
|
37
|
+
});
|
|
19
38
|
|
|
20
39
|
if (ALWAYSAI_DEVICE_AGENT_MODE === 'cloud') {
|
|
21
40
|
void runDeviceAgentCloudInterface();
|
|
@@ -14,7 +14,7 @@ export const validConfigMessage = 'Device config is present and valid.';
|
|
|
14
14
|
export async function checkDeviceConfigPresent(
|
|
15
15
|
baseDir?: string
|
|
16
16
|
): Promise<boolean> {
|
|
17
|
-
logger.debug('Checking for required device
|
|
17
|
+
logger.debug('Checking for required device configuration file.');
|
|
18
18
|
const deviceConfig = DeviceConfigFile(
|
|
19
19
|
baseDir
|
|
20
20
|
? join(baseDir, getDeviceConfigPath(), DEVICE_CONFIG_FILE_NAME)
|
|
@@ -48,7 +48,7 @@ export async function checkCertificatesPresent(baseDir?: string) {
|
|
|
48
48
|
export async function checkAaiConfigPresent(
|
|
49
49
|
baseDir?: string
|
|
50
50
|
): Promise<boolean> {
|
|
51
|
-
logger.debug('Checking for required
|
|
51
|
+
logger.debug('Checking for required alwaysAI configuration file.');
|
|
52
52
|
const localAaiCfg = new LocalAaiCfg(baseDir);
|
|
53
53
|
|
|
54
54
|
try {
|
|
@@ -17,7 +17,7 @@ import rimraf from 'rimraf';
|
|
|
17
17
|
import {
|
|
18
18
|
checkRabbitMQContainerRunning,
|
|
19
19
|
stopRabbitMQContainer
|
|
20
|
-
} from '../../local-connection/rabbitmq-
|
|
20
|
+
} from '../../local-connection/rabbitmq-container';
|
|
21
21
|
import { copyDir } from '../../util/copy-dir';
|
|
22
22
|
import {
|
|
23
23
|
getDeviceAgentConfigPath,
|
|
@@ -54,7 +54,7 @@ export async function migrateAgentConfigFile(baseDir?: string) {
|
|
|
54
54
|
try {
|
|
55
55
|
parsedAgentConfig = legacyAgentConfigFile.read();
|
|
56
56
|
} catch (e) {
|
|
57
|
-
logger.error(`Error reading agent config
|
|
57
|
+
logger.error(`Error reading agent config! Error:\n${stringifyError(e)}`);
|
|
58
58
|
logger.error(
|
|
59
59
|
`Agent Config Errors: ${JSON.stringify(
|
|
60
60
|
legacyAgentConfigFile.getErrors(),
|
|
@@ -73,7 +73,7 @@ export async function migrateAgentConfigFile(baseDir?: string) {
|
|
|
73
73
|
// we can remove it after migrating it
|
|
74
74
|
await rimraf(legacyAgentConfigFile.path);
|
|
75
75
|
} catch (e) {
|
|
76
|
-
logger.error(`Error writing agent config
|
|
76
|
+
logger.error(`Error writing agent config! Error:\n${stringifyError(e)}`);
|
|
77
77
|
logger.error(
|
|
78
78
|
`Agent Config Errors: ${JSON.stringify(
|
|
79
79
|
newAgentConfig.getErrors(),
|
|
@@ -199,17 +199,12 @@ export async function migrateFromLegacyCertsAndTokens(
|
|
|
199
199
|
await migrateAgentConfigFile(baseDir);
|
|
200
200
|
|
|
201
201
|
// if rabbitmq container is present, stop container and migrate files
|
|
202
|
-
|
|
203
|
-
if (await
|
|
204
|
-
|
|
202
|
+
if (await checkRabbitMQContainerRunning()) {
|
|
203
|
+
if ((await stopRabbitMQContainer()) === false) {
|
|
204
|
+
logger.error(
|
|
205
|
+
`You may need to manually stop the container by running docker-compose down in the following directory: ${getDeviceAgentDockerComposePath()}`
|
|
206
|
+
);
|
|
205
207
|
}
|
|
206
|
-
} catch (e) {
|
|
207
|
-
logger.error(
|
|
208
|
-
`You may need to manually stop the container by running docker-compose down in the following directory: ${getDeviceAgentDockerComposePath()}`
|
|
209
|
-
);
|
|
210
|
-
logger.debug(
|
|
211
|
-
`Error in checking / stopping RabbitMQ container!\n${stringifyError(e)}`
|
|
212
|
-
);
|
|
213
208
|
}
|
|
214
209
|
if (await exists(getDeviceAgentConfigPath(baseDir))) {
|
|
215
210
|
await copyDir({
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import {
|
|
2
|
+
checkRabbitMQContainerRunning,
|
|
3
|
+
runRabbitMQContainer,
|
|
4
|
+
stopRabbitMQContainer
|
|
5
|
+
} from './rabbitmq-container';
|
|
6
|
+
|
|
7
|
+
const mockJsSpawner = {
|
|
8
|
+
exists: jest.fn(),
|
|
9
|
+
mkdirp: jest.fn(),
|
|
10
|
+
writeFile: jest.fn()
|
|
11
|
+
};
|
|
12
|
+
jest.mock('alwaysai/lib/util/spawner', () => {
|
|
13
|
+
return {
|
|
14
|
+
JsSpawner: jest.fn(() => mockJsSpawner)
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
jest.mock('../docker/docker-compose', () => {
|
|
19
|
+
return {
|
|
20
|
+
compose: {
|
|
21
|
+
ps: jest.fn(),
|
|
22
|
+
upAll: jest.fn(),
|
|
23
|
+
down: jest.fn()
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
import { compose } from '../docker/docker-compose';
|
|
28
|
+
|
|
29
|
+
describe('Test check RabbitMQ container running', () => {
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
jest.clearAllMocks();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('Compose file not found', async () => {
|
|
35
|
+
mockJsSpawner.exists.mockResolvedValue(false);
|
|
36
|
+
|
|
37
|
+
const running = await checkRabbitMQContainerRunning();
|
|
38
|
+
|
|
39
|
+
expect(running).toBeFalsy();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('Container not found', async () => {
|
|
43
|
+
mockJsSpawner.exists.mockResolvedValue(true);
|
|
44
|
+
jest.mocked(compose.ps).mockResolvedValue({
|
|
45
|
+
exitCode: 0,
|
|
46
|
+
out: '',
|
|
47
|
+
err: '',
|
|
48
|
+
data: {
|
|
49
|
+
services: []
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const running = await checkRabbitMQContainerRunning();
|
|
54
|
+
|
|
55
|
+
expect(running).toBeFalsy();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('Container is not up', async () => {
|
|
59
|
+
mockJsSpawner.exists.mockResolvedValue(true);
|
|
60
|
+
jest.mocked(compose.ps).mockResolvedValue({
|
|
61
|
+
exitCode: 0,
|
|
62
|
+
out: '',
|
|
63
|
+
err: '',
|
|
64
|
+
data: {
|
|
65
|
+
services: [
|
|
66
|
+
{
|
|
67
|
+
name: 'alwaysAIRabbitMQContainer',
|
|
68
|
+
command: '',
|
|
69
|
+
state: 'Down',
|
|
70
|
+
ports: []
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const running = await checkRabbitMQContainerRunning();
|
|
77
|
+
|
|
78
|
+
expect(running).toBeFalsy();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('Container is up', async () => {
|
|
82
|
+
mockJsSpawner.exists.mockResolvedValue(true);
|
|
83
|
+
jest.mocked(compose.ps).mockResolvedValue({
|
|
84
|
+
exitCode: 0,
|
|
85
|
+
out: '',
|
|
86
|
+
err: '',
|
|
87
|
+
data: {
|
|
88
|
+
services: [
|
|
89
|
+
{
|
|
90
|
+
name: 'alwaysAIRabbitMQContainer',
|
|
91
|
+
command: '',
|
|
92
|
+
state: 'Up',
|
|
93
|
+
ports: []
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const running = await checkRabbitMQContainerRunning();
|
|
100
|
+
|
|
101
|
+
expect(running).toBeTruthy();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('Test run RabbitMQ container', () => {
|
|
106
|
+
afterEach(() => {
|
|
107
|
+
jest.clearAllMocks();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('Compose file write fails', async () => {
|
|
111
|
+
mockJsSpawner.exists.mockResolvedValueOnce(false);
|
|
112
|
+
mockJsSpawner.mkdirp.mockImplementation(async () => {
|
|
113
|
+
return;
|
|
114
|
+
});
|
|
115
|
+
mockJsSpawner.writeFile.mockImplementation(async () => {
|
|
116
|
+
throw new Error('Failed to write file!');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const running = await runRabbitMQContainer();
|
|
120
|
+
|
|
121
|
+
expect(running).toBeFalsy();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('Compose up fails', async () => {
|
|
125
|
+
mockJsSpawner.exists.mockResolvedValueOnce(true);
|
|
126
|
+
mockJsSpawner.writeFile.mockImplementation(async () => {
|
|
127
|
+
return;
|
|
128
|
+
});
|
|
129
|
+
jest.mocked(compose.upAll).mockResolvedValue({
|
|
130
|
+
exitCode: 1,
|
|
131
|
+
out: '',
|
|
132
|
+
err: ''
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const running = await runRabbitMQContainer();
|
|
136
|
+
|
|
137
|
+
expect(running).toBeFalsy();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('Container fails to come up', async () => {
|
|
141
|
+
// exists(deviceAgentConfigPath)
|
|
142
|
+
mockJsSpawner.exists.mockResolvedValueOnce(true);
|
|
143
|
+
mockJsSpawner.writeFile.mockImplementation(async () => {
|
|
144
|
+
return;
|
|
145
|
+
});
|
|
146
|
+
jest.mocked(compose.upAll).mockResolvedValue({
|
|
147
|
+
exitCode: 0,
|
|
148
|
+
out: '',
|
|
149
|
+
err: ''
|
|
150
|
+
});
|
|
151
|
+
// exists(deviceAgentDockerComposePath)
|
|
152
|
+
mockJsSpawner.exists.mockResolvedValueOnce(true);
|
|
153
|
+
jest.mocked(compose.ps).mockResolvedValue({
|
|
154
|
+
exitCode: 0,
|
|
155
|
+
out: '',
|
|
156
|
+
err: '',
|
|
157
|
+
data: {
|
|
158
|
+
services: [
|
|
159
|
+
{
|
|
160
|
+
name: 'alwaysAIRabbitMQContainer',
|
|
161
|
+
command: '',
|
|
162
|
+
state: 'Down',
|
|
163
|
+
ports: []
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const running = await runRabbitMQContainer({ max_attempts: 2 });
|
|
170
|
+
|
|
171
|
+
expect(jest.mocked(compose.ps)).toBeCalledTimes(2);
|
|
172
|
+
expect(running).toBeFalsy();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('Container comes up successfully', async () => {
|
|
176
|
+
// exists(deviceAgentConfigPath)
|
|
177
|
+
mockJsSpawner.exists.mockResolvedValueOnce(true);
|
|
178
|
+
mockJsSpawner.writeFile.mockImplementation(async () => {
|
|
179
|
+
return;
|
|
180
|
+
});
|
|
181
|
+
jest.mocked(compose.upAll).mockResolvedValue({
|
|
182
|
+
exitCode: 0,
|
|
183
|
+
out: '',
|
|
184
|
+
err: ''
|
|
185
|
+
});
|
|
186
|
+
// exists(deviceAgentDockerComposePath)
|
|
187
|
+
mockJsSpawner.exists.mockResolvedValueOnce(true);
|
|
188
|
+
// Check status twice, one with container still down and next up
|
|
189
|
+
jest.mocked(compose.ps).mockResolvedValueOnce({
|
|
190
|
+
exitCode: 0,
|
|
191
|
+
out: '',
|
|
192
|
+
err: '',
|
|
193
|
+
data: {
|
|
194
|
+
services: [
|
|
195
|
+
{
|
|
196
|
+
name: 'alwaysAIRabbitMQContainer',
|
|
197
|
+
command: '',
|
|
198
|
+
state: 'Down',
|
|
199
|
+
ports: []
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
jest.mocked(compose.ps).mockResolvedValueOnce({
|
|
205
|
+
exitCode: 0,
|
|
206
|
+
out: '',
|
|
207
|
+
err: '',
|
|
208
|
+
data: {
|
|
209
|
+
services: [
|
|
210
|
+
{
|
|
211
|
+
name: 'alwaysAIRabbitMQContainer',
|
|
212
|
+
command: '',
|
|
213
|
+
state: 'Up',
|
|
214
|
+
ports: []
|
|
215
|
+
}
|
|
216
|
+
]
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const running = await runRabbitMQContainer({ max_attempts: 2 });
|
|
221
|
+
|
|
222
|
+
expect(jest.mocked(compose.ps)).toBeCalledTimes(2);
|
|
223
|
+
expect(running).toBeTruthy();
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('Test stopping RabbitMQ container', () => {
|
|
228
|
+
afterEach(() => {
|
|
229
|
+
jest.clearAllMocks();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('Compose down fails', async () => {
|
|
233
|
+
jest.mocked(compose.down).mockResolvedValue({
|
|
234
|
+
exitCode: 1,
|
|
235
|
+
out: '',
|
|
236
|
+
err: ''
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const stopped = await stopRabbitMQContainer();
|
|
240
|
+
|
|
241
|
+
expect(stopped).toBeFalsy();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test('Stop succeeds', async () => {
|
|
245
|
+
jest.mocked(compose.down).mockResolvedValue({
|
|
246
|
+
exitCode: 0,
|
|
247
|
+
out: '',
|
|
248
|
+
err: ''
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const stopped = await stopRabbitMQContainer();
|
|
252
|
+
|
|
253
|
+
expect(stopped).toBeTruthy();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { JsSpawner, stringifyError } from 'alwaysai/lib/util';
|
|
2
|
+
import * as YAML from 'yaml';
|
|
3
|
+
import { compose } from '../docker/docker-compose';
|
|
4
|
+
import {
|
|
5
|
+
getDeviceAgentConfigPath,
|
|
6
|
+
getDeviceAgentDockerComposePath
|
|
7
|
+
} from '../util/directories';
|
|
8
|
+
import { logger } from '../util/logger';
|
|
9
|
+
import sleep from '../util/sleep';
|
|
10
|
+
import { LOCAL_CONNECTION_PORT } from './constants';
|
|
11
|
+
|
|
12
|
+
const rabbitMQServiceName = 'alwaysAIRabbitMQ';
|
|
13
|
+
const rabbitMQContainerName = 'alwaysAIRabbitMQContainer';
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
Utility functions to control the Local Connection container (RabbitMQ).
|
|
17
|
+
|
|
18
|
+
All exported functions handle their own errors and log their status, returning a
|
|
19
|
+
boolean indicating whether the action was successful.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export async function checkRabbitMQContainerRunning(): Promise<boolean> {
|
|
23
|
+
logger.debug('Checking Local Connection Container status');
|
|
24
|
+
const spawner = JsSpawner();
|
|
25
|
+
if (!(await spawner.exists(getDeviceAgentDockerComposePath()))) {
|
|
26
|
+
logger.warn('Local Connection configuration file is not present');
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const containerData = await compose.ps({ cwd: getDeviceAgentConfigPath() });
|
|
30
|
+
for (const service of containerData.data.services) {
|
|
31
|
+
if (service?.name === rabbitMQContainerName) {
|
|
32
|
+
if (service.state.includes('Up')) {
|
|
33
|
+
logger.debug('Local Connection container is up.');
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
logger.debug(
|
|
37
|
+
`Local Connection container is not up. Status: ${service.state}`
|
|
38
|
+
);
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
logger.warn(
|
|
43
|
+
`Local Connection container not found in container data: ${JSON.stringify(
|
|
44
|
+
containerData,
|
|
45
|
+
null,
|
|
46
|
+
2
|
|
47
|
+
)}`
|
|
48
|
+
);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function writeRabbitMQDockerComposeFile() {
|
|
53
|
+
const spawner = JsSpawner();
|
|
54
|
+
if (!(await spawner.exists(getDeviceAgentConfigPath()))) {
|
|
55
|
+
await JsSpawner().mkdirp(getDeviceAgentConfigPath());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
logger.debug(
|
|
59
|
+
`Writing Local Connection Docker compose files with Local Connection port: ${LOCAL_CONNECTION_PORT}`
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const rabbitmqDockerComposeCmd = {
|
|
63
|
+
services: {
|
|
64
|
+
[rabbitMQServiceName]: {
|
|
65
|
+
container_name: rabbitMQContainerName,
|
|
66
|
+
image: 'rabbitmq:3.11',
|
|
67
|
+
ports: [`${LOCAL_CONNECTION_PORT}:${LOCAL_CONNECTION_PORT}`],
|
|
68
|
+
hostname: 'my-rabbit',
|
|
69
|
+
restart: 'on-failure',
|
|
70
|
+
environment: [`RABBITMQ_NODE_PORT=${LOCAL_CONNECTION_PORT}`],
|
|
71
|
+
volumes: [
|
|
72
|
+
'./rabbitmq/:/var/lib/rabbitmq/',
|
|
73
|
+
'./rabbitmq/log/:/var/log/rabbitmq/'
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const RabbitMQDockerComposeYaml = YAML.stringify(rabbitmqDockerComposeCmd);
|
|
80
|
+
await spawner.writeFile(
|
|
81
|
+
getDeviceAgentDockerComposePath(),
|
|
82
|
+
RabbitMQDockerComposeYaml
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function runRabbitMQContainer(opts?: {
|
|
87
|
+
max_attempts: number;
|
|
88
|
+
}): Promise<boolean> {
|
|
89
|
+
const max_attempts = opts?.max_attempts || 10;
|
|
90
|
+
logger.debug('Starting the Local Connection container');
|
|
91
|
+
try {
|
|
92
|
+
await writeRabbitMQDockerComposeFile();
|
|
93
|
+
const upOut = await compose.upAll({
|
|
94
|
+
cwd: getDeviceAgentConfigPath()
|
|
95
|
+
});
|
|
96
|
+
logger.debug(
|
|
97
|
+
`Docker compose up for Local Connection:\n${JSON.stringify(
|
|
98
|
+
upOut,
|
|
99
|
+
null,
|
|
100
|
+
2
|
|
101
|
+
)}`
|
|
102
|
+
);
|
|
103
|
+
if (upOut.exitCode !== 0) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Failed to start Local Connection! stdout=${upOut.out} stderr=${upOut.err}`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
// check if the container is up
|
|
109
|
+
let attempts = 0;
|
|
110
|
+
while (!(await checkRabbitMQContainerRunning())) {
|
|
111
|
+
attempts++;
|
|
112
|
+
if (attempts >= max_attempts) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
'Local Connection container failed to come up after start!'
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
await sleep(1000);
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
} catch (e) {
|
|
121
|
+
logger.error(
|
|
122
|
+
`Unable to start Local Connection container!\n${stringifyError(e)}`
|
|
123
|
+
);
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function stopRabbitMQContainer(): Promise<boolean> {
|
|
129
|
+
logger.debug(`Stopping alwaysAI Local Connection container`);
|
|
130
|
+
try {
|
|
131
|
+
const downOut = await compose.down({ cwd: getDeviceAgentConfigPath() });
|
|
132
|
+
logger.debug(
|
|
133
|
+
`Local Connection docker-compose down: ${JSON.stringify(
|
|
134
|
+
downOut,
|
|
135
|
+
null,
|
|
136
|
+
2
|
|
137
|
+
)}`
|
|
138
|
+
);
|
|
139
|
+
if (downOut.exitCode !== 0) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Failed to stop Local Connection! stdout=${downOut.out} stderr=${downOut.err}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
} catch (e) {
|
|
146
|
+
logger.error(
|
|
147
|
+
`Unable to stop Local Connection Container:\n${stringifyError(e)}`
|
|
148
|
+
);
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -11,7 +11,7 @@ import { AgentConfigFile } from '../../infrastructure/agent-config';
|
|
|
11
11
|
import {
|
|
12
12
|
checkRabbitMQContainerRunning,
|
|
13
13
|
stopRabbitMQContainer
|
|
14
|
-
} from '../../local-connection/rabbitmq-
|
|
14
|
+
} from '../../local-connection/rabbitmq-container';
|
|
15
15
|
import {
|
|
16
16
|
APP_ROOT,
|
|
17
17
|
CREDENTIALS_FILE_PATH,
|
|
@@ -20,28 +20,24 @@ import {
|
|
|
20
20
|
} from '../../util/directories';
|
|
21
21
|
import { safeRimraf } from '../../util/file';
|
|
22
22
|
import { logger } from '../../util/logger';
|
|
23
|
+
import { existsSync } from 'fs';
|
|
23
24
|
|
|
24
25
|
export const cleanCliLeaf = CliLeaf({
|
|
25
26
|
name: 'clean',
|
|
26
27
|
description: 'Remove all provisioning files',
|
|
27
28
|
async action(_, opts) {
|
|
28
29
|
logger.info('Removing provisioning files.');
|
|
29
|
-
|
|
30
|
-
if (await
|
|
31
|
-
|
|
30
|
+
if (await checkRabbitMQContainerRunning()) {
|
|
31
|
+
if ((await stopRabbitMQContainer()) === false) {
|
|
32
|
+
logger.error(
|
|
33
|
+
`You may need to manually stop the container by running docker-compose down in the following directory: ${getDeviceAgentDockerComposePath()}`
|
|
34
|
+
);
|
|
32
35
|
}
|
|
33
|
-
} catch (e) {
|
|
34
|
-
logger.error(
|
|
35
|
-
`You may need to manually stop the container by running docker-compose down in the following directory: ${getDeviceAgentDockerComposePath()}`
|
|
36
|
-
);
|
|
37
|
-
logger.debug(
|
|
38
|
-
`Error in checking / stopping RabbitMQ container!\n${stringifyError(e)}`
|
|
39
|
-
);
|
|
40
36
|
}
|
|
41
37
|
|
|
42
38
|
await safeRimraf(getDeviceAgentConfigPath());
|
|
43
39
|
|
|
44
|
-
logger.debug('Checking for alwaysAI applications still
|
|
40
|
+
logger.debug('Checking for alwaysAI applications still installed');
|
|
45
41
|
const apps = await AgentConfigFile().getApps();
|
|
46
42
|
const uninstallAppPromises: Promise<void>[] = [];
|
|
47
43
|
for (const app of apps) {
|
|
@@ -60,13 +56,18 @@ export const cleanCliLeaf = CliLeaf({
|
|
|
60
56
|
DeviceConfigFile().remove();
|
|
61
57
|
|
|
62
58
|
await safeRimraf(CREDENTIALS_FILE_PATH);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
|
|
60
|
+
if (existsSync(APP_ROOT)) {
|
|
61
|
+
const files = await readdir(APP_ROOT);
|
|
62
|
+
logger.debug(
|
|
63
|
+
`${APP_ROOT} folder contents ${JSON.stringify(files.length)}`
|
|
64
|
+
);
|
|
65
|
+
// This won't handle hidden files, but will remove if truly empty, which is safer
|
|
66
|
+
if (files.length === 0) {
|
|
67
|
+
logger.debug('Applications directory is empty, removing.');
|
|
68
|
+
await safeRimraf(APP_ROOT);
|
|
69
|
+
}
|
|
70
|
+
logger.info('Device configuration cleaned');
|
|
69
71
|
}
|
|
70
|
-
logger.info('Device configuration cleaned');
|
|
71
72
|
}
|
|
72
73
|
});
|
|
@@ -5,6 +5,7 @@ import { initCliLeaf } from './init';
|
|
|
5
5
|
import { restartCliLeaf } from './restart';
|
|
6
6
|
import { refreshCliLeaf } from './refresh';
|
|
7
7
|
import { migrateCliLeaf } from './migrate';
|
|
8
|
+
import { stopLocalConnectionCliLeaf } from './local-connection';
|
|
8
9
|
|
|
9
10
|
export const deviceCliBranch = CliBranch({
|
|
10
11
|
name: 'device',
|
|
@@ -15,6 +16,7 @@ export const deviceCliBranch = CliBranch({
|
|
|
15
16
|
refreshCliLeaf,
|
|
16
17
|
cleanCliLeaf,
|
|
17
18
|
restartCliLeaf,
|
|
18
|
-
migrateCliLeaf
|
|
19
|
+
migrateCliLeaf,
|
|
20
|
+
stopLocalConnectionCliLeaf
|
|
19
21
|
]
|
|
20
22
|
});
|