@alwaysai/device-agent 1.3.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/application-control/config.js +2 -2
- package/lib/application-control/config.js.map +1 -1
- package/lib/application-control/environment-variables.d.ts.map +1 -1
- package/lib/application-control/environment-variables.js +9 -4
- package/lib/application-control/environment-variables.js.map +1 -1
- package/lib/application-control/environment-variables.test.js +1 -1
- package/lib/application-control/environment-variables.test.js.map +1 -1
- package/lib/application-control/install.d.ts.map +1 -1
- package/lib/application-control/install.js +7 -2
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/models.d.ts +5 -0
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +28 -14
- 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 +14 -17
- package/lib/application-control/status.js.map +1 -1
- package/lib/application-control/utils.js +2 -2
- package/lib/application-control/utils.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +5 -5
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +140 -105
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts +4 -2
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +46 -25
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.test.js +132 -16
- package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
- package/lib/cloud-connection/messages.d.ts.map +1 -1
- package/lib/cloud-connection/messages.js +3 -4
- package/lib/cloud-connection/messages.js.map +1 -1
- package/lib/cloud-connection/passthrough-handler.d.ts +5 -3
- package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -1
- package/lib/cloud-connection/passthrough-handler.js +76 -62
- package/lib/cloud-connection/passthrough-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.d.ts +16 -21
- package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +162 -108
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.test.js +100 -83
- package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.d.ts +3 -0
- package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
- package/lib/cloud-connection/transaction-manager.js +11 -0
- package/lib/cloud-connection/transaction-manager.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.test.js +102 -0
- package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
- package/lib/device-control/device-control.d.ts +16 -15
- package/lib/device-control/device-control.d.ts.map +1 -1
- package/lib/device-control/device-control.js +117 -18
- package/lib/device-control/device-control.js.map +1 -1
- package/lib/docker/docker-compose.d.ts +14 -0
- package/lib/docker/docker-compose.d.ts.map +1 -0
- package/lib/docker/docker-compose.js +56 -0
- package/lib/docker/docker-compose.js.map +1 -0
- package/lib/index.js +2 -5
- package/lib/index.js.map +1 -1
- package/lib/infrastructure/agent-config.d.ts +45 -14
- package/lib/infrastructure/agent-config.d.ts.map +1 -1
- package/lib/infrastructure/agent-config.js +30 -15
- package/lib/infrastructure/agent-config.js.map +1 -1
- package/lib/infrastructure/agent-config.test.js +3 -0
- package/lib/infrastructure/agent-config.test.js.map +1 -1
- package/lib/local-connection/rabbitmq-connection.js +11 -11
- package/lib/local-connection/rabbitmq-connection.js.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.d.ts +97 -0
- package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.js +435 -0
- package/lib/secure-tunneling/secure-tunneling.js.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.test.d.ts +2 -0
- package/lib/secure-tunneling/secure-tunneling.test.d.ts.map +1 -0
- package/lib/secure-tunneling/secure-tunneling.test.js +1070 -0
- package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -0
- package/lib/secure-tunneling/spawner-detached.d.ts +6 -0
- package/lib/secure-tunneling/spawner-detached.d.ts.map +1 -0
- package/lib/secure-tunneling/spawner-detached.js +107 -0
- package/lib/secure-tunneling/spawner-detached.js.map +1 -0
- package/lib/subcommands/app/analytics.d.ts.map +1 -1
- package/lib/subcommands/app/analytics.js +9 -13
- package/lib/subcommands/app/analytics.js.map +1 -1
- package/lib/subcommands/app/env-vars.d.ts.map +1 -1
- package/lib/subcommands/app/env-vars.js +11 -16
- package/lib/subcommands/app/env-vars.js.map +1 -1
- package/lib/subcommands/app/models.d.ts.map +1 -1
- package/lib/subcommands/app/models.js +12 -16
- package/lib/subcommands/app/models.js.map +1 -1
- package/lib/subcommands/device/clean.d.ts.map +1 -1
- package/lib/subcommands/device/clean.js +8 -6
- package/lib/subcommands/device/clean.js.map +1 -1
- package/lib/subcommands/device/get-info.d.ts +2 -0
- package/lib/subcommands/device/get-info.d.ts.map +1 -0
- package/lib/subcommands/device/get-info.js +36 -0
- package/lib/subcommands/device/get-info.js.map +1 -0
- package/lib/subcommands/device/index.d.ts.map +1 -1
- package/lib/subcommands/device/index.js +11 -2
- package/lib/subcommands/device/index.js.map +1 -1
- package/lib/subcommands/device/init.d.ts +5 -0
- package/lib/subcommands/device/init.d.ts.map +1 -0
- package/lib/subcommands/device/{device.js → init.js} +5 -36
- package/lib/subcommands/device/init.js.map +1 -0
- package/lib/subcommands/device/refresh.d.ts +2 -0
- package/lib/subcommands/device/refresh.d.ts.map +1 -0
- package/lib/subcommands/device/refresh.js +24 -0
- package/lib/subcommands/device/refresh.js.map +1 -0
- package/lib/subcommands/device/restart.d.ts +2 -0
- package/lib/subcommands/device/restart.d.ts.map +1 -0
- package/lib/subcommands/device/restart.js +14 -0
- package/lib/subcommands/device/restart.js.map +1 -0
- package/lib/util/check-for-updates.d.ts +3 -0
- package/lib/util/check-for-updates.d.ts.map +1 -0
- package/lib/util/check-for-updates.js +69 -0
- package/lib/util/check-for-updates.js.map +1 -0
- package/lib/util/cloud-mode-ready.d.ts +1 -0
- package/lib/util/cloud-mode-ready.d.ts.map +1 -1
- package/lib/util/cloud-mode-ready.js +36 -1
- package/lib/util/cloud-mode-ready.js.map +1 -1
- package/lib/util/file.d.ts +7 -0
- package/lib/util/file.d.ts.map +1 -0
- package/lib/util/file.js +66 -0
- package/lib/util/file.js.map +1 -0
- package/lib/util/file.test.d.ts +2 -0
- package/lib/util/file.test.d.ts.map +1 -0
- package/lib/util/file.test.js +87 -0
- package/lib/util/file.test.js.map +1 -0
- package/package.json +8 -7
- package/readme.md +3 -3
- package/src/application-control/config.ts +1 -1
- package/src/application-control/environment-variables.test.ts +1 -1
- package/src/application-control/environment-variables.ts +9 -6
- package/src/application-control/install.ts +8 -3
- package/src/application-control/models.ts +47 -19
- package/src/application-control/status.ts +16 -14
- package/src/application-control/utils.ts +1 -1
- package/src/cloud-connection/device-agent-cloud-connection.ts +202 -148
- package/src/cloud-connection/live-updates-handler.test.ts +161 -20
- package/src/cloud-connection/live-updates-handler.ts +63 -31
- package/src/cloud-connection/messages.ts +3 -4
- package/src/cloud-connection/passthrough-handler.ts +98 -76
- package/src/cloud-connection/shadow-handler.test.ts +101 -84
- package/src/cloud-connection/shadow-handler.ts +287 -133
- package/src/cloud-connection/transaction-manager.test.ts +124 -0
- package/src/cloud-connection/transaction-manager.ts +15 -0
- package/src/device-control/device-control.ts +125 -23
- package/src/docker/docker-compose.ts +60 -0
- package/src/index.ts +2 -6
- package/src/infrastructure/agent-config.test.ts +3 -0
- package/src/infrastructure/agent-config.ts +38 -40
- package/src/local-connection/rabbitmq-connection.ts +8 -8
- package/src/secure-tunneling/secure-tunneling.test.ts +1239 -0
- package/src/secure-tunneling/secure-tunneling.ts +599 -0
- package/src/secure-tunneling/spawner-detached.ts +123 -0
- package/src/subcommands/app/analytics.ts +16 -13
- package/src/subcommands/app/env-vars.ts +18 -16
- package/src/subcommands/app/models.ts +20 -16
- package/src/subcommands/device/clean.ts +5 -2
- package/src/subcommands/device/get-info.ts +49 -0
- package/src/subcommands/device/index.ts +11 -2
- package/src/subcommands/device/{device.ts → init.ts} +5 -47
- package/src/subcommands/device/refresh.ts +22 -0
- package/src/subcommands/device/restart.ts +11 -0
- package/src/util/check-for-updates.ts +69 -0
- package/src/util/cloud-mode-ready.ts +36 -0
- package/src/util/file.test.ts +90 -0
- package/src/util/file.ts +76 -0
- package/lib/docker/docker-compose-cmd.d.ts +0 -5
- package/lib/docker/docker-compose-cmd.d.ts.map +0 -1
- package/lib/docker/docker-compose-cmd.js +0 -16
- package/lib/docker/docker-compose-cmd.js.map +0 -1
- package/lib/secure-tunneling/index.d.ts +0 -5
- package/lib/secure-tunneling/index.d.ts.map +0 -1
- package/lib/secure-tunneling/index.js +0 -64
- package/lib/secure-tunneling/index.js.map +0 -1
- package/lib/subcommands/device/device.d.ts +0 -7
- package/lib/subcommands/device/device.d.ts.map +0 -1
- package/lib/subcommands/device/device.js.map +0 -1
- package/lib/util/safe-rimraf.d.ts +0 -2
- package/lib/util/safe-rimraf.d.ts.map +0 -1
- package/lib/util/safe-rimraf.js +0 -16
- package/lib/util/safe-rimraf.js.map +0 -1
- package/src/docker/docker-compose-cmd.ts +0 -15
- package/src/secure-tunneling/index.ts +0 -74
- package/src/util/safe-rimraf.ts +0 -14
|
@@ -2,28 +2,29 @@ import {
|
|
|
2
2
|
AppConfig,
|
|
3
3
|
validateAppConfig
|
|
4
4
|
} from '@alwaysai/app-configuration-schemas';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
EnvVars,
|
|
7
|
+
getAllEnvs,
|
|
8
|
+
readAppCfgFile,
|
|
9
|
+
setEnv
|
|
10
|
+
} from '../application-control';
|
|
11
|
+
import { getSystemInformation } from '../device-control/device-control';
|
|
6
12
|
import { logger } from '../util/logger';
|
|
7
13
|
import { Publisher } from './publisher';
|
|
8
14
|
import { AppConfigModels, getAppCfgModelsDiff } from './shadow';
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
};
|
|
23
|
-
systemInfo: {
|
|
24
|
-
update: string;
|
|
25
|
-
};
|
|
26
|
-
}
|
|
15
|
+
import {
|
|
16
|
+
generateTxId,
|
|
17
|
+
validateProjectShadowUpdate,
|
|
18
|
+
buildBaseShadowMessage,
|
|
19
|
+
buildUpdateProjectShadowMessage,
|
|
20
|
+
buildUpdateSystemInfoShadowMessage,
|
|
21
|
+
getShadowTopic,
|
|
22
|
+
ShadowProjectsUpdateAll,
|
|
23
|
+
getDesiredFromMessage,
|
|
24
|
+
ProjectShadowUpdate,
|
|
25
|
+
buildUpdateSecureTunnelShadowMessage,
|
|
26
|
+
SecureTunnelShadowDescriptionReported
|
|
27
|
+
} from '@alwaysai/device-agent-schemas';
|
|
27
28
|
|
|
28
29
|
export type AppConfigUpdate = {
|
|
29
30
|
newAppCfg: AppConfig;
|
|
@@ -44,101 +45,190 @@ export type ShadowUpdate = {
|
|
|
44
45
|
export class ShadowHandler {
|
|
45
46
|
private clientId: string;
|
|
46
47
|
private publisher: Publisher;
|
|
47
|
-
public
|
|
48
|
-
public readonly shadowTopics:
|
|
49
|
-
|
|
48
|
+
public projectShadowTopics: string[] = [];
|
|
49
|
+
public readonly shadowTopics: { [key: string]: any };
|
|
50
50
|
constructor(clientId: string, publisher: Publisher) {
|
|
51
51
|
this.clientId = clientId;
|
|
52
52
|
this.publisher = publisher;
|
|
53
|
-
this.shadowPrefix = `$aws/things/${this.clientId}/shadow/name/`;
|
|
54
53
|
this.shadowTopics = {
|
|
55
|
-
|
|
56
|
-
get:
|
|
57
|
-
getAccepted:
|
|
58
|
-
getRejected:
|
|
59
|
-
update:
|
|
60
|
-
updateDelta:
|
|
61
|
-
updateAccepted:
|
|
62
|
-
updateRejected:
|
|
63
|
-
delete: `${this.shadowPrefix}projects/delete`
|
|
54
|
+
project: {
|
|
55
|
+
get: getShadowTopic(clientId, 'projects', 'get'),
|
|
56
|
+
getAccepted: getShadowTopic(clientId, 'projects', 'get/accepted'),
|
|
57
|
+
getRejected: getShadowTopic(clientId, 'projects', 'get/rejected'),
|
|
58
|
+
update: getShadowTopic(clientId, 'projects', 'update'),
|
|
59
|
+
updateDelta: getShadowTopic(clientId, 'projects', 'update/delta'),
|
|
60
|
+
updateAccepted: getShadowTopic(clientId, 'projects', 'update/accepted'),
|
|
61
|
+
updateRejected: getShadowTopic(clientId, 'projects', 'update/rejected')
|
|
64
62
|
},
|
|
65
63
|
systemInfo: {
|
|
66
|
-
update:
|
|
64
|
+
update: getShadowTopic(clientId, 'system-info', 'update')
|
|
65
|
+
},
|
|
66
|
+
secureTunnel: {
|
|
67
|
+
get: getShadowTopic(clientId, 'secure-tunnel', 'get'),
|
|
68
|
+
getAccepted: getShadowTopic(clientId, 'secure-tunnel', 'get/accepted'),
|
|
69
|
+
getRejected: getShadowTopic(clientId, 'secure-tunnel', 'get/rejected'),
|
|
70
|
+
update: getShadowTopic(clientId, 'secure-tunnel', 'update'),
|
|
71
|
+
updateDelta: getShadowTopic(clientId, 'secure-tunnel', 'update/delta'),
|
|
72
|
+
updateAccepted: getShadowTopic(
|
|
73
|
+
clientId,
|
|
74
|
+
'secure-tunnel',
|
|
75
|
+
'update/accepted'
|
|
76
|
+
),
|
|
77
|
+
updateRejected: getShadowTopic(
|
|
78
|
+
clientId,
|
|
79
|
+
'secure-tunnel',
|
|
80
|
+
'update/rejected'
|
|
81
|
+
),
|
|
82
|
+
delete: getShadowTopic(clientId, 'secure-tunnel', 'delete'),
|
|
83
|
+
deleteAccepted: getShadowTopic(
|
|
84
|
+
clientId,
|
|
85
|
+
'secure-tunnel',
|
|
86
|
+
'delete/accepted'
|
|
87
|
+
),
|
|
88
|
+
deleteRejected: getShadowTopic(
|
|
89
|
+
clientId,
|
|
90
|
+
'secure-tunnel',
|
|
91
|
+
'delete/rejected'
|
|
92
|
+
)
|
|
67
93
|
}
|
|
68
94
|
};
|
|
95
|
+
this.projectShadowTopics.push(this.shadowTopics.project.getAccepted);
|
|
96
|
+
this.projectShadowTopics.push(this.shadowTopics.project.getRejected);
|
|
97
|
+
this.projectShadowTopics.push(this.shadowTopics.project.updateDelta);
|
|
98
|
+
this.projectShadowTopics.push(this.shadowTopics.project.updateAccepted);
|
|
99
|
+
this.projectShadowTopics.push(this.shadowTopics.project.updateRejected);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private async generateAppConfigUpdate({
|
|
103
|
+
appConfig,
|
|
104
|
+
txId,
|
|
105
|
+
projectId
|
|
106
|
+
}: {
|
|
107
|
+
appConfig: string;
|
|
108
|
+
txId: string;
|
|
109
|
+
projectId: string;
|
|
110
|
+
}): Promise<AppConfigUpdate | null> {
|
|
111
|
+
let newAppCfg: any;
|
|
112
|
+
// Handle errors and validation
|
|
113
|
+
try {
|
|
114
|
+
newAppCfg = JSON.parse(appConfig);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
logger.error(
|
|
117
|
+
`Could not parse the appConfig for transaction ${txId}!\n${error}`
|
|
118
|
+
);
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!validateAppConfig(newAppCfg)) {
|
|
123
|
+
// FIXME: Raise an exception to be handled at higher layer
|
|
124
|
+
logger.error(
|
|
125
|
+
`Received invalid app config for ${projectId}!\n${JSON.stringify(
|
|
126
|
+
validateAppConfig.errors,
|
|
127
|
+
null,
|
|
128
|
+
2
|
|
129
|
+
)}`
|
|
130
|
+
);
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// If all ok, return the AppConfigUpdate
|
|
135
|
+
logger.info(`Found a delta for app config shadow. Updating ${projectId}`);
|
|
136
|
+
const { updatedModels } = await getAppCfgModelsDiff({
|
|
137
|
+
newAppCfg,
|
|
138
|
+
projectId
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const appCfgUpdate: AppConfigUpdate = { newAppCfg };
|
|
142
|
+
if (updatedModels && Object.keys(updatedModels).length) {
|
|
143
|
+
appCfgUpdate.updatedModels = updatedModels;
|
|
144
|
+
}
|
|
145
|
+
return appCfgUpdate;
|
|
69
146
|
}
|
|
70
147
|
|
|
71
|
-
private async
|
|
148
|
+
private async processProjectShadowUpdates({
|
|
72
149
|
delta
|
|
73
150
|
}: {
|
|
74
151
|
delta: any;
|
|
75
152
|
}): Promise<ShadowUpdate[]> {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
const deltaKeys = Object.keys(delta);
|
|
79
|
-
|
|
80
|
-
for (const projectId of deltaKeys) {
|
|
81
|
-
const projectShadow = delta[projectId];
|
|
82
|
-
// For incoming shadow updates, there will be no TxID, so it needs to be generated here.
|
|
83
|
-
const txId = generateTxId();
|
|
84
|
-
const shadowUpdate: ShadowUpdate = { projectId, txId };
|
|
153
|
+
const shadowUpdatesPromises: Promise<ShadowUpdate | null>[] = [];
|
|
85
154
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
} catch (error) {
|
|
155
|
+
for (const [projectId, projectDelta] of Object.entries(delta)) {
|
|
156
|
+
if (projectDelta) {
|
|
157
|
+
const valid = validateProjectShadowUpdate(projectDelta);
|
|
158
|
+
if (!valid) {
|
|
91
159
|
logger.error(
|
|
92
|
-
`
|
|
93
|
-
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
if (!validateAppConfig(newAppCfg)) {
|
|
97
|
-
// FIXME: Raise an exception to be handled at higher layer
|
|
98
|
-
logger.error(
|
|
99
|
-
`Received invalid app config for ${projectId}!\n${JSON.stringify(
|
|
100
|
-
validateAppConfig.errors,
|
|
160
|
+
`Error validating shadow update: ${JSON.stringify(
|
|
161
|
+
{ projectDelta, errors: validateProjectShadowUpdate.errors },
|
|
101
162
|
null,
|
|
102
163
|
2
|
|
103
164
|
)}`
|
|
104
165
|
);
|
|
105
166
|
continue;
|
|
106
167
|
}
|
|
107
|
-
const { updatedModels } = await getAppCfgModelsDiff({
|
|
108
|
-
newAppCfg,
|
|
109
|
-
projectId
|
|
110
|
-
});
|
|
111
168
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
} else {
|
|
118
|
-
logger.info(
|
|
119
|
-
`Ignoring app config shadow update for ${projectId} due to no delta`
|
|
169
|
+
shadowUpdatesPromises.push(
|
|
170
|
+
this.processProjectShadowUpdate({
|
|
171
|
+
projectId,
|
|
172
|
+
projectDelta
|
|
173
|
+
})
|
|
120
174
|
);
|
|
121
175
|
}
|
|
176
|
+
}
|
|
177
|
+
return (await Promise.all(shadowUpdatesPromises)).filter(
|
|
178
|
+
(promise) => promise !== null
|
|
179
|
+
) as ShadowUpdate[];
|
|
180
|
+
}
|
|
122
181
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
182
|
+
private async processProjectShadowUpdate({
|
|
183
|
+
projectId,
|
|
184
|
+
projectDelta
|
|
185
|
+
}: {
|
|
186
|
+
projectId: string;
|
|
187
|
+
projectDelta: ProjectShadowUpdate;
|
|
188
|
+
}): Promise<ShadowUpdate | null> {
|
|
189
|
+
const txId = generateTxId();
|
|
190
|
+
const shadowUpdate: ShadowUpdate = { projectId, txId };
|
|
191
|
+
|
|
192
|
+
// Handle appConfig Updates
|
|
193
|
+
if (!projectDelta.appConfig) {
|
|
194
|
+
logger.info(
|
|
195
|
+
`Ignoring app config shadow update for ${projectId} due to no delta`
|
|
196
|
+
);
|
|
197
|
+
} else {
|
|
198
|
+
logger.info(`Found a delta for app config shadow. Updating ${projectId}`);
|
|
199
|
+
const appConfig = projectDelta.appConfig;
|
|
200
|
+
const appCfgUpdate = await this.generateAppConfigUpdate({
|
|
201
|
+
appConfig,
|
|
202
|
+
txId,
|
|
203
|
+
projectId
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
if (appCfgUpdate) {
|
|
207
|
+
shadowUpdate.appCfgUpdate = appCfgUpdate;
|
|
133
208
|
}
|
|
134
209
|
}
|
|
135
210
|
|
|
136
|
-
|
|
211
|
+
// Handle envVars Updates
|
|
212
|
+
if (!projectDelta.envVars) {
|
|
213
|
+
logger.info(
|
|
214
|
+
`Ignoring app environment variable shadow update for ${projectId} due to no delta`
|
|
215
|
+
);
|
|
216
|
+
} else {
|
|
217
|
+
logger.info(
|
|
218
|
+
`Found a delta for app environment variable shadow. Updating ${projectId}`
|
|
219
|
+
);
|
|
220
|
+
const envVars = projectDelta.envVars;
|
|
221
|
+
shadowUpdate.envVarUpdate = { envVars };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return shadowUpdate.appCfgUpdate || shadowUpdate.envVarUpdate
|
|
225
|
+
? shadowUpdate
|
|
226
|
+
: null;
|
|
137
227
|
}
|
|
138
228
|
|
|
139
229
|
// Public interface
|
|
140
230
|
|
|
141
|
-
public async
|
|
231
|
+
public async handleProjectShadow({
|
|
142
232
|
topic,
|
|
143
233
|
payload,
|
|
144
234
|
clientToken
|
|
@@ -148,12 +238,14 @@ export class ShadowHandler {
|
|
|
148
238
|
clientToken: string;
|
|
149
239
|
}): Promise<ShadowUpdate[]> {
|
|
150
240
|
// TODO: make use a function like the other topic getters
|
|
151
|
-
|
|
241
|
+
if (!this.projectShadowTopics.includes(topic)) {
|
|
242
|
+
throw Error(`Topic ${topic} is not in the ${this.projectShadowTopics}`);
|
|
243
|
+
}
|
|
152
244
|
switch (topic) {
|
|
153
|
-
case this.shadowTopics.
|
|
245
|
+
case this.shadowTopics.project.updateAccepted: {
|
|
154
246
|
if (clientToken === this.clientId) {
|
|
155
247
|
logger.debug(
|
|
156
|
-
`Ignoring
|
|
248
|
+
`Ignoring message as it was caused by Device Agent itself: ${JSON.stringify(
|
|
157
249
|
{ topic, payload },
|
|
158
250
|
null,
|
|
159
251
|
2
|
|
@@ -161,19 +253,49 @@ export class ShadowHandler {
|
|
|
161
253
|
);
|
|
162
254
|
break;
|
|
163
255
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
256
|
+
const desired = getDesiredFromMessage(payload);
|
|
257
|
+
if (!desired) {
|
|
258
|
+
logger.debug(
|
|
259
|
+
`No desired state found in message: ${JSON.stringify(payload)}`
|
|
260
|
+
);
|
|
261
|
+
} else {
|
|
262
|
+
return await this.processProjectShadowUpdates({ delta: desired });
|
|
263
|
+
}
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
case this.shadowTopics.project.getAccepted: {
|
|
267
|
+
if (clientToken !== this.clientId) {
|
|
268
|
+
logger.debug(
|
|
269
|
+
`Ignoring message as it was caused by Device Agent itself: ${JSON.stringify(
|
|
270
|
+
{ topic, payload },
|
|
271
|
+
null,
|
|
272
|
+
2
|
|
273
|
+
)}`
|
|
274
|
+
);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
if (payload.state.delta) {
|
|
278
|
+
return await this.processProjectShadowUpdates({
|
|
279
|
+
delta: payload.state.delta
|
|
169
280
|
});
|
|
170
281
|
} else {
|
|
171
|
-
logger.info(
|
|
282
|
+
logger.info(
|
|
283
|
+
`No delta in projects.getAccepted in named shadow '${
|
|
284
|
+
topic.split('/')[5]
|
|
285
|
+
}'`
|
|
286
|
+
);
|
|
172
287
|
}
|
|
173
288
|
break;
|
|
289
|
+
}
|
|
290
|
+
case this.shadowTopics.project.getRejected:
|
|
291
|
+
case this.shadowTopics.project.updateDelta:
|
|
292
|
+
case this.shadowTopics.project.updateRejected: {
|
|
293
|
+
// Not handling these for now
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
174
296
|
default:
|
|
175
297
|
logger.info(
|
|
176
|
-
`Ignoring shadow message: ${JSON.stringify(
|
|
298
|
+
`Did not match a correct topic. Ignoring shadow message: ${JSON.stringify(
|
|
177
299
|
{ topic, payload },
|
|
178
300
|
null,
|
|
179
301
|
2
|
|
@@ -185,58 +307,90 @@ export class ShadowHandler {
|
|
|
185
307
|
|
|
186
308
|
public async updateSystemInfoShadow() {
|
|
187
309
|
const systemInfo = await getSystemInformation();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const topic = this.shadowTopics.systemInfo.update;
|
|
195
|
-
this.publisher.publish(topic, JSON.stringify(packet));
|
|
310
|
+
this.publisher.publish(
|
|
311
|
+
getShadowTopic(this.clientId, 'system-info', 'update'),
|
|
312
|
+
JSON.stringify(
|
|
313
|
+
buildUpdateSystemInfoShadowMessage(this.clientId, systemInfo)
|
|
314
|
+
)
|
|
315
|
+
);
|
|
196
316
|
}
|
|
197
317
|
|
|
198
318
|
public async updateProjectShadow(projectId: string) {
|
|
199
319
|
const appCfg = await readAppCfgFile({ projectId });
|
|
200
320
|
const envVars = await getAllEnvs({ projectId });
|
|
201
321
|
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
envVars
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
},
|
|
211
|
-
clientToken: this.clientId
|
|
322
|
+
const toReport: ShadowProjectsUpdateAll = {
|
|
323
|
+
[projectId]: {
|
|
324
|
+
appConfig: JSON.stringify(appCfg),
|
|
325
|
+
envVars
|
|
326
|
+
}
|
|
212
327
|
};
|
|
213
|
-
|
|
214
|
-
|
|
328
|
+
this.publisher.publish(
|
|
329
|
+
getShadowTopic(this.clientId, 'projects', 'update'),
|
|
330
|
+
JSON.stringify(
|
|
331
|
+
buildUpdateProjectShadowMessage({
|
|
332
|
+
clientId: this.clientId,
|
|
333
|
+
reported: toReport
|
|
334
|
+
})
|
|
335
|
+
)
|
|
336
|
+
);
|
|
215
337
|
}
|
|
216
338
|
|
|
217
|
-
public
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
339
|
+
public async updateProjectEnvVars({
|
|
340
|
+
projectId,
|
|
341
|
+
envVars
|
|
342
|
+
}: {
|
|
343
|
+
projectId: string;
|
|
344
|
+
envVars: EnvVars;
|
|
345
|
+
}) {
|
|
346
|
+
await setEnv({ projectId, envVars });
|
|
347
|
+
|
|
348
|
+
this.publisher.publish(
|
|
349
|
+
getShadowTopic(this.clientId, 'projects', 'update'),
|
|
350
|
+
JSON.stringify(
|
|
351
|
+
buildUpdateProjectShadowMessage({
|
|
352
|
+
clientId: this.clientId,
|
|
353
|
+
reported: {
|
|
354
|
+
[projectId]: {
|
|
355
|
+
envVars
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
})
|
|
359
|
+
)
|
|
360
|
+
);
|
|
223
361
|
}
|
|
224
362
|
|
|
225
|
-
public
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
this.publisher.publish(
|
|
363
|
+
public async updateSecureTunnelShadow(
|
|
364
|
+
secureTunnelShadowUpdate: SecureTunnelShadowDescriptionReported
|
|
365
|
+
) {
|
|
366
|
+
this.publisher.publish(
|
|
367
|
+
getShadowTopic(this.clientId, 'secure-tunnel', 'update'),
|
|
368
|
+
JSON.stringify(
|
|
369
|
+
buildUpdateSecureTunnelShadowMessage(
|
|
370
|
+
secureTunnelShadowUpdate,
|
|
371
|
+
this.clientId
|
|
372
|
+
)
|
|
373
|
+
)
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
public getProjectShadowUpdates() {
|
|
378
|
+
this.publisher.publish(
|
|
379
|
+
getShadowTopic(this.clientId, 'projects', 'get'),
|
|
380
|
+
JSON.stringify(buildBaseShadowMessage(this.clientId))
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
public clearProjectShadow(projectId: string) {
|
|
385
|
+
this.publisher.publish(
|
|
386
|
+
getShadowTopic(this.clientId, 'projects', 'update'),
|
|
387
|
+
JSON.stringify(
|
|
388
|
+
buildUpdateProjectShadowMessage({
|
|
389
|
+
clientId: this.clientId,
|
|
390
|
+
reported: { [projectId]: null },
|
|
391
|
+
desired: { [projectId]: null }
|
|
392
|
+
})
|
|
393
|
+
)
|
|
394
|
+
);
|
|
241
395
|
}
|
|
242
396
|
}
|
|
@@ -7,6 +7,7 @@ import { TransactionManager } from './transaction-manager';
|
|
|
7
7
|
import { v4 as uuidv4 } from 'uuid';
|
|
8
8
|
import { Publisher } from './publisher';
|
|
9
9
|
import { LiveUpdatesHandler } from './live-updates-handler';
|
|
10
|
+
import { AppConfigUpdate, ShadowUpdate } from './shadow-handler';
|
|
10
11
|
|
|
11
12
|
const mockClient = {
|
|
12
13
|
publish: jest.fn()
|
|
@@ -245,4 +246,127 @@ describe('Test Transaction Manager', () => {
|
|
|
245
246
|
|
|
246
247
|
expect(txnMgr.getProjectFromTransaction(txId)).toBeUndefined();
|
|
247
248
|
});
|
|
249
|
+
|
|
250
|
+
test('add an appCfgUpdate from txDetails', async () => {
|
|
251
|
+
const txId = generateTxId();
|
|
252
|
+
const projectId = generateRandomProjectId();
|
|
253
|
+
const cfgUpdate: ShadowUpdate = { txId: txId, projectId: projectId };
|
|
254
|
+
|
|
255
|
+
await txnMgr.runTransactionStep({
|
|
256
|
+
func: func_incomplete,
|
|
257
|
+
projectId,
|
|
258
|
+
txId,
|
|
259
|
+
start: true
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
expect(txnMgr.getAppCfgUpdateFromTxID(txId)).toEqual(undefined);
|
|
263
|
+
|
|
264
|
+
txnMgr.setAppCfgUpdateToTx(txId, cfgUpdate);
|
|
265
|
+
|
|
266
|
+
expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
|
|
267
|
+
expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
|
|
268
|
+
expect(txnMgr.isOngoingTransaction(txId)).toBe(true);
|
|
269
|
+
expect(txnMgr.isOngoingTransactionForProjectID(projectId)).toBe(true);
|
|
270
|
+
expect(txnMgr.isAnyOngoingTransaction()).toBe(true);
|
|
271
|
+
expect(txnMgr.getAppCfgUpdateFromTxID(txId)).toEqual(cfgUpdate);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('update a tx while holding appCfgUpdate', async () => {
|
|
275
|
+
const txId = generateTxId();
|
|
276
|
+
const projectId = generateRandomProjectId();
|
|
277
|
+
const cfgUpdate: ShadowUpdate = { txId: txId, projectId: projectId };
|
|
278
|
+
|
|
279
|
+
await txnMgr.runTransactionStep({
|
|
280
|
+
func: func_incomplete,
|
|
281
|
+
projectId,
|
|
282
|
+
txId,
|
|
283
|
+
start: true
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
txnMgr.setAppCfgUpdateToTx(txId, cfgUpdate);
|
|
287
|
+
|
|
288
|
+
await txnMgr.runTransactionStep({
|
|
289
|
+
func: func_incomplete,
|
|
290
|
+
projectId,
|
|
291
|
+
txId,
|
|
292
|
+
start: false,
|
|
293
|
+
stepName: 'step2'
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
|
|
297
|
+
expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
|
|
298
|
+
expect(txnMgr.isOngoingTransaction(txId)).toBe(true);
|
|
299
|
+
expect(txnMgr.isOngoingTransactionForProjectID(projectId)).toBe(true);
|
|
300
|
+
expect(txnMgr.isAnyOngoingTransaction()).toBe(true);
|
|
301
|
+
expect(txnMgr.getAppCfgUpdateFromTxID(txId)).toEqual(cfgUpdate);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('update an appCfgUpdate to the same tx ', async () => {
|
|
305
|
+
const txId = generateTxId();
|
|
306
|
+
const projectId = generateRandomProjectId();
|
|
307
|
+
const cfgUpdate: ShadowUpdate = { txId: txId, projectId: projectId };
|
|
308
|
+
|
|
309
|
+
const ogAppCfg1: AppConfigUpdate = {
|
|
310
|
+
newAppCfg: {
|
|
311
|
+
scripts: {
|
|
312
|
+
start: 'python app.py'
|
|
313
|
+
},
|
|
314
|
+
models: {}
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const cfgUpdate2: ShadowUpdate = {
|
|
319
|
+
txId: txId,
|
|
320
|
+
projectId: projectId,
|
|
321
|
+
appCfgUpdate: ogAppCfg1
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
await txnMgr.runTransactionStep({
|
|
325
|
+
func: func_incomplete,
|
|
326
|
+
projectId,
|
|
327
|
+
txId,
|
|
328
|
+
start: true
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
txnMgr.setAppCfgUpdateToTx(txId, cfgUpdate);
|
|
332
|
+
|
|
333
|
+
await txnMgr.runTransactionStep({
|
|
334
|
+
func: func_incomplete,
|
|
335
|
+
projectId,
|
|
336
|
+
txId,
|
|
337
|
+
start: false,
|
|
338
|
+
stepName: 'step2'
|
|
339
|
+
});
|
|
340
|
+
txnMgr.setAppCfgUpdateToTx(txId, cfgUpdate2);
|
|
341
|
+
|
|
342
|
+
expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
|
|
343
|
+
expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
|
|
344
|
+
expect(txnMgr.isOngoingTransaction(txId)).toBe(true);
|
|
345
|
+
expect(txnMgr.isOngoingTransactionForProjectID(projectId)).toBe(true);
|
|
346
|
+
expect(txnMgr.isAnyOngoingTransaction()).toBe(true);
|
|
347
|
+
expect(txnMgr.getAppCfgUpdateFromTxID(txId)).toEqual(cfgUpdate2);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test('remove a tx, ensure appCfgUpdate is removed', async () => {
|
|
351
|
+
const txId = generateTxId();
|
|
352
|
+
const projectId = generateRandomProjectId();
|
|
353
|
+
const cfgUpdate: ShadowUpdate = { txId: txId, projectId: projectId };
|
|
354
|
+
|
|
355
|
+
await txnMgr.runTransactionStep({
|
|
356
|
+
func: func_incomplete,
|
|
357
|
+
projectId,
|
|
358
|
+
txId,
|
|
359
|
+
start: true
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
txnMgr.setAppCfgUpdateToTx(txId, cfgUpdate);
|
|
363
|
+
txnMgr.completeTransaction(txId);
|
|
364
|
+
|
|
365
|
+
expect(txnMgr.getProjectFromTransaction(txId)).toEqual(undefined);
|
|
366
|
+
expect(txnMgr.getTransactionFromProject(projectId)).toEqual(undefined);
|
|
367
|
+
expect(txnMgr.isOngoingTransaction(txId)).toBe(false);
|
|
368
|
+
expect(txnMgr.isOngoingTransactionForProjectID(projectId)).toBe(false);
|
|
369
|
+
expect(txnMgr.isAnyOngoingTransaction()).toBe(false);
|
|
370
|
+
expect(txnMgr.getAppCfgUpdateFromTxID(txId)).toEqual(undefined);
|
|
371
|
+
});
|
|
248
372
|
});
|
|
@@ -8,6 +8,7 @@ import { Publisher } from './publisher';
|
|
|
8
8
|
import { logger } from '../util/logger';
|
|
9
9
|
import { keyMirror } from 'alwaysai/lib/util';
|
|
10
10
|
import { CodedError } from '@carnesen/coded-error';
|
|
11
|
+
import { ShadowUpdate } from './shadow-handler';
|
|
11
12
|
|
|
12
13
|
interface TransactionDetails {
|
|
13
14
|
txId: string;
|
|
@@ -16,6 +17,7 @@ interface TransactionDetails {
|
|
|
16
17
|
start: string;
|
|
17
18
|
update?: string;
|
|
18
19
|
stop?: string;
|
|
20
|
+
appCfgUpdate?: ShadowUpdate;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export class TransactionManager {
|
|
@@ -169,6 +171,19 @@ export class TransactionManager {
|
|
|
169
171
|
return txnDetails?.projectId;
|
|
170
172
|
}
|
|
171
173
|
|
|
174
|
+
public getAppCfgUpdateFromTxID(txId: string): ShadowUpdate | undefined {
|
|
175
|
+
return this.detailsByTx[txId]?.appCfgUpdate;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
public setAppCfgUpdateToTx(txId: string, appCfgUpdate: ShadowUpdate) {
|
|
179
|
+
if (this.isOngoingTransaction(txId)) {
|
|
180
|
+
this.detailsByTx[txId].appCfgUpdate = appCfgUpdate;
|
|
181
|
+
} else
|
|
182
|
+
throw new Error(
|
|
183
|
+
`Could not set appCfgUpdate, the transaction ${txId} does not exist.`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
172
187
|
public completeTransaction(txId: string): void {
|
|
173
188
|
const txDetails = this.detailsByTx[txId];
|
|
174
189
|
if (txDetails === undefined) {
|