@alwaysai/device-agent 1.3.1 → 1.4.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/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 +6 -2
- package/lib/application-control/install.js.map +1 -1
- package/lib/application-control/models.d.ts.map +1 -1
- package/lib/application-control/models.js +4 -2
- package/lib/application-control/models.js.map +1 -1
- package/lib/application-control/status.js +4 -5
- package/lib/application-control/status.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +3 -3
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +114 -99
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts +1 -0
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +22 -4
- package/lib/cloud-connection/live-updates-handler.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/shadow-handler.d.ts +14 -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/device-control/device-control.d.ts +7 -14
- package/lib/device-control/device-control.d.ts.map +1 -1
- package/lib/device-control/device-control.js +37 -14
- package/lib/device-control/device-control.js.map +1 -1
- package/lib/secure-tunneling/secure-tunneling.d.ts +105 -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 +3 -1
- package/lib/subcommands/device/clean.js.map +1 -1
- package/lib/subcommands/device/device.d.ts.map +1 -1
- package/lib/subcommands/device/device.js +14 -6
- package/lib/subcommands/device/device.js.map +1 -1
- 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/package.json +2 -2
- 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 +7 -3
- package/src/application-control/models.ts +11 -6
- package/src/application-control/status.ts +8 -8
- package/src/cloud-connection/device-agent-cloud-connection.ts +161 -131
- package/src/cloud-connection/live-updates-handler.ts +34 -6
- package/src/cloud-connection/messages.ts +3 -4
- package/src/cloud-connection/shadow-handler.test.ts +101 -84
- package/src/cloud-connection/shadow-handler.ts +275 -133
- package/src/device-control/device-control.ts +46 -19
- package/src/secure-tunneling/secure-tunneling.test.ts +1239 -0
- package/src/secure-tunneling/secure-tunneling.ts +606 -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 +4 -1
- package/src/subcommands/device/device.ts +26 -10
- package/src/util/cloud-mode-ready.ts +36 -0
- 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/src/secure-tunneling/index.ts +0 -74
|
@@ -2,28 +2,28 @@ 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
|
-
delete: string;
|
|
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
|
+
ShadowTopics,
|
|
25
|
+
ProjectShadowUpdate
|
|
26
|
+
} from '@alwaysai/device-agent-schemas';
|
|
27
27
|
|
|
28
28
|
export type AppConfigUpdate = {
|
|
29
29
|
newAppCfg: AppConfig;
|
|
@@ -44,101 +44,193 @@ export type ShadowUpdate = {
|
|
|
44
44
|
export class ShadowHandler {
|
|
45
45
|
private clientId: string;
|
|
46
46
|
private publisher: Publisher;
|
|
47
|
-
public
|
|
48
|
-
public readonly shadowTopics:
|
|
49
|
-
|
|
47
|
+
public projectShadowTopics: string[] = [];
|
|
48
|
+
public readonly shadowTopics: { [key: string]: any };
|
|
50
49
|
constructor(clientId: string, publisher: Publisher) {
|
|
51
50
|
this.clientId = clientId;
|
|
52
51
|
this.publisher = publisher;
|
|
53
|
-
this.shadowPrefix = `$aws/things/${this.clientId}/shadow/name/`;
|
|
54
52
|
this.shadowTopics = {
|
|
55
|
-
|
|
56
|
-
get:
|
|
57
|
-
getAccepted:
|
|
58
|
-
getRejected:
|
|
59
|
-
update:
|
|
60
|
-
updateDelta:
|
|
61
|
-
updateAccepted:
|
|
62
|
-
updateRejected:
|
|
63
|
-
delete: `${this.shadowPrefix}projects/delete`
|
|
53
|
+
project: {
|
|
54
|
+
get: getShadowTopic(clientId, 'projects', 'get'),
|
|
55
|
+
getAccepted: getShadowTopic(clientId, 'projects', 'get/accepted'),
|
|
56
|
+
getRejected: getShadowTopic(clientId, 'projects', 'get/rejected'),
|
|
57
|
+
update: getShadowTopic(clientId, 'projects', 'update'),
|
|
58
|
+
updateDelta: getShadowTopic(clientId, 'projects', 'update/delta'),
|
|
59
|
+
updateAccepted: getShadowTopic(clientId, 'projects', 'update/accepted'),
|
|
60
|
+
updateRejected: getShadowTopic(clientId, 'projects', 'update/rejected')
|
|
64
61
|
},
|
|
65
62
|
systemInfo: {
|
|
66
|
-
update:
|
|
63
|
+
update: getShadowTopic(clientId, 'system-info', 'update')
|
|
64
|
+
},
|
|
65
|
+
secureTunnel: {
|
|
66
|
+
get: getShadowTopic(clientId, 'secure-tunnel', 'get'),
|
|
67
|
+
getAccepted: getShadowTopic(clientId, 'secure-tunnel', 'get/accepted'),
|
|
68
|
+
getRejected: getShadowTopic(clientId, 'secure-tunnel', 'get/rejected'),
|
|
69
|
+
update: getShadowTopic(clientId, 'secure-tunnel', 'update'),
|
|
70
|
+
updateDelta: getShadowTopic(clientId, 'secure-tunnel', 'update/delta'),
|
|
71
|
+
updateAccepted: getShadowTopic(
|
|
72
|
+
clientId,
|
|
73
|
+
'secure-tunnel',
|
|
74
|
+
'update/accepted'
|
|
75
|
+
),
|
|
76
|
+
updateRejected: getShadowTopic(
|
|
77
|
+
clientId,
|
|
78
|
+
'secure-tunnel',
|
|
79
|
+
'update/rejected'
|
|
80
|
+
),
|
|
81
|
+
delete: getShadowTopic(clientId, 'secure-tunnel', 'delete'),
|
|
82
|
+
deleteAccepted: getShadowTopic(
|
|
83
|
+
clientId,
|
|
84
|
+
'secure-tunnel',
|
|
85
|
+
'delete/accepted'
|
|
86
|
+
),
|
|
87
|
+
deleteRejected: getShadowTopic(
|
|
88
|
+
clientId,
|
|
89
|
+
'secure-tunnel',
|
|
90
|
+
'delete/rejected'
|
|
91
|
+
)
|
|
67
92
|
}
|
|
68
93
|
};
|
|
94
|
+
this.projectShadowTopics.push(this.shadowTopics.project.getAccepted);
|
|
95
|
+
this.projectShadowTopics.push(this.shadowTopics.project.getRejected);
|
|
96
|
+
this.projectShadowTopics.push(this.shadowTopics.project.updateDelta);
|
|
97
|
+
this.projectShadowTopics.push(this.shadowTopics.project.updateAccepted);
|
|
98
|
+
this.projectShadowTopics.push(this.shadowTopics.project.updateRejected);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private async generateAppConfigUpdate({
|
|
102
|
+
appConfig,
|
|
103
|
+
txId,
|
|
104
|
+
projectId
|
|
105
|
+
}: {
|
|
106
|
+
appConfig: string;
|
|
107
|
+
txId: string;
|
|
108
|
+
projectId: string;
|
|
109
|
+
}): Promise<AppConfigUpdate | null> {
|
|
110
|
+
let appCfgUpdate: any;
|
|
111
|
+
let newAppCfg: any;
|
|
112
|
+
|
|
113
|
+
// Handle errors and validation
|
|
114
|
+
try {
|
|
115
|
+
newAppCfg = JSON.parse(appConfig);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
logger.error(
|
|
118
|
+
`Could not parse the appConfig for transaction ${txId}!\n${error}`
|
|
119
|
+
);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!validateAppConfig(newAppCfg)) {
|
|
124
|
+
// FIXME: Raise an exception to be handled at higher layer
|
|
125
|
+
logger.error(
|
|
126
|
+
`Received invalid app config for ${projectId}!\n${JSON.stringify(
|
|
127
|
+
validateAppConfig.errors,
|
|
128
|
+
null,
|
|
129
|
+
2
|
|
130
|
+
)}`
|
|
131
|
+
);
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// If all ok, return the AppConfigUpdate
|
|
136
|
+
logger.info(`Found a delta for app config shadow. Updating ${projectId}`);
|
|
137
|
+
const { updatedModels } = await getAppCfgModelsDiff({
|
|
138
|
+
newAppCfg,
|
|
139
|
+
projectId
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (updatedModels && Object.keys(updatedModels).length) {
|
|
143
|
+
appCfgUpdate = { newAppCfg, updatedModels };
|
|
144
|
+
} else {
|
|
145
|
+
appCfgUpdate = { newAppCfg };
|
|
146
|
+
}
|
|
147
|
+
return appCfgUpdate;
|
|
69
148
|
}
|
|
70
149
|
|
|
71
|
-
private async
|
|
150
|
+
private async processProjectShadowUpdates({
|
|
72
151
|
delta
|
|
73
152
|
}: {
|
|
74
153
|
delta: any;
|
|
75
154
|
}): Promise<ShadowUpdate[]> {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
const deltaKeys = Object.keys(delta);
|
|
155
|
+
const shadowUpdatesPromises: Promise<ShadowUpdate | null>[] = [];
|
|
79
156
|
|
|
80
|
-
for (const projectId of
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const shadowUpdate: ShadowUpdate = { projectId, txId };
|
|
85
|
-
|
|
86
|
-
if (projectShadow.appConfig) {
|
|
87
|
-
let newAppCfg: any;
|
|
88
|
-
try {
|
|
89
|
-
newAppCfg = JSON.parse(projectShadow.appConfig);
|
|
90
|
-
} catch (error) {
|
|
91
|
-
logger.error(
|
|
92
|
-
`Could not parse the appConfig for transaction ${txId}!\n${error}`
|
|
93
|
-
);
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
if (!validateAppConfig(newAppCfg)) {
|
|
97
|
-
// FIXME: Raise an exception to be handled at higher layer
|
|
157
|
+
for (const [projectId, projectDelta] of Object.entries(delta)) {
|
|
158
|
+
if (projectDelta) {
|
|
159
|
+
const valid = validateProjectShadowUpdate(projectDelta);
|
|
160
|
+
if (!valid) {
|
|
98
161
|
logger.error(
|
|
99
|
-
`
|
|
100
|
-
|
|
162
|
+
`Error validating shadow update: ${JSON.stringify(
|
|
163
|
+
{ projectDelta, errors: validateProjectShadowUpdate.errors },
|
|
101
164
|
null,
|
|
102
165
|
2
|
|
103
166
|
)}`
|
|
104
167
|
);
|
|
105
168
|
continue;
|
|
106
169
|
}
|
|
107
|
-
const { updatedModels } = await getAppCfgModelsDiff({
|
|
108
|
-
newAppCfg,
|
|
109
|
-
projectId
|
|
110
|
-
});
|
|
111
170
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
} else {
|
|
118
|
-
logger.info(
|
|
119
|
-
`Ignoring app config shadow update for ${projectId} due to no delta`
|
|
171
|
+
shadowUpdatesPromises.push(
|
|
172
|
+
this.processProjectShadowUpdate({
|
|
173
|
+
projectId,
|
|
174
|
+
projectDelta
|
|
175
|
+
})
|
|
120
176
|
);
|
|
121
177
|
}
|
|
178
|
+
}
|
|
179
|
+
return (await Promise.all(shadowUpdatesPromises)).filter(
|
|
180
|
+
(promise) => promise !== null
|
|
181
|
+
) as ShadowUpdate[];
|
|
182
|
+
}
|
|
122
183
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
184
|
+
private async processProjectShadowUpdate({
|
|
185
|
+
projectId,
|
|
186
|
+
projectDelta
|
|
187
|
+
}: {
|
|
188
|
+
projectId: string;
|
|
189
|
+
projectDelta: ProjectShadowUpdate;
|
|
190
|
+
}): Promise<ShadowUpdate | null> {
|
|
191
|
+
const txId = generateTxId();
|
|
192
|
+
const shadowUpdate: ShadowUpdate = { projectId, txId };
|
|
193
|
+
|
|
194
|
+
// Handle appConfig Updates
|
|
195
|
+
if (!projectDelta.appConfig) {
|
|
196
|
+
logger.info(
|
|
197
|
+
`Ignoring app config shadow update for ${projectId} due to no delta`
|
|
198
|
+
);
|
|
199
|
+
} else {
|
|
200
|
+
logger.info(`Found a delta for app config shadow. Updating ${projectId}`);
|
|
201
|
+
const appConfig = projectDelta.appConfig;
|
|
202
|
+
const appCfgUpdate = await this.generateAppConfigUpdate({
|
|
203
|
+
appConfig,
|
|
204
|
+
txId,
|
|
205
|
+
projectId
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
if (appCfgUpdate) {
|
|
209
|
+
shadowUpdate.appCfgUpdate = appCfgUpdate;
|
|
133
210
|
}
|
|
134
211
|
}
|
|
135
212
|
|
|
136
|
-
|
|
213
|
+
// Handle envVars Updates
|
|
214
|
+
if (!projectDelta.envVars) {
|
|
215
|
+
logger.info(
|
|
216
|
+
`Ignoring app environment variable shadow update for ${projectId} due to no delta`
|
|
217
|
+
);
|
|
218
|
+
} else {
|
|
219
|
+
logger.info(
|
|
220
|
+
`Found a delta for app environment variable shadow. Updating ${projectId}`
|
|
221
|
+
);
|
|
222
|
+
const envVars = projectDelta.envVars;
|
|
223
|
+
shadowUpdate.envVarUpdate = { envVars };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return shadowUpdate.appCfgUpdate || shadowUpdate.envVarUpdate
|
|
227
|
+
? shadowUpdate
|
|
228
|
+
: null;
|
|
137
229
|
}
|
|
138
230
|
|
|
139
231
|
// Public interface
|
|
140
232
|
|
|
141
|
-
public async
|
|
233
|
+
public async handleProjectShadow({
|
|
142
234
|
topic,
|
|
143
235
|
payload,
|
|
144
236
|
clientToken
|
|
@@ -148,12 +240,35 @@ export class ShadowHandler {
|
|
|
148
240
|
clientToken: string;
|
|
149
241
|
}): Promise<ShadowUpdate[]> {
|
|
150
242
|
// TODO: make use a function like the other topic getters
|
|
151
|
-
|
|
243
|
+
if (!this.projectShadowTopics.includes(topic)) {
|
|
244
|
+
throw Error(`Topic ${topic} is not in the ${this.projectShadowTopics}`);
|
|
245
|
+
}
|
|
152
246
|
switch (topic) {
|
|
153
|
-
case this.shadowTopics.
|
|
247
|
+
case this.shadowTopics.project.updateAccepted: {
|
|
154
248
|
if (clientToken === this.clientId) {
|
|
155
249
|
logger.debug(
|
|
156
|
-
`Ignoring
|
|
250
|
+
`Ignoring message as it was caused by Device Agent itself: ${JSON.stringify(
|
|
251
|
+
{ topic, payload },
|
|
252
|
+
null,
|
|
253
|
+
2
|
|
254
|
+
)}`
|
|
255
|
+
);
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
const desired = getDesiredFromMessage(payload);
|
|
259
|
+
if (!desired) {
|
|
260
|
+
logger.debug(
|
|
261
|
+
`No desired state found in message: ${JSON.stringify(payload)}`
|
|
262
|
+
);
|
|
263
|
+
} else {
|
|
264
|
+
return await this.processProjectShadowUpdates({ delta: desired });
|
|
265
|
+
}
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
case this.shadowTopics.project.getAccepted: {
|
|
269
|
+
if (clientToken !== this.clientId) {
|
|
270
|
+
logger.debug(
|
|
271
|
+
`Ignoring message as it was caused by Device Agent itself: ${JSON.stringify(
|
|
157
272
|
{ topic, payload },
|
|
158
273
|
null,
|
|
159
274
|
2
|
|
@@ -161,19 +276,28 @@ export class ShadowHandler {
|
|
|
161
276
|
);
|
|
162
277
|
break;
|
|
163
278
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return await this.handleNamedShadowUpdate({
|
|
168
|
-
delta: payload['delta']
|
|
279
|
+
if (payload.state.delta) {
|
|
280
|
+
return await this.processProjectShadowUpdates({
|
|
281
|
+
delta: payload.state.delta
|
|
169
282
|
});
|
|
170
283
|
} else {
|
|
171
|
-
logger.info(
|
|
284
|
+
logger.info(
|
|
285
|
+
`No delta in projects.getAccepted in named shadow '${
|
|
286
|
+
topic.split('/')[5]
|
|
287
|
+
}'`
|
|
288
|
+
);
|
|
172
289
|
}
|
|
173
290
|
break;
|
|
291
|
+
}
|
|
292
|
+
case this.shadowTopics.project.getRejected:
|
|
293
|
+
case this.shadowTopics.project.updateDelta:
|
|
294
|
+
case this.shadowTopics.project.updateRejected: {
|
|
295
|
+
// Not handling these for now
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
174
298
|
default:
|
|
175
299
|
logger.info(
|
|
176
|
-
`Ignoring shadow message: ${JSON.stringify(
|
|
300
|
+
`Did not match a correct topic. Ignoring shadow message: ${JSON.stringify(
|
|
177
301
|
{ topic, payload },
|
|
178
302
|
null,
|
|
179
303
|
2
|
|
@@ -185,58 +309,76 @@ export class ShadowHandler {
|
|
|
185
309
|
|
|
186
310
|
public async updateSystemInfoShadow() {
|
|
187
311
|
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));
|
|
312
|
+
this.publisher.publish(
|
|
313
|
+
getShadowTopic(this.clientId, 'system-info', 'update'),
|
|
314
|
+
JSON.stringify(
|
|
315
|
+
buildUpdateSystemInfoShadowMessage(this.clientId, systemInfo)
|
|
316
|
+
)
|
|
317
|
+
);
|
|
196
318
|
}
|
|
197
319
|
|
|
198
320
|
public async updateProjectShadow(projectId: string) {
|
|
199
321
|
const appCfg = await readAppCfgFile({ projectId });
|
|
200
322
|
const envVars = await getAllEnvs({ projectId });
|
|
201
323
|
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
envVars
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
},
|
|
211
|
-
clientToken: this.clientId
|
|
324
|
+
const toReport: ShadowProjectsUpdateAll = {
|
|
325
|
+
[projectId]: {
|
|
326
|
+
appConfig: JSON.stringify(appCfg),
|
|
327
|
+
envVars
|
|
328
|
+
}
|
|
212
329
|
};
|
|
213
|
-
|
|
214
|
-
|
|
330
|
+
this.publisher.publish(
|
|
331
|
+
getShadowTopic(this.clientId, 'projects', 'update'),
|
|
332
|
+
JSON.stringify(
|
|
333
|
+
buildUpdateProjectShadowMessage({
|
|
334
|
+
clientId: this.clientId,
|
|
335
|
+
reported: toReport
|
|
336
|
+
})
|
|
337
|
+
)
|
|
338
|
+
);
|
|
215
339
|
}
|
|
216
340
|
|
|
217
|
-
public
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
341
|
+
public async updateProjectEnvVars({
|
|
342
|
+
projectId,
|
|
343
|
+
envVars
|
|
344
|
+
}: {
|
|
345
|
+
projectId: string;
|
|
346
|
+
envVars: EnvVars;
|
|
347
|
+
}) {
|
|
348
|
+
await setEnv({ projectId, envVars });
|
|
349
|
+
|
|
350
|
+
this.publisher.publish(
|
|
351
|
+
getShadowTopic(this.clientId, 'projects', 'update'),
|
|
352
|
+
JSON.stringify(
|
|
353
|
+
buildUpdateProjectShadowMessage({
|
|
354
|
+
clientId: this.clientId,
|
|
355
|
+
reported: {
|
|
356
|
+
[projectId]: {
|
|
357
|
+
envVars
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
})
|
|
361
|
+
)
|
|
362
|
+
);
|
|
223
363
|
}
|
|
224
364
|
|
|
225
|
-
public
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
365
|
+
public getProjectShadowUpdates() {
|
|
366
|
+
this.publisher.publish(
|
|
367
|
+
getShadowTopic(this.clientId, 'projects', 'get'),
|
|
368
|
+
JSON.stringify(buildBaseShadowMessage(this.clientId))
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
public clearProjectShadow(projectId: string) {
|
|
373
|
+
this.publisher.publish(
|
|
374
|
+
getShadowTopic(this.clientId, 'projects', 'update'),
|
|
375
|
+
JSON.stringify(
|
|
376
|
+
buildUpdateProjectShadowMessage({
|
|
377
|
+
clientId: this.clientId,
|
|
378
|
+
reported: { [projectId]: null },
|
|
379
|
+
desired: { [projectId]: null }
|
|
380
|
+
})
|
|
381
|
+
)
|
|
382
|
+
);
|
|
241
383
|
}
|
|
242
384
|
}
|
|
@@ -1,38 +1,48 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SystemInformationShadowUpdate } from '@alwaysai/device-agent-schemas';
|
|
2
2
|
import { logger } from '../util/logger';
|
|
3
3
|
import * as osu from 'node-os-utils';
|
|
4
4
|
import * as si from 'systeminformation';
|
|
5
5
|
import { exec } from 'child_process';
|
|
6
6
|
import { promisify } from 'util';
|
|
7
|
+
import { DeviceStatsPayload } from '@alwaysai/device-agent-schemas';
|
|
7
8
|
|
|
8
9
|
const exec_promise = promisify(exec);
|
|
9
10
|
|
|
10
11
|
// Device Stats
|
|
11
|
-
export async function getCpuDetails()
|
|
12
|
+
export async function getCpuDetails(): Promise<
|
|
13
|
+
DeviceStatsPayload['cpuDetails']
|
|
14
|
+
> {
|
|
12
15
|
const cpuFree = await osu.cpu.free();
|
|
13
16
|
const cpuTemp = await si.cpuTemperature();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
const cpuDetails: DeviceStatsPayload['cpuDetails'] = {};
|
|
18
|
+
if (cpuFree !== null) cpuDetails.usedPerc = 100 - cpuFree;
|
|
19
|
+
if (cpuTemp?.main !== null) cpuDetails.temperature = cpuTemp.main;
|
|
20
|
+
return cpuDetails;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
export async function getDiskDetails()
|
|
23
|
+
export async function getDiskDetails(): Promise<
|
|
24
|
+
DeviceStatsPayload['diskDetails']
|
|
25
|
+
> {
|
|
21
26
|
// Types incorrectly specify diskname as required instead of optional
|
|
22
27
|
// @ts-expect-error
|
|
23
28
|
const driveInfo = await osu.drive.info();
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
const diskDetails: DeviceStatsPayload['diskDetails'] = {};
|
|
30
|
+
if (driveInfo?.usedGb !== null)
|
|
31
|
+
diskDetails.usedGb = parseFloat(driveInfo.usedGb);
|
|
32
|
+
if (driveInfo?.freeGb !== null) {
|
|
33
|
+
diskDetails.freeGb = parseFloat(driveInfo.freeGb);
|
|
34
|
+
}
|
|
35
|
+
return diskDetails;
|
|
28
36
|
}
|
|
29
37
|
|
|
30
|
-
export async function getMemDetails()
|
|
38
|
+
export async function getMemDetails(): Promise<
|
|
39
|
+
DeviceStatsPayload['memDetails']
|
|
40
|
+
> {
|
|
31
41
|
const memInfo = await osu.mem.info();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
const memDetails: DeviceStatsPayload['memDetails'] = {};
|
|
43
|
+
if (memInfo?.usedMemMb !== null) memDetails.usedMb = memInfo.usedMemMb;
|
|
44
|
+
if (memInfo?.freeMemMb !== null) memDetails.freeMb = memInfo.freeMemMb;
|
|
45
|
+
return memDetails;
|
|
36
46
|
}
|
|
37
47
|
|
|
38
48
|
// System information
|
|
@@ -113,15 +123,32 @@ export async function getPackageVersions() {
|
|
|
113
123
|
};
|
|
114
124
|
}
|
|
115
125
|
|
|
116
|
-
export async function
|
|
126
|
+
export async function getLastBootTime() {
|
|
127
|
+
try {
|
|
128
|
+
const test = await exec_promise(
|
|
129
|
+
"last -x reboot | grep reboot | head -n 1 | awk '{ print $5, $6, $7, $8 }'"
|
|
130
|
+
);
|
|
131
|
+
if (test.stderr) {
|
|
132
|
+
logger.error(`Stderr when getting last boot time: ${test.stderr}`);
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
return new Date(test.stdout).toString();
|
|
136
|
+
} catch (e) {
|
|
137
|
+
logger.error(`Issue getting last boot time: ${e.message}`);
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function getSystemInformation(): Promise<SystemInformationShadowUpdate> {
|
|
117
143
|
try {
|
|
118
|
-
const systemInfo:
|
|
144
|
+
const systemInfo: SystemInformationShadowUpdate = {
|
|
119
145
|
os: await getOsInfo(),
|
|
120
146
|
cpu: await getCpuInfo(),
|
|
121
147
|
disk: await getDiskInfo(),
|
|
122
148
|
device: await getDeviceInfo(),
|
|
123
149
|
network: await getNetworkInfo(),
|
|
124
|
-
versions: await getPackageVersions()
|
|
150
|
+
versions: await getPackageVersions(),
|
|
151
|
+
lastBootTime: await getLastBootTime()
|
|
125
152
|
};
|
|
126
153
|
return systemInfo;
|
|
127
154
|
} catch (e) {
|