@alwaysai/device-agent 1.4.0 → 2.0.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.d.ts.map +1 -1
- package/lib/application-control/config.js +10 -5
- package/lib/application-control/config.js.map +1 -1
- package/lib/application-control/environment-variables.d.ts +1 -5
- package/lib/application-control/environment-variables.d.ts.map +1 -1
- package/lib/application-control/environment-variables.js +9 -26
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/environment-variables.test.js +27 -7
- package/lib/application-control/environment-variables.test.js.map +1 -1
- package/lib/application-control/index.d.ts +4 -4
- package/lib/application-control/index.d.ts.map +1 -1
- package/lib/application-control/index.js +1 -4
- package/lib/application-control/index.js.map +1 -1
- package/lib/application-control/install.d.ts.map +1 -1
- package/lib/application-control/install.js +9 -7
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/models.d.ts +5 -11
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +27 -64
- 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.d.ts +0 -4
- package/lib/application-control/utils.d.ts.map +1 -1
- package/lib/application-control/utils.js +3 -26
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/bootstrap-provision.js +3 -2
- package/lib/cloud-connection/bootstrap-provision.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +11 -16
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +295 -246
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/device-agent.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent.js +11 -9
- package/lib/cloud-connection/device-agent.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts +18 -27
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +58 -170
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.test.js +76 -54
- package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
- package/lib/cloud-connection/passthrough-handler.d.ts +9 -4
- package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -1
- package/lib/cloud-connection/passthrough-handler.js +95 -62
- package/lib/cloud-connection/passthrough-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.d.ts +5 -1
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +63 -31
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.test.js +45 -57
- package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
- package/lib/cloud-connection/shadow.d.ts.map +1 -1
- package/lib/cloud-connection/shadow.js +2 -1
- package/lib/cloud-connection/shadow.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.d.ts +7 -2
- package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
- package/lib/cloud-connection/transaction-manager.js +29 -29
- package/lib/cloud-connection/transaction-manager.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.test.js +105 -3
- package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
- package/lib/device-control/device-control.d.ts +14 -6
- package/lib/device-control/device-control.d.ts.map +1 -1
- package/lib/device-control/device-control.js +172 -72
- 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 +57 -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 +46 -14
- package/lib/infrastructure/agent-config.d.ts.map +1 -1
- package/lib/infrastructure/agent-config.js +36 -21
- package/lib/infrastructure/agent-config.js.map +1 -1
- package/lib/infrastructure/agent-config.test.js +6 -1
- package/lib/infrastructure/agent-config.test.js.map +1 -1
- package/lib/infrastructure/config-check-utility.d.ts +6 -0
- package/lib/infrastructure/config-check-utility.d.ts.map +1 -0
- package/lib/infrastructure/config-check-utility.js +67 -0
- package/lib/infrastructure/config-check-utility.js.map +1 -0
- package/lib/infrastructure/config-check-utility.test.d.ts +2 -0
- package/lib/infrastructure/config-check-utility.test.d.ts.map +1 -0
- package/lib/infrastructure/config-check-utility.test.js +109 -0
- package/lib/infrastructure/config-check-utility.test.js.map +1 -0
- package/lib/infrastructure/device-certificate.d.ts +10 -0
- package/lib/infrastructure/device-certificate.d.ts.map +1 -0
- package/lib/infrastructure/device-certificate.js +47 -0
- package/lib/infrastructure/device-certificate.js.map +1 -0
- package/lib/infrastructure/device-certificate.test.d.ts +2 -0
- package/lib/infrastructure/device-certificate.test.d.ts.map +1 -0
- package/lib/infrastructure/device-certificate.test.js +24 -0
- package/lib/infrastructure/device-certificate.test.js.map +1 -0
- package/lib/infrastructure/legacy-migration/legacy-file.test.d.ts +2 -0
- package/lib/infrastructure/legacy-migration/legacy-file.test.d.ts.map +1 -0
- package/lib/infrastructure/legacy-migration/legacy-file.test.js +61 -0
- package/lib/infrastructure/legacy-migration/legacy-file.test.js.map +1 -0
- package/lib/infrastructure/legacy-migration/legacy-files.d.ts +75 -0
- package/lib/infrastructure/legacy-migration/legacy-files.d.ts.map +1 -0
- package/lib/infrastructure/legacy-migration/legacy-files.js +75 -0
- package/lib/infrastructure/legacy-migration/legacy-files.js.map +1 -0
- package/lib/infrastructure/legacy-migration/legacy-migration.d.ts +6 -0
- package/lib/infrastructure/legacy-migration/legacy-migration.d.ts.map +1 -0
- package/lib/infrastructure/legacy-migration/legacy-migration.js +149 -0
- package/lib/infrastructure/legacy-migration/legacy-migration.js.map +1 -0
- package/lib/infrastructure/legacy-migration/legacy-migration.test.d.ts +2 -0
- package/lib/infrastructure/legacy-migration/legacy-migration.test.d.ts.map +1 -0
- package/lib/infrastructure/legacy-migration/legacy-migration.test.js +226 -0
- package/lib/infrastructure/legacy-migration/legacy-migration.test.js.map +1 -0
- package/lib/infrastructure/require-files-present-ready.test.d.ts +2 -0
- package/lib/infrastructure/require-files-present-ready.test.d.ts.map +1 -0
- package/lib/infrastructure/require-files-present-ready.test.js +44 -0
- package/lib/infrastructure/require-files-present-ready.test.js.map +1 -0
- package/lib/infrastructure/required-config-checks.d.ts +2 -0
- package/lib/infrastructure/required-config-checks.d.ts.map +1 -0
- package/lib/infrastructure/required-config-checks.js +30 -0
- package/lib/infrastructure/required-config-checks.js.map +1 -0
- package/lib/infrastructure/tokens-and-device-cfg.d.ts.map +1 -1
- package/lib/infrastructure/tokens-and-device-cfg.js +11 -8
- package/lib/infrastructure/tokens-and-device-cfg.js.map +1 -1
- package/lib/local-connection/rabbitmq-connection.d.ts.map +1 -1
- package/lib/local-connection/rabbitmq-connection.js +21 -21
- package/lib/local-connection/rabbitmq-connection.js.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.d.ts +15 -23
- package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.js +52 -47
- package/lib/secure-tunneling/secure-tunneling.js.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.test.js +29 -31
- package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -1
- package/lib/subcommands/app/analytics.d.ts.map +1 -1
- package/lib/subcommands/app/analytics.js +1 -2
- package/lib/subcommands/app/analytics.js.map +1 -1
- package/lib/subcommands/app/env-vars.d.ts +4 -0
- package/lib/subcommands/app/env-vars.d.ts.map +1 -1
- package/lib/subcommands/app/env-vars.js +52 -6
- package/lib/subcommands/app/env-vars.js.map +1 -1
- package/lib/subcommands/app/index.d.ts.map +1 -1
- package/lib/subcommands/app/index.js +1 -3
- package/lib/subcommands/app/index.js.map +1 -1
- package/lib/subcommands/app/models.d.ts +0 -11
- package/lib/subcommands/app/models.d.ts.map +1 -1
- package/lib/subcommands/app/models.js +2 -58
- package/lib/subcommands/app/models.js.map +1 -1
- package/lib/subcommands/app/shadow.d.ts.map +1 -1
- package/lib/subcommands/app/shadow.js +6 -5
- package/lib/subcommands/app/shadow.js.map +1 -1
- package/lib/subcommands/app/version.d.ts.map +1 -1
- package/lib/subcommands/app/version.js +2 -4
- package/lib/subcommands/app/version.js.map +1 -1
- package/lib/subcommands/config.d.ts +2 -0
- package/lib/subcommands/config.d.ts.map +1 -0
- package/lib/subcommands/config.js +39 -0
- package/lib/subcommands/config.js.map +1 -0
- package/lib/subcommands/device/clean.d.ts +1 -1
- package/lib/subcommands/device/clean.d.ts.map +1 -1
- package/lib/subcommands/device/clean.js +25 -15
- 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 +13 -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} +10 -49
- package/lib/subcommands/device/init.js.map +1 -0
- package/lib/subcommands/device/migrate.d.ts +2 -0
- package/lib/subcommands/device/migrate.d.ts.map +1 -0
- package/lib/subcommands/device/migrate.js +24 -0
- package/lib/subcommands/device/migrate.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 +25 -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/subcommands/index.d.ts +1 -1
- package/lib/subcommands/index.d.ts.map +1 -1
- package/lib/subcommands/index.js +3 -1
- package/lib/subcommands/index.js.map +1 -1
- package/lib/subcommands/rabbitmq-connection.d.ts +1 -1
- package/lib/subcommands/rabbitmq-connection.d.ts.map +1 -1
- package/lib/util/aai-error.d.ts +12 -0
- package/lib/util/aai-error.d.ts.map +1 -0
- package/lib/util/aai-error.js +11 -0
- package/lib/util/aai-error.js.map +1 -0
- package/lib/util/aws-regions.d.ts +2 -0
- package/lib/util/aws-regions.d.ts.map +1 -0
- package/lib/util/{cloud-mode-ready.js → aws-regions.js} +2 -20
- package/lib/util/aws-regions.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 +46 -0
- package/lib/util/check-for-updates.js.map +1 -0
- package/lib/util/clean-certs.d.ts.map +1 -1
- package/lib/util/clean-certs.js +5 -4
- package/lib/util/clean-certs.js.map +1 -1
- package/lib/util/directories.d.ts +4 -18
- package/lib/util/directories.d.ts.map +1 -1
- package/lib/util/directories.js +18 -32
- package/lib/util/directories.js.map +1 -1
- package/lib/util/file.d.ts +11 -0
- package/lib/util/file.d.ts.map +1 -0
- package/lib/util/file.js +127 -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/lib/util/get-device-id.d.ts.map +1 -1
- package/lib/util/get-device-id.js +7 -1
- package/lib/util/get-device-id.js.map +1 -1
- package/lib/util/http-client.js +3 -3
- package/lib/util/http-client.js.map +1 -1
- package/package.json +22 -19
- package/readme.md +15 -35
- package/src/application-control/config.ts +10 -13
- package/src/application-control/environment-variables.test.ts +28 -7
- package/src/application-control/environment-variables.ts +13 -40
- package/src/application-control/index.ts +3 -16
- package/src/application-control/install.ts +16 -10
- package/src/application-control/models.ts +40 -98
- package/src/application-control/status.ts +9 -7
- package/src/application-control/utils.ts +1 -29
- package/src/cloud-connection/bootstrap-provision.ts +7 -7
- package/src/cloud-connection/device-agent-cloud-connection.ts +647 -509
- package/src/cloud-connection/device-agent.ts +16 -7
- package/src/cloud-connection/live-updates-handler.test.ts +137 -64
- package/src/cloud-connection/live-updates-handler.ts +103 -234
- package/src/cloud-connection/passthrough-handler.ts +134 -75
- package/src/cloud-connection/shadow-handler.test.ts +45 -57
- package/src/cloud-connection/shadow-handler.ts +114 -56
- package/src/cloud-connection/shadow.ts +4 -1
- package/src/cloud-connection/transaction-manager.test.ts +127 -3
- package/src/cloud-connection/transaction-manager.ts +68 -39
- package/src/device-control/device-control.ts +179 -72
- package/src/docker/docker-compose.ts +61 -0
- package/src/index.ts +2 -6
- package/src/infrastructure/agent-config.test.ts +9 -2
- package/src/infrastructure/agent-config.ts +45 -46
- package/src/infrastructure/config-check-utility.test.ts +154 -0
- package/src/infrastructure/config-check-utility.ts +77 -0
- package/src/infrastructure/device-certificate.test.ts +40 -0
- package/src/infrastructure/device-certificate.ts +58 -0
- package/src/infrastructure/legacy-migration/legacy-file.test.ts +88 -0
- package/src/infrastructure/legacy-migration/legacy-files.ts +101 -0
- package/src/infrastructure/legacy-migration/legacy-migration.test.ts +396 -0
- package/src/infrastructure/legacy-migration/legacy-migration.ts +229 -0
- package/src/infrastructure/require-files-present-ready.test.ts +53 -0
- package/src/infrastructure/required-config-checks.ts +33 -0
- package/src/infrastructure/tokens-and-device-cfg.ts +12 -10
- package/src/local-connection/rabbitmq-connection.ts +28 -23
- package/src/secure-tunneling/secure-tunneling.test.ts +37 -39
- package/src/secure-tunneling/secure-tunneling.ts +74 -69
- package/src/subcommands/app/analytics.ts +2 -4
- package/src/subcommands/app/env-vars.ts +72 -9
- package/src/subcommands/app/index.ts +3 -11
- package/src/subcommands/app/models.ts +5 -81
- package/src/subcommands/app/shadow.ts +6 -5
- package/src/subcommands/app/version.ts +3 -4
- package/src/subcommands/config.ts +42 -0
- package/src/subcommands/device/clean.ts +32 -18
- package/src/subcommands/device/get-info.ts +49 -0
- package/src/subcommands/device/index.ts +13 -2
- package/src/subcommands/device/{device.ts → init.ts} +11 -69
- package/src/subcommands/device/migrate.ts +20 -0
- package/src/subcommands/device/refresh.ts +23 -0
- package/src/subcommands/device/restart.ts +11 -0
- package/src/subcommands/index.ts +3 -1
- package/src/util/aai-error.ts +20 -0
- package/src/util/{cloud-mode-ready.ts → aws-regions.ts} +0 -24
- package/src/util/check-for-updates.ts +53 -0
- package/src/util/clean-certs.ts +8 -4
- package/src/util/directories.ts +23 -67
- package/src/util/file.test.ts +90 -0
- package/src/util/file.ts +156 -0
- package/src/util/get-device-id.ts +7 -7
- package/src/util/http-client.ts +2 -2
- 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/cloud-mode-ready.d.ts +0 -3
- package/lib/util/cloud-mode-ready.d.ts.map +0 -1
- package/lib/util/cloud-mode-ready.js.map +0 -1
- package/lib/util/download-file.d.ts +0 -6
- package/lib/util/download-file.d.ts.map +0 -1
- package/lib/util/download-file.js +0 -25
- package/lib/util/download-file.js.map +0 -1
- package/lib/util/fetch-with-timeout.d.ts +0 -4
- package/lib/util/fetch-with-timeout.d.ts.map +0 -1
- package/lib/util/fetch-with-timeout.js +0 -30
- package/lib/util/fetch-with-timeout.js.map +0 -1
- package/lib/util/parsing.d.ts +0 -2
- package/lib/util/parsing.d.ts.map +0 -1
- package/lib/util/parsing.js +0 -17
- package/lib/util/parsing.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/download-file.ts +0 -25
- package/src/util/fetch-with-timeout.ts +0 -35
- package/src/util/parsing.ts +0 -11
- package/src/util/safe-rimraf.ts +0 -14
|
@@ -10,15 +10,27 @@ import {
|
|
|
10
10
|
SignedUrlsRequestPayload,
|
|
11
11
|
ToCloudMessage,
|
|
12
12
|
ToDeviceAgentMessage,
|
|
13
|
-
|
|
13
|
+
buildAppLogsMessage,
|
|
14
|
+
buildAppStateMessage,
|
|
15
|
+
buildDeviceStatsMessage,
|
|
14
16
|
buildSignedUrlsRequestMessage,
|
|
15
17
|
buildToClientStatusResponseMessage,
|
|
16
|
-
|
|
18
|
+
getToDeviceTopic,
|
|
19
|
+
getUpdateDeltaStateFromMessage,
|
|
17
20
|
keyMirrors,
|
|
21
|
+
validateSecureTunnelShadowUpdate,
|
|
18
22
|
validateToDeviceAgentMessage
|
|
19
23
|
} from '@alwaysai/device-agent-schemas';
|
|
24
|
+
import {
|
|
25
|
+
DEVICE_CERTIFICATE_FILE_PATH,
|
|
26
|
+
DEVICE_PRIVATE_KEY_FILE_PATH
|
|
27
|
+
} from 'alwaysai/lib/infrastructure';
|
|
28
|
+
import { stringifyError } from 'alwaysai/lib/util';
|
|
29
|
+
import { exec } from 'child_process';
|
|
20
30
|
import { existsSync } from 'fs';
|
|
31
|
+
import { promisify } from 'util';
|
|
21
32
|
import {
|
|
33
|
+
getAppLogs,
|
|
22
34
|
installApp,
|
|
23
35
|
restartApp,
|
|
24
36
|
startApp,
|
|
@@ -28,35 +40,33 @@ import {
|
|
|
28
40
|
updateModelsWithPresignedUrls
|
|
29
41
|
} from '../application-control';
|
|
30
42
|
import { createAppBackup, rollbackApp } from '../application-control/backup';
|
|
43
|
+
import { pruneModels } from '../application-control/models';
|
|
31
44
|
import { reboot } from '../device-control/device-control';
|
|
32
45
|
import { ALWAYSAI_ANALYTICS_PASSTHROUGH } from '../environment';
|
|
33
46
|
import { AgentConfigFile } from '../infrastructure/agent-config';
|
|
47
|
+
import { getBootstrapPrivateKeyFilePath } from '../infrastructure/device-certificate';
|
|
48
|
+
import { migrateFromLegacyCertsAndTokens } from '../infrastructure/legacy-migration/legacy-migration';
|
|
49
|
+
import { requiredConfigFilesPresentAndValid } from '../infrastructure/required-config-checks';
|
|
34
50
|
import { getIoTCoreEndpointUrl } from '../infrastructure/urls';
|
|
35
51
|
import { SecureTunnelHandlerSingleton } from '../secure-tunneling/secure-tunneling';
|
|
36
|
-
import
|
|
37
|
-
import {
|
|
38
|
-
|
|
39
|
-
BOOTSTRAP_CERTIFICATES_DIR_PATH,
|
|
40
|
-
BOOTSTRAP_PRIVATE_KEY_FILE_PATH,
|
|
41
|
-
DEVICE_CERTIFICATE_FILE_PATH,
|
|
42
|
-
DEVICE_PRIVATE_KEY_FILE_PATH
|
|
43
|
-
} from '../util/directories';
|
|
52
|
+
import AaiError from '../util/aai-error';
|
|
53
|
+
import { getDeviceAgentVersion } from '../util/check-for-updates';
|
|
54
|
+
import { AWS_ROOT_CERTIFICATE_FILE_PATH } from '../util/directories';
|
|
44
55
|
import { getDeviceUuid } from '../util/get-device-id';
|
|
45
56
|
import { logger } from '../util/logger';
|
|
46
57
|
import sleep from '../util/sleep';
|
|
47
58
|
import { bootstrapProvision } from './bootstrap-provision';
|
|
48
59
|
import { LiveUpdatesHandler } from './live-updates-handler';
|
|
49
|
-
import {
|
|
60
|
+
import { getAppStatePayload, getDeviceStatsPayload } from './messages';
|
|
61
|
+
import { PassthroughHandler } from './passthrough-handler';
|
|
50
62
|
import { Publisher } from './publisher';
|
|
51
63
|
import { ShadowHandler, ShadowUpdate } from './shadow-handler';
|
|
52
64
|
import { TransactionManager } from './transaction-manager';
|
|
53
|
-
import { exec } from 'child_process';
|
|
54
|
-
import { promisify } from 'util';
|
|
55
65
|
|
|
56
66
|
const exec_promise = promisify(exec);
|
|
57
67
|
|
|
58
68
|
export class DeviceAgentCloudConnection {
|
|
59
|
-
|
|
69
|
+
public shadowHandler: ShadowHandler;
|
|
60
70
|
public publisher: Publisher;
|
|
61
71
|
private liveUpdatesHandler: LiveUpdatesHandler;
|
|
62
72
|
private txnMgr: TransactionManager;
|
|
@@ -69,357 +79,183 @@ export class DeviceAgentCloudConnection {
|
|
|
69
79
|
private readonly secureTunnelNotifyTopic = `$aws/things/${this.clientId}/tunnels/notify`;
|
|
70
80
|
private readonly secureTunnelHandler =
|
|
71
81
|
SecureTunnelHandlerSingleton.getInstance();
|
|
72
|
-
// FIXME: Add support for multiple simultaneous project updates
|
|
73
|
-
private appCfgUpdateQueue: ShadowUpdate[] = [];
|
|
74
82
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
break;
|
|
86
|
-
case keyMirrors.appStateControl.restart:
|
|
87
|
-
await restartApp({ projectId });
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
return true;
|
|
91
|
-
};
|
|
83
|
+
constructor() {
|
|
84
|
+
this.device = awsIot.device({
|
|
85
|
+
keyPath: DEVICE_PRIVATE_KEY_FILE_PATH,
|
|
86
|
+
certPath: DEVICE_CERTIFICATE_FILE_PATH,
|
|
87
|
+
caPath: AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
88
|
+
clientId: this.clientId,
|
|
89
|
+
host: this.host,
|
|
90
|
+
port: this.port,
|
|
91
|
+
keepalive: 10 // time before re-connect attempt on dropped connection, default is 400 seconds
|
|
92
|
+
});
|
|
92
93
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
case keyMirrors.appVersionControl.install: {
|
|
101
|
-
const { projectId, appReleaseHash } = payload;
|
|
94
|
+
this.publisher = new Publisher(this.device, this.clientId);
|
|
95
|
+
this.shadowHandler = new ShadowHandler(this.clientId, this.publisher);
|
|
96
|
+
this.liveUpdatesHandler = new LiveUpdatesHandler();
|
|
97
|
+
this.txnMgr = new TransactionManager(
|
|
98
|
+
this.publisher,
|
|
99
|
+
this.liveUpdatesHandler
|
|
100
|
+
);
|
|
102
101
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
const message = buildSignedUrlsRequestMessage(
|
|
110
|
-
this.clientId,
|
|
111
|
-
signedUrlsRequestPayload,
|
|
112
|
-
txId
|
|
113
|
-
);
|
|
114
|
-
await this.publishCloudRequest(message);
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
case keyMirrors.appVersionControl.uninstall: {
|
|
118
|
-
const { projectId } = payload;
|
|
119
|
-
await this.atomicApplicationUninstall(projectId);
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
default:
|
|
123
|
-
logger.warn(
|
|
124
|
-
`Ignore App Version Control packet: ${JSON.stringify(
|
|
125
|
-
payload,
|
|
126
|
-
null,
|
|
127
|
-
2
|
|
128
|
-
)}`
|
|
129
|
-
);
|
|
130
|
-
return true;
|
|
102
|
+
this.subscribe(this.toDeviceTopic);
|
|
103
|
+
this.subscribe(this.secureTunnelNotifyTopic);
|
|
104
|
+
for (const topic of this.shadowHandler.projectShadowTopics) {
|
|
105
|
+
this.subscribe(topic);
|
|
131
106
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
private handleAppInstallCloudResponsePayload = async (
|
|
135
|
-
payload: AppInstallResponsePayload
|
|
136
|
-
): Promise<boolean> => {
|
|
137
|
-
const {
|
|
138
|
-
projectId,
|
|
139
|
-
appReleaseHash,
|
|
140
|
-
appInstallPayload,
|
|
141
|
-
modelsInstallPayload
|
|
142
|
-
} = payload.appInstallResponse;
|
|
143
|
-
const signedUrlsPayload = {
|
|
144
|
-
appInstallPayload,
|
|
145
|
-
modelsInstallPayload
|
|
146
|
-
};
|
|
147
|
-
await this.atomicApplicationUpdate(async () => {
|
|
148
|
-
this.shadowHandler.clearProjectShadow(projectId);
|
|
149
|
-
await installApp({ projectId, appReleaseHash, signedUrlsPayload });
|
|
150
|
-
}, projectId);
|
|
151
|
-
return true;
|
|
152
|
-
};
|
|
107
|
+
this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.updateDelta);
|
|
108
|
+
this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted);
|
|
153
109
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
): Promise<boolean> => {
|
|
157
|
-
const update = this.appCfgUpdateQueue.shift();
|
|
158
|
-
if (update === undefined) {
|
|
159
|
-
throw new Error(
|
|
160
|
-
'Unknown error while updating models via application config! No config present for model update.'
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
const { appCfgUpdate, envVarUpdate } = update;
|
|
164
|
-
const projectId = payload.modelsInstallResponse.projectId;
|
|
165
|
-
if (appCfgUpdate) {
|
|
166
|
-
await this.atomicApplicationUpdate(
|
|
167
|
-
async () =>
|
|
168
|
-
await updateModelsWithPresignedUrls({
|
|
169
|
-
projectId,
|
|
170
|
-
modelInstallPayloads: payload.modelsInstallResponse.newModels,
|
|
171
|
-
newAppCfg: appCfgUpdate.newAppCfg
|
|
172
|
-
}),
|
|
173
|
-
projectId
|
|
174
|
-
);
|
|
175
|
-
}
|
|
110
|
+
this.setupHandlers();
|
|
111
|
+
}
|
|
176
112
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
await this.shadowHandler.updateProjectEnvVars({
|
|
181
|
-
projectId,
|
|
182
|
-
envVars: envVarUpdate.envVars
|
|
183
|
-
}),
|
|
184
|
-
projectId,
|
|
185
|
-
true
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
return true;
|
|
189
|
-
};
|
|
113
|
+
/*=================================================================
|
|
114
|
+
Public interface
|
|
115
|
+
=================================================================*/
|
|
190
116
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
switch (payload.action) {
|
|
194
|
-
case system_restart: {
|
|
195
|
-
await reboot();
|
|
196
|
-
break;
|
|
197
|
-
}
|
|
198
|
-
default: {
|
|
199
|
-
logger.info(
|
|
200
|
-
`Unrecognized device action requested: '${payload.action}'.`
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
117
|
+
public getClientId(): string {
|
|
118
|
+
return this.clientId;
|
|
204
119
|
}
|
|
205
120
|
|
|
206
|
-
|
|
207
|
-
this.
|
|
121
|
+
public isCmdInProgress(projectId: string): boolean {
|
|
122
|
+
return this.txnMgr.isOngoingTransactionForProjectID(projectId);
|
|
208
123
|
}
|
|
209
124
|
|
|
210
|
-
|
|
211
|
-
logger.
|
|
212
|
-
|
|
125
|
+
public async handleMessage(topic: string, message: any) {
|
|
126
|
+
logger.debug(
|
|
127
|
+
`Received message: ${JSON.stringify({ topic, message }, null, 2)}`
|
|
128
|
+
);
|
|
129
|
+
// ProjectShadow messages
|
|
130
|
+
if (this.shadowHandler.projectShadowTopics.includes(topic)) {
|
|
131
|
+
await this.handleProjectShadowMessage(topic, message);
|
|
132
|
+
} else if (topic === this.toDeviceTopic) {
|
|
133
|
+
await this.handleDeviceAgentMessage({
|
|
134
|
+
topic,
|
|
135
|
+
message
|
|
136
|
+
});
|
|
137
|
+
// SecureTunnelNotify messages
|
|
138
|
+
} else if (topic === this.secureTunnelNotifyTopic) {
|
|
139
|
+
await this.secureTunnelHandler.secureTunnelNotifyHandler(message);
|
|
140
|
+
// SecureTunnel messages
|
|
141
|
+
} else if (
|
|
142
|
+
topic === this.shadowHandler.shadowTopics.secureTunnel.updateDelta
|
|
143
|
+
) {
|
|
144
|
+
await this.handleSecureTunnelMessage(message);
|
|
145
|
+
} else if (
|
|
146
|
+
topic === this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted
|
|
147
|
+
) {
|
|
148
|
+
logger.info(`Received secure tunnel deleteAccepted: ${message}`);
|
|
149
|
+
await this.secureTunnelHandler.destroy();
|
|
150
|
+
} else {
|
|
151
|
+
logger.error(`Unexpected topic, ignoring! ${topic}`);
|
|
152
|
+
}
|
|
213
153
|
}
|
|
214
154
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
throw e;
|
|
222
|
-
}
|
|
155
|
+
public async stop() {
|
|
156
|
+
// This method is currently only used by the CLI, and shadow messages can be
|
|
157
|
+
// lost since we aren't waiting for responses so sleep for a short time to
|
|
158
|
+
// receive them
|
|
159
|
+
await sleep(1000);
|
|
160
|
+
this.device.end();
|
|
223
161
|
}
|
|
224
162
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
163
|
+
/*=================================================================
|
|
164
|
+
Private interface
|
|
165
|
+
=================================================================*/
|
|
166
|
+
|
|
167
|
+
private setupHandlers() {
|
|
168
|
+
this.device.on('connect', (connack: any) => {
|
|
169
|
+
logger.info('Device Agent has connected to the cloud');
|
|
170
|
+
// FIXME: EI-709 Skip this request for now to prevent kicking off another
|
|
171
|
+
// shadow update process if IoT Core disconnect occurs during app config update
|
|
172
|
+
//this.shadowHandler.getShadowUpdates();
|
|
173
|
+
void this.shadowHandler.updateSystemInfoShadow();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
this.device.on('disconnect', () => {
|
|
177
|
+
logger.warn('Device Agent has been disconnected from the cloud');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
this.device.on('reconnect', () => {
|
|
181
|
+
logger.info(
|
|
182
|
+
`Device Agent attempting to re-connect ${new Date().toLocaleString()}`
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
this.device.on('error', function (e) {
|
|
187
|
+
logger.error(`Error connecting to cloud!\n${stringifyError(e)}`);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
this.device.on('message', async (topic: string, payload: string) => {
|
|
233
191
|
try {
|
|
234
|
-
|
|
192
|
+
const jsonPacket = JSON.parse(payload);
|
|
193
|
+
await this.handleMessage(topic, jsonPacket);
|
|
235
194
|
} catch (e) {
|
|
236
|
-
logger.error(
|
|
237
|
-
`Could not create a backup for the project: ${projectId}:\n${e.message}\n${e.stack}`
|
|
238
|
-
);
|
|
195
|
+
logger.error(`Error parsing message!\n${stringifyError(e)}`);
|
|
239
196
|
}
|
|
240
|
-
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
this.device.on('offline', () => {
|
|
200
|
+
logger.warn(`Device Agent is offline ${new Date().toLocaleString()}`);
|
|
201
|
+
void this.logConnectionInfo();
|
|
202
|
+
});
|
|
203
|
+
}
|
|
241
204
|
|
|
205
|
+
private async logConnectionInfo() {
|
|
242
206
|
try {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
207
|
+
/**
|
|
208
|
+
* We're using the 'netcat' or 'nc' command to test the connection to the IoT Core endpoint.
|
|
209
|
+
* This command doesn't always exit (see below), so
|
|
210
|
+
* we use timeout to break out of the prompt
|
|
211
|
+
* and catch the resulting error/parse the resulting stderr
|
|
212
|
+
*
|
|
213
|
+
* Sample command for current host and port:
|
|
214
|
+
* nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 8883
|
|
215
|
+
*
|
|
216
|
+
* Sample output when port is not blocked and host is reachable:
|
|
217
|
+
* $ nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 443
|
|
218
|
+
* Connection to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 443 port [tcp/https] succeeded!
|
|
219
|
+
*
|
|
220
|
+
*
|
|
221
|
+
* Sample output when port is blocked (will repeatedly try until ctrl-C out):
|
|
222
|
+
* $ nc -zv -w 1 a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com 8883
|
|
223
|
+
* nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
|
|
224
|
+
* nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
|
|
225
|
+
* nc: connect to a3tzi5g7sq5zsj-ats.iot.us-west-2.amazonaws.com port 8883 (tcp) timed out: Operation now in progress
|
|
226
|
+
* ^C
|
|
227
|
+
*
|
|
228
|
+
*
|
|
229
|
+
* Sample command/output when the port isn't enable on that host:
|
|
230
|
+
* $ nc -zv -w 1 localhost 8883
|
|
231
|
+
* nc: connect to localhost port 8883 (tcp) failed: Connection refused
|
|
232
|
+
*/
|
|
233
|
+
await exec_promise(`nc -zv -w 1 ${this.host} ${this.port}`, {
|
|
234
|
+
timeout: 2000
|
|
235
|
+
});
|
|
236
|
+
} catch (err) {
|
|
237
|
+
const output = JSON.stringify(err['stderr']);
|
|
238
|
+
if (output.indexOf('not known') !== -1) {
|
|
239
|
+
logger.warn(
|
|
240
|
+
'Iot Core endpoint appears to be unreachable, internet connection may be unstable or the host may be down.'
|
|
256
241
|
);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
await this.atomicApplicationUninstall(projectId);
|
|
261
|
-
} catch {
|
|
262
|
-
// atomicApplicationUninstall handles failing, so there's nothing to handle here.
|
|
263
|
-
}
|
|
264
|
-
logger.error(
|
|
265
|
-
`Application update failed, rolled back to previous version: ${errorAppUpdate}`
|
|
242
|
+
} else if (output.indexOf('timed out') !== -1) {
|
|
243
|
+
logger.warn(
|
|
244
|
+
`Internet connection appears fine, however the endpoint was not reachable on the current connection port: ${this.port}\nPlease check if a firewall is in place.`
|
|
266
245
|
);
|
|
267
|
-
|
|
268
|
-
|
|
246
|
+
} else if (output.indexOf('refused') !== -1) {
|
|
247
|
+
logger.warn(
|
|
248
|
+
`The connection was refused, likely ${this.host} is not running a service on ${this.port}.`
|
|
269
249
|
);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
private handleProjectShadowConfigUpdate = async (
|
|
278
|
-
update: ShadowUpdate,
|
|
279
|
-
txId: string
|
|
280
|
-
): Promise<boolean> => {
|
|
281
|
-
const { projectId, appCfgUpdate, envVarUpdate } = update;
|
|
282
|
-
|
|
283
|
-
if (
|
|
284
|
-
appCfgUpdate?.updatedModels &&
|
|
285
|
-
Object.keys(appCfgUpdate.updatedModels).length
|
|
286
|
-
) {
|
|
287
|
-
// When there are model updates request signed URLs and wait to apply config changes
|
|
288
|
-
const { updatedModels } = appCfgUpdate;
|
|
289
|
-
|
|
290
|
-
logger.debug(
|
|
291
|
-
`Requesting presigned urls from cloud for model versions: ${JSON.stringify(
|
|
292
|
-
updatedModels
|
|
293
|
-
)}`
|
|
294
|
-
);
|
|
295
|
-
const modelsOnlyUrlsRequestPayload: SignedUrlsRequestPayload = {
|
|
296
|
-
modelsOnlyUrlsRequest: {
|
|
297
|
-
projectId,
|
|
298
|
-
models: updatedModels
|
|
299
|
-
}
|
|
300
|
-
};
|
|
301
|
-
const message = buildSignedUrlsRequestMessage(
|
|
302
|
-
this.clientId,
|
|
303
|
-
modelsOnlyUrlsRequestPayload,
|
|
304
|
-
txId
|
|
305
|
-
);
|
|
306
|
-
this.publisher.publishToCloud(message);
|
|
307
|
-
|
|
308
|
-
this.appCfgUpdateQueue.push(update);
|
|
309
|
-
return false;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (appCfgUpdate) {
|
|
313
|
-
await this.atomicApplicationUpdate(
|
|
314
|
-
async () =>
|
|
315
|
-
await updateAppCfg({
|
|
316
|
-
projectId,
|
|
317
|
-
newAppCfg: appCfgUpdate.newAppCfg
|
|
318
|
-
}),
|
|
319
|
-
projectId
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (envVarUpdate) {
|
|
324
|
-
await this.atomicApplicationUpdate(
|
|
325
|
-
async () =>
|
|
326
|
-
await this.shadowHandler.updateProjectEnvVars({
|
|
327
|
-
projectId,
|
|
328
|
-
envVars: envVarUpdate.envVars
|
|
329
|
-
}),
|
|
330
|
-
projectId,
|
|
331
|
-
true
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
return true;
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
private async handleProjectShadowMessage(topic: string, message: any) {
|
|
338
|
-
const shadowUpdates = await this.shadowHandler.handleProjectShadow({
|
|
339
|
-
topic,
|
|
340
|
-
payload: message,
|
|
341
|
-
clientToken: message.clientToken
|
|
342
|
-
});
|
|
343
|
-
if (shadowUpdates.length) {
|
|
344
|
-
const shadowUpdatePromises: Promise<void>[] = [];
|
|
345
|
-
for (const shadowUpdate of shadowUpdates) {
|
|
346
|
-
const projectId = shadowUpdate.projectId;
|
|
347
|
-
const txId = shadowUpdate.txId;
|
|
348
|
-
shadowUpdatePromises.push(
|
|
349
|
-
this.txnMgr
|
|
350
|
-
.runTransactionStep({
|
|
351
|
-
func: () =>
|
|
352
|
-
this.handleProjectShadowConfigUpdate(shadowUpdate, txId),
|
|
353
|
-
projectId,
|
|
354
|
-
txId,
|
|
355
|
-
start: true,
|
|
356
|
-
stepName: topic
|
|
357
|
-
})
|
|
358
|
-
.catch((e: Error) => {
|
|
359
|
-
logger.error(
|
|
360
|
-
`There was an issue updating project shadow config: ${JSON.stringify(
|
|
361
|
-
e
|
|
362
|
-
)}`
|
|
363
|
-
);
|
|
364
|
-
})
|
|
250
|
+
} else {
|
|
251
|
+
logger.warn(
|
|
252
|
+
`Output from checking connection to ${this.host} on ${this.port}: ${output}`
|
|
365
253
|
);
|
|
366
254
|
}
|
|
367
|
-
|
|
368
|
-
await Promise.all(shadowUpdatePromises);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/*=================================================================
|
|
373
|
-
Public interface
|
|
374
|
-
=================================================================*/
|
|
375
|
-
|
|
376
|
-
constructor() {
|
|
377
|
-
this.device = awsIot.device({
|
|
378
|
-
keyPath: DEVICE_PRIVATE_KEY_FILE_PATH,
|
|
379
|
-
certPath: DEVICE_CERTIFICATE_FILE_PATH,
|
|
380
|
-
caPath: AWS_ROOT_CERTIFICATE_FILE_PATH,
|
|
381
|
-
clientId: this.clientId,
|
|
382
|
-
host: this.host,
|
|
383
|
-
port: this.port,
|
|
384
|
-
keepalive: 1 // time before re-connect attempt on dropped connection, default is 400 seconds
|
|
385
|
-
});
|
|
386
|
-
this.publisher = new Publisher(this.device, this.clientId);
|
|
387
|
-
this.shadowHandler = new ShadowHandler(this.clientId, this.publisher);
|
|
388
|
-
this.liveUpdatesHandler = new LiveUpdatesHandler(
|
|
389
|
-
this.publisher,
|
|
390
|
-
this.clientId
|
|
391
|
-
);
|
|
392
|
-
this.txnMgr = new TransactionManager(
|
|
393
|
-
this.publisher,
|
|
394
|
-
this.liveUpdatesHandler
|
|
395
|
-
);
|
|
396
|
-
|
|
397
|
-
this.subscribe(this.toDeviceTopic);
|
|
398
|
-
this.subscribe(this.secureTunnelNotifyTopic);
|
|
399
|
-
for (const topic of this.shadowHandler.projectShadowTopics) {
|
|
400
|
-
this.subscribe(topic);
|
|
401
255
|
}
|
|
402
|
-
this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.updateDelta);
|
|
403
|
-
this.subscribe(this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
public getClientId(): string {
|
|
407
|
-
return this.clientId;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
public getToDeviceTopic() {
|
|
411
|
-
return this.toDeviceTopic;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
public isCmdInProgress(projectId: string): boolean {
|
|
415
|
-
return this.txnMgr.isOngoingTransactionForProjectID(projectId);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
public async updateProjectShadow(projectId: string) {
|
|
419
|
-
await this.shadowHandler.updateProjectShadow(projectId);
|
|
420
256
|
}
|
|
421
257
|
|
|
422
|
-
|
|
258
|
+
private async handleDeviceAgentMessage({
|
|
423
259
|
topic,
|
|
424
260
|
message
|
|
425
261
|
}: {
|
|
@@ -460,11 +296,22 @@ export class DeviceAgentCloudConnection {
|
|
|
460
296
|
projectId,
|
|
461
297
|
txId,
|
|
462
298
|
start: true,
|
|
299
|
+
liveUpdatesPublishFn: async () =>
|
|
300
|
+
this.publisher.publishToClient(
|
|
301
|
+
buildToClientStatusResponseMessage(
|
|
302
|
+
this.clientId,
|
|
303
|
+
{ status: keyMirrors.statusResponse.in_progress },
|
|
304
|
+
txId
|
|
305
|
+
),
|
|
306
|
+
logger.silly
|
|
307
|
+
),
|
|
463
308
|
stepName: payload.baseCommand
|
|
464
309
|
});
|
|
465
310
|
} catch (e) {
|
|
466
311
|
logger.error(
|
|
467
|
-
`Error processing application state control request
|
|
312
|
+
`Error processing application state control request for ${projectId}!\n${stringifyError(
|
|
313
|
+
e
|
|
314
|
+
)}`
|
|
468
315
|
);
|
|
469
316
|
}
|
|
470
317
|
|
|
@@ -480,18 +327,98 @@ export class DeviceAgentCloudConnection {
|
|
|
480
327
|
projectId,
|
|
481
328
|
txId,
|
|
482
329
|
start: true,
|
|
330
|
+
liveUpdatesPublishFn: async () =>
|
|
331
|
+
this.publisher.publishToClient(
|
|
332
|
+
buildToClientStatusResponseMessage(
|
|
333
|
+
this.clientId,
|
|
334
|
+
{ status: keyMirrors.statusResponse.in_progress },
|
|
335
|
+
txId
|
|
336
|
+
),
|
|
337
|
+
logger.silly
|
|
338
|
+
),
|
|
483
339
|
stepName: payload.baseCommand
|
|
484
340
|
});
|
|
485
341
|
} catch (e) {
|
|
486
|
-
logger.error(
|
|
342
|
+
logger.error(
|
|
343
|
+
`Error processing application install request for ${projectId}!\n${stringifyError(
|
|
344
|
+
e
|
|
345
|
+
)}`
|
|
346
|
+
);
|
|
487
347
|
}
|
|
488
348
|
|
|
489
349
|
break;
|
|
490
350
|
}
|
|
491
351
|
case live_state_updates: {
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
352
|
+
const { deviceStats, appState, appLogs } = message.payload;
|
|
353
|
+
|
|
354
|
+
if (deviceStats !== undefined) {
|
|
355
|
+
if (deviceStats) {
|
|
356
|
+
await this.liveUpdatesHandler.enable(
|
|
357
|
+
keyMirrors.toClientMessageType.device_stats,
|
|
358
|
+
async () =>
|
|
359
|
+
this.publisher.publishToClient(
|
|
360
|
+
buildDeviceStatsMessage(
|
|
361
|
+
this.clientId,
|
|
362
|
+
await getDeviceStatsPayload(),
|
|
363
|
+
txId
|
|
364
|
+
),
|
|
365
|
+
logger.silly
|
|
366
|
+
)
|
|
367
|
+
);
|
|
368
|
+
} else {
|
|
369
|
+
this.liveUpdatesHandler.disable(
|
|
370
|
+
keyMirrors.toClientMessageType.device_stats
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (appState !== undefined) {
|
|
376
|
+
if (appState) {
|
|
377
|
+
await this.liveUpdatesHandler.enable(
|
|
378
|
+
keyMirrors.toClientMessageType.app_state,
|
|
379
|
+
async () =>
|
|
380
|
+
this.publisher.publishToClient(
|
|
381
|
+
buildAppStateMessage(
|
|
382
|
+
this.clientId,
|
|
383
|
+
await getAppStatePayload(),
|
|
384
|
+
txId
|
|
385
|
+
),
|
|
386
|
+
logger.silly
|
|
387
|
+
)
|
|
388
|
+
);
|
|
389
|
+
} else {
|
|
390
|
+
this.liveUpdatesHandler.disable(
|
|
391
|
+
keyMirrors.toClientMessageType.app_state
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (appLogs !== undefined) {
|
|
397
|
+
if (appLogs.toggle) {
|
|
398
|
+
await this.liveUpdatesHandler.startStream(
|
|
399
|
+
appLogs.projectId,
|
|
400
|
+
async () =>
|
|
401
|
+
await getAppLogs({
|
|
402
|
+
projectId: appLogs.projectId,
|
|
403
|
+
args: ['--tail', '100', '--no-log-prefix']
|
|
404
|
+
}),
|
|
405
|
+
async (logChunk: string) =>
|
|
406
|
+
this.publisher.publishToClient(
|
|
407
|
+
buildAppLogsMessage(
|
|
408
|
+
this.clientId,
|
|
409
|
+
{
|
|
410
|
+
projectId: appLogs.projectId,
|
|
411
|
+
logChunk
|
|
412
|
+
},
|
|
413
|
+
txId
|
|
414
|
+
),
|
|
415
|
+
logger.silly
|
|
416
|
+
)
|
|
417
|
+
);
|
|
418
|
+
} else {
|
|
419
|
+
this.liveUpdatesHandler.stopStream(appLogs.projectId);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
495
422
|
break;
|
|
496
423
|
}
|
|
497
424
|
case app_install_response: {
|
|
@@ -526,7 +453,8 @@ export class DeviceAgentCloudConnection {
|
|
|
526
453
|
);
|
|
527
454
|
}
|
|
528
455
|
await this.txnMgr.runTransactionStep({
|
|
529
|
-
func: () =>
|
|
456
|
+
func: () =>
|
|
457
|
+
this.handleModelsInstallCloudResponsePayload(payload, txId),
|
|
530
458
|
projectId,
|
|
531
459
|
txId,
|
|
532
460
|
start: false,
|
|
@@ -537,62 +465,59 @@ export class DeviceAgentCloudConnection {
|
|
|
537
465
|
case status_response: {
|
|
538
466
|
const { failure } = keyMirrors.statusResponse;
|
|
539
467
|
if (message.payload.status === failure) {
|
|
540
|
-
this.txnMgr.completeTransaction(
|
|
541
|
-
|
|
542
|
-
const failureStatusResponsePayload: StatusResponsePayload = {
|
|
543
|
-
status: keyMirrors.statusResponse.failure,
|
|
544
|
-
message: message.payload.message
|
|
545
|
-
};
|
|
546
|
-
// Send final status message
|
|
547
|
-
const failureStatusResponseMessage =
|
|
468
|
+
this.txnMgr.completeTransaction(
|
|
469
|
+
txId,
|
|
548
470
|
buildToClientStatusResponseMessage(
|
|
549
471
|
this.clientId,
|
|
550
|
-
|
|
472
|
+
{
|
|
473
|
+
status: keyMirrors.statusResponse.failure,
|
|
474
|
+
message: message.payload.message
|
|
475
|
+
},
|
|
551
476
|
txId
|
|
552
|
-
)
|
|
553
|
-
|
|
477
|
+
)
|
|
478
|
+
);
|
|
554
479
|
}
|
|
555
480
|
break;
|
|
556
481
|
}
|
|
557
482
|
case device_action: {
|
|
558
483
|
try {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
484
|
+
this.publisher.publishToClient(
|
|
485
|
+
buildToClientStatusResponseMessage(
|
|
486
|
+
this.clientId,
|
|
487
|
+
{
|
|
488
|
+
status: keyMirrors.statusResponse.in_progress
|
|
489
|
+
},
|
|
490
|
+
txId
|
|
491
|
+
)
|
|
566
492
|
);
|
|
567
|
-
this.publisher.publishToClient(statusResponseMessage);
|
|
568
493
|
|
|
569
494
|
await this.handleDeviceAction(message.payload);
|
|
570
495
|
|
|
571
|
-
|
|
572
|
-
status: keyMirrors.statusResponse.success
|
|
573
|
-
};
|
|
574
|
-
const successStatusResponseMessage =
|
|
496
|
+
this.publisher.publishToClient(
|
|
575
497
|
buildToClientStatusResponseMessage(
|
|
576
498
|
this.clientId,
|
|
577
|
-
|
|
499
|
+
{
|
|
500
|
+
status: keyMirrors.statusResponse.success
|
|
501
|
+
},
|
|
578
502
|
txId
|
|
579
|
-
)
|
|
580
|
-
|
|
503
|
+
)
|
|
504
|
+
);
|
|
581
505
|
} catch (e) {
|
|
582
506
|
logger.error(
|
|
583
|
-
`There was a problem performing device action '${
|
|
507
|
+
`There was a problem performing device action '${
|
|
508
|
+
message.payload.action
|
|
509
|
+
}'!\n${stringifyError(e)}`
|
|
584
510
|
);
|
|
585
|
-
|
|
586
|
-
status: keyMirrors.statusResponse.failure,
|
|
587
|
-
message: e.message
|
|
588
|
-
};
|
|
589
|
-
const failureStatusResponseMessage =
|
|
511
|
+
this.publisher.publishToClient(
|
|
590
512
|
buildToClientStatusResponseMessage(
|
|
591
513
|
this.clientId,
|
|
592
|
-
|
|
514
|
+
{
|
|
515
|
+
status: keyMirrors.statusResponse.failure,
|
|
516
|
+
message: e.message
|
|
517
|
+
},
|
|
593
518
|
txId
|
|
594
|
-
)
|
|
595
|
-
|
|
519
|
+
)
|
|
520
|
+
);
|
|
596
521
|
}
|
|
597
522
|
break;
|
|
598
523
|
}
|
|
@@ -607,163 +532,376 @@ export class DeviceAgentCloudConnection {
|
|
|
607
532
|
}
|
|
608
533
|
}
|
|
609
534
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
await this.secureTunnelHandler.secureTunnelNotifyHandler(message);
|
|
628
|
-
// SecureTunnel messages
|
|
629
|
-
} else if (
|
|
630
|
-
topic === this.shadowHandler.shadowTopics.secureTunnel.updateDelta
|
|
631
|
-
) {
|
|
632
|
-
logger.info(`Received secure tunnel update: ${message}`);
|
|
633
|
-
const reported = await this.secureTunnelHandler.syncShadowToDeviceState(
|
|
634
|
-
message
|
|
635
|
-
);
|
|
636
|
-
this.publisher.publish(
|
|
637
|
-
this.shadowHandler.shadowTopics.secureTunnel.update,
|
|
638
|
-
JSON.stringify({ state: { reported } })
|
|
639
|
-
);
|
|
640
|
-
} else if (
|
|
641
|
-
topic === this.shadowHandler.shadowTopics.secureTunnel.deleteAccepted
|
|
642
|
-
) {
|
|
643
|
-
logger.info(`Received secure tunnel deleteAccepted: ${message}`);
|
|
644
|
-
await this.secureTunnelHandler.destroy();
|
|
645
|
-
} else {
|
|
646
|
-
logger.error(`Unexpected topic, ignoring! ${topic}`);
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
public async setupHandlers() {
|
|
651
|
-
this.device.on('connect', (connack: any) => {
|
|
652
|
-
logger.info('Device Agent has connected to the cloud');
|
|
653
|
-
// FIXME: EI-709 Skip this request for now to prevent kicking off another
|
|
654
|
-
// shadow update process if IoT Core disconnect occurs during app config update
|
|
655
|
-
//this.shadowHandler.getShadowUpdates();
|
|
656
|
-
void this.shadowHandler.updateSystemInfoShadow();
|
|
657
|
-
});
|
|
658
|
-
|
|
659
|
-
this.device.on('disconnect', () => {
|
|
660
|
-
logger.warn('Device Agent has been disconnected from the cloud');
|
|
661
|
-
});
|
|
535
|
+
private handleAppStateControl = async (
|
|
536
|
+
payload: AppStateControlPayload
|
|
537
|
+
): Promise<boolean> => {
|
|
538
|
+
const { baseCommand, projectId } = payload;
|
|
539
|
+
switch (baseCommand) {
|
|
540
|
+
case keyMirrors.appStateControl.start:
|
|
541
|
+
await startApp({ projectId });
|
|
542
|
+
break;
|
|
543
|
+
case keyMirrors.appStateControl.stop:
|
|
544
|
+
await stopApp({ projectId });
|
|
545
|
+
break;
|
|
546
|
+
case keyMirrors.appStateControl.restart:
|
|
547
|
+
await restartApp({ projectId });
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
return true;
|
|
551
|
+
};
|
|
662
552
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
553
|
+
private handleAppVersionControl = async (
|
|
554
|
+
payload:
|
|
555
|
+
| AppVersionControlInstallPayload
|
|
556
|
+
| AppVersionControlUninstallPayload,
|
|
557
|
+
txId: string
|
|
558
|
+
): Promise<boolean> => {
|
|
559
|
+
switch (payload.baseCommand) {
|
|
560
|
+
case keyMirrors.appVersionControl.install: {
|
|
561
|
+
const { projectId, appReleaseHash } = payload;
|
|
562
|
+
|
|
563
|
+
const signedUrlsRequestPayload: SignedUrlsRequestPayload = {
|
|
564
|
+
signedUrlsRequest: {
|
|
565
|
+
projectId,
|
|
566
|
+
appReleaseHash
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
const message = buildSignedUrlsRequestMessage(
|
|
570
|
+
this.clientId,
|
|
571
|
+
signedUrlsRequestPayload,
|
|
572
|
+
txId
|
|
573
|
+
);
|
|
574
|
+
await this.publishCloudRequest(message);
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
case keyMirrors.appVersionControl.uninstall: {
|
|
578
|
+
const { projectId } = payload;
|
|
579
|
+
await this.atomicApplicationUninstall(projectId);
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
default:
|
|
583
|
+
logger.warn(
|
|
584
|
+
`Ignore App Version Control packet: ${JSON.stringify(
|
|
585
|
+
payload,
|
|
586
|
+
null,
|
|
587
|
+
2
|
|
588
|
+
)}`
|
|
589
|
+
);
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
private handleAppInstallCloudResponsePayload = async (
|
|
595
|
+
payload: AppInstallResponsePayload
|
|
596
|
+
): Promise<boolean> => {
|
|
597
|
+
const {
|
|
598
|
+
projectId,
|
|
599
|
+
appReleaseHash,
|
|
600
|
+
appInstallPayload,
|
|
601
|
+
modelsInstallPayload
|
|
602
|
+
} = payload.appInstallResponse;
|
|
603
|
+
const signedUrlsPayload = {
|
|
604
|
+
appInstallPayload,
|
|
605
|
+
modelsInstallPayload
|
|
606
|
+
};
|
|
607
|
+
await this.atomicApplicationUpdate(async () => {
|
|
608
|
+
this.shadowHandler.clearProjectShadow(projectId);
|
|
609
|
+
await installApp({ projectId, appReleaseHash, signedUrlsPayload });
|
|
610
|
+
}, projectId);
|
|
611
|
+
return true;
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
private handleModelsInstallCloudResponsePayload = async (
|
|
615
|
+
payload: ModelsInstallResponsePayload,
|
|
616
|
+
txId: string
|
|
617
|
+
): Promise<boolean> => {
|
|
618
|
+
const projectId = payload.modelsInstallResponse.projectId;
|
|
619
|
+
|
|
620
|
+
const update = this.txnMgr.getAppCfgUpdateFromTxID(txId);
|
|
621
|
+
if (update === undefined) {
|
|
622
|
+
throw new Error(
|
|
623
|
+
'Unknown error while updating models via application config! No config present for model update.'
|
|
666
624
|
);
|
|
667
|
-
}
|
|
625
|
+
}
|
|
626
|
+
const { appCfgUpdate, envVarUpdate } = update;
|
|
627
|
+
if (appCfgUpdate) {
|
|
628
|
+
await this.atomicApplicationUpdate(
|
|
629
|
+
async () =>
|
|
630
|
+
await updateModelsWithPresignedUrls({
|
|
631
|
+
projectId,
|
|
632
|
+
modelInstallPayloads: payload.modelsInstallResponse.newModels,
|
|
633
|
+
newAppCfg: appCfgUpdate.newAppCfg
|
|
634
|
+
}),
|
|
635
|
+
projectId
|
|
636
|
+
);
|
|
637
|
+
}
|
|
668
638
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
639
|
+
if (envVarUpdate) {
|
|
640
|
+
await this.atomicApplicationUpdate(
|
|
641
|
+
async () =>
|
|
642
|
+
await this.shadowHandler.updateProjectEnvVars({
|
|
643
|
+
projectId,
|
|
644
|
+
envVars: envVarUpdate.envVars
|
|
645
|
+
}),
|
|
646
|
+
projectId,
|
|
647
|
+
true
|
|
648
|
+
);
|
|
649
|
+
}
|
|
673
650
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
651
|
+
return true;
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
private async handleDeviceAction(payload: DeviceActionPayload) {
|
|
655
|
+
const { system_restart } = keyMirrors.deviceAction;
|
|
656
|
+
switch (payload.action) {
|
|
657
|
+
case system_restart: {
|
|
658
|
+
await reboot();
|
|
659
|
+
break;
|
|
680
660
|
}
|
|
681
|
-
|
|
661
|
+
default: {
|
|
662
|
+
logger.info(
|
|
663
|
+
`Unrecognized device action requested: '${payload.action}'.`
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
682
668
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
669
|
+
private async publishCloudRequest(message: ToCloudMessage) {
|
|
670
|
+
this.publisher.publishToCloud(message);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
private subscribe(topic: string) {
|
|
674
|
+
logger.info(`Subscribing to ${topic}`);
|
|
675
|
+
this.device.subscribe(topic);
|
|
687
676
|
}
|
|
688
677
|
|
|
689
|
-
|
|
678
|
+
private async atomicApplicationUninstall(projectId: string) {
|
|
690
679
|
try {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
});
|
|
720
|
-
} catch (err) {
|
|
721
|
-
const output = JSON.stringify(err['stderr']);
|
|
722
|
-
if (output.indexOf('not known') !== -1) {
|
|
723
|
-
logger.warn(
|
|
724
|
-
'Iot Core endpoint appears to be unreachable, internet connection may be unstable or the host may be down.'
|
|
680
|
+
await uninstallApp({ projectId });
|
|
681
|
+
this.shadowHandler.clearProjectShadow(projectId);
|
|
682
|
+
} catch (e) {
|
|
683
|
+
logger.error(`Failed to uninstall ${projectId}!\n${stringifyError(e)}`);
|
|
684
|
+
throw e;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// eslint-disable-next-line
|
|
689
|
+
private async atomicApplicationUpdate<F extends () => any>(
|
|
690
|
+
func: F,
|
|
691
|
+
projectId: string,
|
|
692
|
+
skipUpdateShadow?: boolean
|
|
693
|
+
): Promise<ReturnType<F>> {
|
|
694
|
+
if (await AgentConfigFile().isAppPresent({ projectId })) {
|
|
695
|
+
// Reject the application update if app is present but not ready
|
|
696
|
+
if (!(await AgentConfigFile().isAppReady({ projectId }))) {
|
|
697
|
+
throw new Error('Application already has installation in progress!');
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// Try to create a backup, so that there is one available if something goes wrong in the next try:catch.
|
|
701
|
+
try {
|
|
702
|
+
await createAppBackup({ projectId });
|
|
703
|
+
} catch (e) {
|
|
704
|
+
logger.error(
|
|
705
|
+
`Could not create a backup for the project: ${projectId}!\n${stringifyError(
|
|
706
|
+
e
|
|
707
|
+
)}`
|
|
725
708
|
);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
try {
|
|
713
|
+
const out: ReturnType<F> = await func();
|
|
714
|
+
if (!skipUpdateShadow)
|
|
715
|
+
await this.shadowHandler.updateProjectShadow(projectId);
|
|
716
|
+
return out;
|
|
717
|
+
} catch (errorAppUpdate) {
|
|
718
|
+
logger.error(
|
|
719
|
+
`Failed to update ${projectId}!\n${stringifyError(errorAppUpdate)}`
|
|
720
|
+
);
|
|
721
|
+
// If something goes wrong, first try to rollback
|
|
722
|
+
try {
|
|
723
|
+
await rollbackApp({ projectId });
|
|
724
|
+
} catch (errorRollbackApp) {
|
|
725
|
+
logger.error(
|
|
726
|
+
`Application rollback failed for ${projectId}!\n${stringifyError(
|
|
727
|
+
errorRollbackApp
|
|
728
|
+
)}`
|
|
729
729
|
);
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
730
|
+
// and if that fails, uninstall the app as a last resort.
|
|
731
|
+
try {
|
|
732
|
+
await this.atomicApplicationUninstall(projectId);
|
|
733
|
+
} catch {
|
|
734
|
+
// atomicApplicationUninstall logs failure, so there's nothing to do here.
|
|
735
|
+
}
|
|
736
|
+
throw new AaiError(
|
|
737
|
+
'Application update and rollback failed, uninstalled the application!',
|
|
738
|
+
{ cause: errorAppUpdate }
|
|
733
739
|
);
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
|
|
740
|
+
}
|
|
741
|
+
throw new Error(
|
|
742
|
+
'Application update failed, rolled the application back!',
|
|
743
|
+
{ cause: errorAppUpdate }
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
private handleProjectShadowConfigUpdate = async (
|
|
749
|
+
update: ShadowUpdate,
|
|
750
|
+
txId: string
|
|
751
|
+
): Promise<boolean> => {
|
|
752
|
+
const { projectId, appCfgUpdate, envVarUpdate } = update;
|
|
753
|
+
|
|
754
|
+
if (
|
|
755
|
+
appCfgUpdate?.updatedModels &&
|
|
756
|
+
Object.keys(appCfgUpdate.updatedModels).length
|
|
757
|
+
) {
|
|
758
|
+
// When there are model updates request signed URLs and wait to apply config changes
|
|
759
|
+
const { updatedModels } = appCfgUpdate;
|
|
760
|
+
|
|
761
|
+
logger.debug(
|
|
762
|
+
`Requesting presigned urls from cloud for model versions: ${JSON.stringify(
|
|
763
|
+
updatedModels
|
|
764
|
+
)}`
|
|
765
|
+
);
|
|
766
|
+
const modelsOnlyUrlsRequestPayload: SignedUrlsRequestPayload = {
|
|
767
|
+
modelsOnlyUrlsRequest: {
|
|
768
|
+
projectId,
|
|
769
|
+
models: updatedModels
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
const message = buildSignedUrlsRequestMessage(
|
|
773
|
+
this.clientId,
|
|
774
|
+
modelsOnlyUrlsRequestPayload,
|
|
775
|
+
txId
|
|
776
|
+
);
|
|
777
|
+
this.publisher.publishToCloud(message);
|
|
778
|
+
|
|
779
|
+
this.txnMgr.setAppCfgUpdateToTx(txId, update);
|
|
780
|
+
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
if (appCfgUpdate) {
|
|
785
|
+
await this.atomicApplicationUpdate(async () => {
|
|
786
|
+
await pruneModels({
|
|
787
|
+
projectId,
|
|
788
|
+
appCfg: appCfgUpdate.newAppCfg
|
|
789
|
+
});
|
|
790
|
+
await updateAppCfg({
|
|
791
|
+
projectId,
|
|
792
|
+
newAppCfg: appCfgUpdate.newAppCfg
|
|
793
|
+
});
|
|
794
|
+
}, projectId);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
if (envVarUpdate) {
|
|
798
|
+
await this.atomicApplicationUpdate(
|
|
799
|
+
async () =>
|
|
800
|
+
await this.shadowHandler.updateProjectEnvVars({
|
|
801
|
+
projectId,
|
|
802
|
+
envVars: envVarUpdate.envVars
|
|
803
|
+
}),
|
|
804
|
+
projectId,
|
|
805
|
+
true
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
return true;
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
private async handleProjectShadowMessage(topic: string, message: any) {
|
|
812
|
+
const shadowUpdates = await this.shadowHandler.handleProjectShadow({
|
|
813
|
+
topic,
|
|
814
|
+
payload: message,
|
|
815
|
+
clientToken: message.clientToken
|
|
816
|
+
});
|
|
817
|
+
if (shadowUpdates.length) {
|
|
818
|
+
const shadowUpdatePromises: Promise<void>[] = [];
|
|
819
|
+
for (const shadowUpdate of shadowUpdates) {
|
|
820
|
+
const projectId = shadowUpdate.projectId;
|
|
821
|
+
const txId = shadowUpdate.txId;
|
|
822
|
+
shadowUpdatePromises.push(
|
|
823
|
+
this.txnMgr
|
|
824
|
+
.runTransactionStep({
|
|
825
|
+
func: () =>
|
|
826
|
+
this.handleProjectShadowConfigUpdate(shadowUpdate, txId),
|
|
827
|
+
projectId,
|
|
828
|
+
txId,
|
|
829
|
+
start: true,
|
|
830
|
+
liveUpdatesPublishFn: async () =>
|
|
831
|
+
this.publisher.publishToClient(
|
|
832
|
+
buildToClientStatusResponseMessage(
|
|
833
|
+
this.clientId,
|
|
834
|
+
{ status: keyMirrors.statusResponse.in_progress },
|
|
835
|
+
txId
|
|
836
|
+
),
|
|
837
|
+
logger.silly
|
|
838
|
+
),
|
|
839
|
+
stepName: topic
|
|
840
|
+
})
|
|
841
|
+
.catch((e) => {
|
|
842
|
+
logger.error(
|
|
843
|
+
`There was an issue updating project shadow config for ${projectId}!\n${stringifyError(
|
|
844
|
+
e
|
|
845
|
+
)}`
|
|
846
|
+
);
|
|
847
|
+
})
|
|
737
848
|
);
|
|
738
849
|
}
|
|
850
|
+
|
|
851
|
+
await Promise.all(shadowUpdatePromises);
|
|
739
852
|
}
|
|
740
853
|
}
|
|
741
854
|
|
|
742
|
-
public async
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
855
|
+
public async handleSecureTunnelMessage(payload: any): Promise<void> {
|
|
856
|
+
logger.info(`Received secure tunnel update: ${JSON.stringify(payload)}`);
|
|
857
|
+
const state = getUpdateDeltaStateFromMessage(payload);
|
|
858
|
+
if (!state) {
|
|
859
|
+
logger.debug(`No state found in message: ${JSON.stringify(payload)}`);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
const valid = validateSecureTunnelShadowUpdate(state);
|
|
863
|
+
if (!valid) {
|
|
864
|
+
logger.error(
|
|
865
|
+
`Error validating message: ${JSON.stringify(
|
|
866
|
+
{ payload, errors: validateSecureTunnelShadowUpdate.errors },
|
|
867
|
+
null,
|
|
868
|
+
2
|
|
869
|
+
)}`
|
|
870
|
+
);
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
const secureTunnelUpdate =
|
|
874
|
+
await this.secureTunnelHandler.syncShadowToDeviceState(payload);
|
|
875
|
+
await this.shadowHandler.updateSecureTunnelShadow(secureTunnelUpdate);
|
|
748
876
|
}
|
|
749
877
|
}
|
|
750
878
|
|
|
751
879
|
export async function runDeviceAgentCloudInterface() {
|
|
752
|
-
|
|
880
|
+
logger.info(
|
|
881
|
+
`Starting alwaysAI Device Agent v${await getDeviceAgentVersion()}`
|
|
882
|
+
);
|
|
883
|
+
if (existsSync(getBootstrapPrivateKeyFilePath())) {
|
|
884
|
+
await bootstrapProvision();
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const filesAlreadyMigrated = await requiredConfigFilesPresentAndValid();
|
|
889
|
+
if (!filesAlreadyMigrated) {
|
|
890
|
+
logger.debug('Attempting configuration file migration.');
|
|
891
|
+
await migrateFromLegacyCertsAndTokens();
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if (await requiredConfigFilesPresentAndValid()) {
|
|
753
895
|
const deviceAgent = new DeviceAgentCloudConnection();
|
|
754
|
-
await deviceAgent.setupHandlers();
|
|
755
896
|
if (ALWAYSAI_ANALYTICS_PASSTHROUGH === true) {
|
|
897
|
+
const shadowHandler = deviceAgent.shadowHandler;
|
|
756
898
|
const publisher = deviceAgent.publisher;
|
|
757
|
-
const passthroughHandler = new PassthroughHandler(
|
|
899
|
+
const passthroughHandler = new PassthroughHandler(
|
|
900
|
+
publisher,
|
|
901
|
+
shadowHandler
|
|
902
|
+
);
|
|
758
903
|
await passthroughHandler.setup();
|
|
759
|
-
await runChannel(passthroughHandler);
|
|
760
904
|
}
|
|
761
|
-
} else if (existsSync(BOOTSTRAP_PRIVATE_KEY_FILE_PATH())) {
|
|
762
|
-
await bootstrapProvision();
|
|
763
|
-
} else if (existsSync(BOOTSTRAP_CERTIFICATES_DIR_PATH())) {
|
|
764
|
-
throw new Error(
|
|
765
|
-
"Device has not been created using 'aai-agent device init' or there has been an issue with device initialization"
|
|
766
|
-
);
|
|
767
905
|
} else {
|
|
768
906
|
throw new Error(
|
|
769
907
|
"Set device agent to local mode and retry the 'aai-agent device init' command"
|