@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.
Files changed (183) hide show
  1. package/lib/application-control/config.js +2 -2
  2. package/lib/application-control/config.js.map +1 -1
  3. package/lib/application-control/environment-variables.d.ts.map +1 -1
  4. package/lib/application-control/environment-variables.js +9 -4
  5. package/lib/application-control/environment-variables.js.map +1 -1
  6. package/lib/application-control/environment-variables.test.js +1 -1
  7. package/lib/application-control/environment-variables.test.js.map +1 -1
  8. package/lib/application-control/install.d.ts.map +1 -1
  9. package/lib/application-control/install.js +7 -2
  10. package/lib/application-control/install.js.map +1 -1
  11. package/lib/application-control/models.d.ts +5 -0
  12. package/lib/application-control/models.d.ts.map +1 -1
  13. package/lib/application-control/models.js +28 -14
  14. package/lib/application-control/models.js.map +1 -1
  15. package/lib/application-control/status.d.ts.map +1 -1
  16. package/lib/application-control/status.js +14 -17
  17. package/lib/application-control/status.js.map +1 -1
  18. package/lib/application-control/utils.js +2 -2
  19. package/lib/application-control/utils.js.map +1 -1
  20. package/lib/cloud-connection/device-agent-cloud-connection.d.ts +5 -5
  21. package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
  22. package/lib/cloud-connection/device-agent-cloud-connection.js +140 -105
  23. package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
  24. package/lib/cloud-connection/live-updates-handler.d.ts +4 -2
  25. package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
  26. package/lib/cloud-connection/live-updates-handler.js +46 -25
  27. package/lib/cloud-connection/live-updates-handler.js.map +1 -1
  28. package/lib/cloud-connection/live-updates-handler.test.js +132 -16
  29. package/lib/cloud-connection/live-updates-handler.test.js.map +1 -1
  30. package/lib/cloud-connection/messages.d.ts.map +1 -1
  31. package/lib/cloud-connection/messages.js +3 -4
  32. package/lib/cloud-connection/messages.js.map +1 -1
  33. package/lib/cloud-connection/passthrough-handler.d.ts +5 -3
  34. package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -1
  35. package/lib/cloud-connection/passthrough-handler.js +76 -62
  36. package/lib/cloud-connection/passthrough-handler.js.map +1 -1
  37. package/lib/cloud-connection/shadow-handler.d.ts +16 -21
  38. package/lib/cloud-connection/shadow-handler.d.ts.map +1 -1
  39. package/lib/cloud-connection/shadow-handler.js +162 -108
  40. package/lib/cloud-connection/shadow-handler.js.map +1 -1
  41. package/lib/cloud-connection/shadow-handler.test.js +100 -83
  42. package/lib/cloud-connection/shadow-handler.test.js.map +1 -1
  43. package/lib/cloud-connection/transaction-manager.d.ts +3 -0
  44. package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
  45. package/lib/cloud-connection/transaction-manager.js +11 -0
  46. package/lib/cloud-connection/transaction-manager.js.map +1 -1
  47. package/lib/cloud-connection/transaction-manager.test.js +102 -0
  48. package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
  49. package/lib/device-control/device-control.d.ts +16 -15
  50. package/lib/device-control/device-control.d.ts.map +1 -1
  51. package/lib/device-control/device-control.js +117 -18
  52. package/lib/device-control/device-control.js.map +1 -1
  53. package/lib/docker/docker-compose.d.ts +14 -0
  54. package/lib/docker/docker-compose.d.ts.map +1 -0
  55. package/lib/docker/docker-compose.js +56 -0
  56. package/lib/docker/docker-compose.js.map +1 -0
  57. package/lib/index.js +2 -5
  58. package/lib/index.js.map +1 -1
  59. package/lib/infrastructure/agent-config.d.ts +45 -14
  60. package/lib/infrastructure/agent-config.d.ts.map +1 -1
  61. package/lib/infrastructure/agent-config.js +30 -15
  62. package/lib/infrastructure/agent-config.js.map +1 -1
  63. package/lib/infrastructure/agent-config.test.js +3 -0
  64. package/lib/infrastructure/agent-config.test.js.map +1 -1
  65. package/lib/local-connection/rabbitmq-connection.js +11 -11
  66. package/lib/local-connection/rabbitmq-connection.js.map +1 -1
  67. package/lib/secure-tunneling/secure-tunneling.d.ts +97 -0
  68. package/lib/secure-tunneling/secure-tunneling.d.ts.map +1 -0
  69. package/lib/secure-tunneling/secure-tunneling.js +435 -0
  70. package/lib/secure-tunneling/secure-tunneling.js.map +1 -0
  71. package/lib/secure-tunneling/secure-tunneling.test.d.ts +2 -0
  72. package/lib/secure-tunneling/secure-tunneling.test.d.ts.map +1 -0
  73. package/lib/secure-tunneling/secure-tunneling.test.js +1070 -0
  74. package/lib/secure-tunneling/secure-tunneling.test.js.map +1 -0
  75. package/lib/secure-tunneling/spawner-detached.d.ts +6 -0
  76. package/lib/secure-tunneling/spawner-detached.d.ts.map +1 -0
  77. package/lib/secure-tunneling/spawner-detached.js +107 -0
  78. package/lib/secure-tunneling/spawner-detached.js.map +1 -0
  79. package/lib/subcommands/app/analytics.d.ts.map +1 -1
  80. package/lib/subcommands/app/analytics.js +9 -13
  81. package/lib/subcommands/app/analytics.js.map +1 -1
  82. package/lib/subcommands/app/env-vars.d.ts.map +1 -1
  83. package/lib/subcommands/app/env-vars.js +11 -16
  84. package/lib/subcommands/app/env-vars.js.map +1 -1
  85. package/lib/subcommands/app/models.d.ts.map +1 -1
  86. package/lib/subcommands/app/models.js +12 -16
  87. package/lib/subcommands/app/models.js.map +1 -1
  88. package/lib/subcommands/device/clean.d.ts.map +1 -1
  89. package/lib/subcommands/device/clean.js +8 -6
  90. package/lib/subcommands/device/clean.js.map +1 -1
  91. package/lib/subcommands/device/get-info.d.ts +2 -0
  92. package/lib/subcommands/device/get-info.d.ts.map +1 -0
  93. package/lib/subcommands/device/get-info.js +36 -0
  94. package/lib/subcommands/device/get-info.js.map +1 -0
  95. package/lib/subcommands/device/index.d.ts.map +1 -1
  96. package/lib/subcommands/device/index.js +11 -2
  97. package/lib/subcommands/device/index.js.map +1 -1
  98. package/lib/subcommands/device/init.d.ts +5 -0
  99. package/lib/subcommands/device/init.d.ts.map +1 -0
  100. package/lib/subcommands/device/{device.js → init.js} +5 -36
  101. package/lib/subcommands/device/init.js.map +1 -0
  102. package/lib/subcommands/device/refresh.d.ts +2 -0
  103. package/lib/subcommands/device/refresh.d.ts.map +1 -0
  104. package/lib/subcommands/device/refresh.js +24 -0
  105. package/lib/subcommands/device/refresh.js.map +1 -0
  106. package/lib/subcommands/device/restart.d.ts +2 -0
  107. package/lib/subcommands/device/restart.d.ts.map +1 -0
  108. package/lib/subcommands/device/restart.js +14 -0
  109. package/lib/subcommands/device/restart.js.map +1 -0
  110. package/lib/util/check-for-updates.d.ts +3 -0
  111. package/lib/util/check-for-updates.d.ts.map +1 -0
  112. package/lib/util/check-for-updates.js +69 -0
  113. package/lib/util/check-for-updates.js.map +1 -0
  114. package/lib/util/cloud-mode-ready.d.ts +1 -0
  115. package/lib/util/cloud-mode-ready.d.ts.map +1 -1
  116. package/lib/util/cloud-mode-ready.js +36 -1
  117. package/lib/util/cloud-mode-ready.js.map +1 -1
  118. package/lib/util/file.d.ts +7 -0
  119. package/lib/util/file.d.ts.map +1 -0
  120. package/lib/util/file.js +66 -0
  121. package/lib/util/file.js.map +1 -0
  122. package/lib/util/file.test.d.ts +2 -0
  123. package/lib/util/file.test.d.ts.map +1 -0
  124. package/lib/util/file.test.js +87 -0
  125. package/lib/util/file.test.js.map +1 -0
  126. package/package.json +8 -7
  127. package/readme.md +3 -3
  128. package/src/application-control/config.ts +1 -1
  129. package/src/application-control/environment-variables.test.ts +1 -1
  130. package/src/application-control/environment-variables.ts +9 -6
  131. package/src/application-control/install.ts +8 -3
  132. package/src/application-control/models.ts +47 -19
  133. package/src/application-control/status.ts +16 -14
  134. package/src/application-control/utils.ts +1 -1
  135. package/src/cloud-connection/device-agent-cloud-connection.ts +202 -148
  136. package/src/cloud-connection/live-updates-handler.test.ts +161 -20
  137. package/src/cloud-connection/live-updates-handler.ts +63 -31
  138. package/src/cloud-connection/messages.ts +3 -4
  139. package/src/cloud-connection/passthrough-handler.ts +98 -76
  140. package/src/cloud-connection/shadow-handler.test.ts +101 -84
  141. package/src/cloud-connection/shadow-handler.ts +287 -133
  142. package/src/cloud-connection/transaction-manager.test.ts +124 -0
  143. package/src/cloud-connection/transaction-manager.ts +15 -0
  144. package/src/device-control/device-control.ts +125 -23
  145. package/src/docker/docker-compose.ts +60 -0
  146. package/src/index.ts +2 -6
  147. package/src/infrastructure/agent-config.test.ts +3 -0
  148. package/src/infrastructure/agent-config.ts +38 -40
  149. package/src/local-connection/rabbitmq-connection.ts +8 -8
  150. package/src/secure-tunneling/secure-tunneling.test.ts +1239 -0
  151. package/src/secure-tunneling/secure-tunneling.ts +599 -0
  152. package/src/secure-tunneling/spawner-detached.ts +123 -0
  153. package/src/subcommands/app/analytics.ts +16 -13
  154. package/src/subcommands/app/env-vars.ts +18 -16
  155. package/src/subcommands/app/models.ts +20 -16
  156. package/src/subcommands/device/clean.ts +5 -2
  157. package/src/subcommands/device/get-info.ts +49 -0
  158. package/src/subcommands/device/index.ts +11 -2
  159. package/src/subcommands/device/{device.ts → init.ts} +5 -47
  160. package/src/subcommands/device/refresh.ts +22 -0
  161. package/src/subcommands/device/restart.ts +11 -0
  162. package/src/util/check-for-updates.ts +69 -0
  163. package/src/util/cloud-mode-ready.ts +36 -0
  164. package/src/util/file.test.ts +90 -0
  165. package/src/util/file.ts +76 -0
  166. package/lib/docker/docker-compose-cmd.d.ts +0 -5
  167. package/lib/docker/docker-compose-cmd.d.ts.map +0 -1
  168. package/lib/docker/docker-compose-cmd.js +0 -16
  169. package/lib/docker/docker-compose-cmd.js.map +0 -1
  170. package/lib/secure-tunneling/index.d.ts +0 -5
  171. package/lib/secure-tunneling/index.d.ts.map +0 -1
  172. package/lib/secure-tunneling/index.js +0 -64
  173. package/lib/secure-tunneling/index.js.map +0 -1
  174. package/lib/subcommands/device/device.d.ts +0 -7
  175. package/lib/subcommands/device/device.d.ts.map +0 -1
  176. package/lib/subcommands/device/device.js.map +0 -1
  177. package/lib/util/safe-rimraf.d.ts +0 -2
  178. package/lib/util/safe-rimraf.d.ts.map +0 -1
  179. package/lib/util/safe-rimraf.js +0 -16
  180. package/lib/util/safe-rimraf.js.map +0 -1
  181. package/src/docker/docker-compose-cmd.ts +0 -15
  182. package/src/secure-tunneling/index.ts +0 -74
  183. 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 { EnvVars, getAllEnvs, readAppCfgFile } from '../application-control';
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 { getSystemInformation } from '../device-control/device-control';
10
- import { generateTxId } from '@alwaysai/device-agent-schemas';
11
-
12
- export interface ShadowTopics {
13
- projects: {
14
- get: string;
15
- getAccepted: string;
16
- getRejected: string;
17
- update: string;
18
- updateDelta: string;
19
- updateAccepted: string;
20
- updateRejected: string;
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
+ 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 readonly shadowPrefix: string;
48
- public readonly shadowTopics: 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
- projects: {
56
- get: `${this.shadowPrefix}projects/get`,
57
- getAccepted: `${this.shadowPrefix}projects/get/accepted`,
58
- getRejected: `${this.shadowPrefix}projects/get/rejected`,
59
- update: `${this.shadowPrefix}projects/update`,
60
- updateDelta: `${this.shadowPrefix}projects/update/delta`,
61
- updateAccepted: `${this.shadowPrefix}projects/update/accepted`,
62
- updateRejected: `${this.shadowPrefix}projects/update/rejected`,
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: `${this.shadowPrefix}system-info/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 handleNamedShadowUpdate({
148
+ private async processProjectShadowUpdates({
72
149
  delta
73
150
  }: {
74
151
  delta: any;
75
152
  }): Promise<ShadowUpdate[]> {
76
- const updates: ShadowUpdate[] = [];
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
- if (projectShadow.appConfig) {
87
- let newAppCfg: any;
88
- try {
89
- newAppCfg = JSON.parse(projectShadow.appConfig);
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
- `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
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
- if (updatedModels && Object.keys(updatedModels).length) {
113
- shadowUpdate.appCfgUpdate = { newAppCfg, updatedModels };
114
- } else {
115
- shadowUpdate.appCfgUpdate = { newAppCfg };
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
- if (projectShadow.envVars) {
124
- const envVars = projectShadow.envVars;
125
- shadowUpdate.envVarUpdate = { envVars };
126
- } else {
127
- logger.info(
128
- `Ignoring app environment variable shadow update for ${projectId} due to no delta`
129
- );
130
- }
131
- if (shadowUpdate.appCfgUpdate || shadowUpdate.envVarUpdate) {
132
- updates.push(shadowUpdate);
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
- return updates;
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 handleShadowTopic({
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
- const shadowName = topic.split('/')[5];
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.projects.updateAccepted:
245
+ case this.shadowTopics.project.updateAccepted: {
154
246
  if (clientToken === this.clientId) {
155
247
  logger.debug(
156
- `Ignoring delta caused by Device Agent: ${JSON.stringify(
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
- return await this.handleNamedShadowUpdate({ delta: payload.desired });
165
- case this.shadowTopics.projects.getAccepted:
166
- if (payload['delta']) {
167
- return await this.handleNamedShadowUpdate({
168
- delta: payload['delta']
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(`No delta updates in named shadow '${shadowName}'`);
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
- const packet = {
189
- state: {
190
- reported: systemInfo
191
- },
192
- clientToken: this.clientId
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 packet = {
203
- state: {
204
- reported: {
205
- [projectId]: {
206
- appConfig: JSON.stringify(appCfg),
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
- const topic = this.shadowTopics.projects.update;
214
- this.publisher.publish(topic, JSON.stringify(packet));
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 getShadowUpdates() {
218
- const topic = this.shadowTopics.projects.get;
219
- const packet = {
220
- clientToken: this.clientId
221
- };
222
- this.publisher.publish(topic, JSON.stringify(packet));
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 clearAppConfig(projectId: string) {
226
- const topic = this.shadowTopics.projects.update;
227
- // TODO: We should actually send only desired and handle the delta
228
- // to update reported
229
- const packet = {
230
- state: {
231
- desired: {
232
- [projectId]: null
233
- },
234
- reported: {
235
- [projectId]: null
236
- }
237
- },
238
- clientToken: this.clientId
239
- };
240
- this.publisher.publish(topic, JSON.stringify(packet));
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) {