@reventlessdev/reventless-aws 3.0.0-alpha.382 → 3.0.0-alpha.383
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/CHANGELOG.md +14 -0
- package/package.json +10 -10
- package/src/Platform.res +9 -2
- package/src/Platform.res.mjs +16 -16
- package/src/adapter/Runtime/AutomationSliceEntryPoint.mjs +13 -9
- package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +227 -1
- package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs +185 -7
- package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res +150 -4
- package/src/adapter/Runtime/AutomationSliceRuntime_Builder_Single.res.mjs +92 -6
- package/src/adapter/ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler_Runtime.res +104 -15
- package/src/adapter/ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler_Runtime.res.mjs +122 -34
- package/src/components/AutomationSlice_Builder.res +24 -0
- package/src/components/AutomationSlice_Builder.res.mjs +20 -2
- package/src/components/OutboundTranslationSlice_Builder.res.mjs +1 -1
- package/tests/AutomationSliceEntryPoint_OpsTest.res +91 -0
- package/tests/AutomationSliceEntryPoint_OpsTest.res.mjs +171 -35
- package/tests/ScheduledPublisher_EventBridgeSchedulerTest.res +25 -0
- package/tests/ScheduledPublisher_EventBridgeSchedulerTest.res.mjs +27 -0
|
@@ -7,6 +7,7 @@ import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
|
7
7
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
8
8
|
import * as Effect from "effect/Effect";
|
|
9
9
|
import * as Stream$1 from "effect/Stream";
|
|
10
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
10
11
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
11
12
|
import * as DcbDecode$Reventless from "@reventlessdev/reventless-spec/src/components/DcbDecode.res.mjs";
|
|
12
13
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
@@ -20,11 +21,13 @@ import * as Secrets_Node_Backend$ReventlessCore from "@reventlessdev/reventless-
|
|
|
20
21
|
import * as Util_DynamoDb_Runtime$ReventlessAws from "../../util/Util_DynamoDb_Runtime.res.mjs";
|
|
21
22
|
import * as Messaging_Log_Backend$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Messaging/Messaging_Log_Backend.res.mjs";
|
|
22
23
|
import * as AutomationSlice_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/AutomationSlice/AutomationSlice_Callback.res.mjs";
|
|
24
|
+
import * as AutomationSlice_Schedules$ReventlessCore from "@reventlessdev/reventless-core/src/components/AutomationSlice/AutomationSlice_Schedules.res.mjs";
|
|
23
25
|
import * as StreamRoutedEntryPoint_Ops$ReventlessAws from "./StreamRoutedEntryPoint_Ops.res.mjs";
|
|
24
26
|
import * as Geocoder_AwsLocation_Backend$ReventlessAws from "../Geocoder/Geocoder_AwsLocation_Backend.res.mjs";
|
|
25
27
|
import * as CommandTopicChannel_SQS_Runtime$ReventlessAws from "../CommandTopic/CommandTopicChannel_SQS_Runtime.res.mjs";
|
|
26
28
|
import * as QueryDbStorage_DynamoDb_Runtime$ReventlessAws from "../QueryDb/QueryDbStorage_DynamoDb_Runtime.res.mjs";
|
|
27
29
|
import * as OutboundTranslationSlice_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/OutboundTranslationSlice/OutboundTranslationSlice_Callback.res.mjs";
|
|
30
|
+
import * as ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws from "../ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler_Runtime.res.mjs";
|
|
28
31
|
|
|
29
32
|
function strOf(obj, key) {
|
|
30
33
|
return Stdlib_Option.flatMap(obj[key], Stdlib_JSON.Decode.string);
|
|
@@ -48,7 +51,8 @@ function decodeEntry(json) {
|
|
|
48
51
|
dcbQueueUrl: Stdlib_Option.getOr(strOf(h, "dcbQueueUrl"), ""),
|
|
49
52
|
commandQueueIsFifo: Stdlib_Option.flatMap(h["commandQueueIsFifo"], Stdlib_JSON.Decode.bool),
|
|
50
53
|
sourceUrn: Stdlib_Option.getOr(strOf(h, "sourceUrn"), ""),
|
|
51
|
-
context: Stdlib_Option.flatMap(h["context"], decodeContext)
|
|
54
|
+
context: Stdlib_Option.flatMap(h["context"], decodeContext),
|
|
55
|
+
schedulesTableName: strOf(h, "schedulesTableName")
|
|
52
56
|
}));
|
|
53
57
|
}
|
|
54
58
|
|
|
@@ -64,7 +68,8 @@ function decodeCompactEntry(modules, sharedQueueUrl, sharedIsFifo, sharedUrns, h
|
|
|
64
68
|
dcbQueueUrl: Stdlib_Option.getOr(strOf(h, "k"), sharedQueueUrl),
|
|
65
69
|
commandQueueIsFifo: f !== undefined ? f : sharedIsFifo,
|
|
66
70
|
sourceUrn: Stdlib_Option.getOr(urns[0], ""),
|
|
67
|
-
context: Stdlib_Option.flatMap(m["context"], decodeContext)
|
|
71
|
+
context: Stdlib_Option.flatMap(m["context"], decodeContext),
|
|
72
|
+
schedulesTableName: strOf(h, "s")
|
|
68
73
|
};
|
|
69
74
|
}
|
|
70
75
|
|
|
@@ -188,6 +193,74 @@ function makeLoadTodoItems(queryDbTableName, todoItems, rowSchema, comp) {
|
|
|
188
193
|
};
|
|
189
194
|
}
|
|
190
195
|
|
|
196
|
+
function scheduleTable(tableName) {
|
|
197
|
+
return {
|
|
198
|
+
id: "",
|
|
199
|
+
name: tableName,
|
|
200
|
+
arn: "",
|
|
201
|
+
hashKey: "id"
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function makeScheduleStore(tableName, rows, comp) {
|
|
206
|
+
let table = scheduleTable(tableName);
|
|
207
|
+
let save = QueryDbStorage_DynamoDb_Runtime$ReventlessAws.save(table);
|
|
208
|
+
let $$delete = QueryDbStorage_DynamoDb_Runtime$ReventlessAws.$$delete(table);
|
|
209
|
+
let stored = {
|
|
210
|
+
contents: []
|
|
211
|
+
};
|
|
212
|
+
let loaded = {
|
|
213
|
+
contents: false
|
|
214
|
+
};
|
|
215
|
+
let load = async () => {
|
|
216
|
+
if (loaded.contents) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
loaded.contents = true;
|
|
220
|
+
let params_ConsistentRead = true;
|
|
221
|
+
let params = {
|
|
222
|
+
TableName: tableName,
|
|
223
|
+
ConsistentRead: params_ConsistentRead
|
|
224
|
+
};
|
|
225
|
+
await Effect.runPromise(Effect.catchAll(Effect.map(Stream.runCollect(Util_DynamoDb_Runtime$ReventlessAws.scanStream(params)), items => {
|
|
226
|
+
items.forEach(item => {
|
|
227
|
+
let json = JSON.parse(Stdlib_Option.getOr(JSON.stringify(item), ""));
|
|
228
|
+
let row;
|
|
229
|
+
try {
|
|
230
|
+
row = Util_Sury$Reventless.fromJson(json, AutomationSlice_Schedules$ReventlessCore.rowSchema);
|
|
231
|
+
} catch (exn) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (Stdlib_Option.isNone(rows[row.key])) {
|
|
235
|
+
rows[row.key] = row;
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}), err => EffectLogger$ReventlessCore.logError(comp, undefined, `restore: couldn't read the schedules from ` + tableName + `: ` + DynamoDb_Error$ReventlessAws.message(err))));
|
|
240
|
+
stored.contents = Object.keys(rows);
|
|
241
|
+
};
|
|
242
|
+
let sync = async () => {
|
|
243
|
+
let keys = Object.keys(rows);
|
|
244
|
+
await Promise.all(Object.entries(rows).map(param => save(param[0], Util_Sury$Reventless.toJson(param[1], AutomationSlice_Schedules$ReventlessCore.rowSchema), "Overwrite", undefined)));
|
|
245
|
+
await Promise.all(stored.contents.filter(key => !keys.includes(key)).map(key => $$delete(key, undefined)));
|
|
246
|
+
stored.contents = keys;
|
|
247
|
+
};
|
|
248
|
+
return [
|
|
249
|
+
load,
|
|
250
|
+
sync
|
|
251
|
+
];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function ownArn() {
|
|
255
|
+
let env = key => Stdlib_Option.getOr(process.env[key], "");
|
|
256
|
+
let account = Stdlib_Option.getOr(env("SCHEDULER_ROLE_ARN").split(":")[4], "");
|
|
257
|
+
return `arn:aws:lambda:` + env("AWS_REGION") + `:` + account + `:function:` + env("AWS_LAMBDA_FUNCTION_NAME");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function platformSchedulesOf(slice) {
|
|
261
|
+
return ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws.platformSchedules(Stdlib_Option.getOr(process.env["SCHEDULER_ROLE_ARN"], ""), ownArn(), slice);
|
|
262
|
+
}
|
|
263
|
+
|
|
191
264
|
function messagingEmailProvider() {
|
|
192
265
|
let sender = Stdlib_Option.getOr(process.env["MESSAGING_EMAIL_SENDER"], "");
|
|
193
266
|
let match = Stdlib_Option.getOr(process.env["MESSAGING_EMAIL_PROVIDER"], "");
|
|
@@ -207,11 +280,31 @@ function capabilities() {
|
|
|
207
280
|
};
|
|
208
281
|
}
|
|
209
282
|
|
|
210
|
-
function
|
|
283
|
+
async function loadSchedules(step) {
|
|
284
|
+
if (step !== undefined) {
|
|
285
|
+
return await step.load();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function followSchedules(step, callback) {
|
|
290
|
+
if (step === undefined) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
let changes = callback.takeScheduleChanges();
|
|
294
|
+
if (changes.length !== 0) {
|
|
295
|
+
await step.sync();
|
|
296
|
+
return await AutomationSlice_Schedules$ReventlessCore.tell(step.platform, changes);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function makeAutomationJsonEventsHandler(context, callback, publishJsons, syncTodoItems, loadTodoItems, schedulesOpt) {
|
|
301
|
+
let schedules = schedulesOpt !== undefined ? schedulesOpt : () => {};
|
|
211
302
|
return stream => Effect.flatMap(Stream.runCollect(stream), jsons => Effect.promise(async () => {
|
|
212
303
|
await loadTodoItems();
|
|
304
|
+
await loadSchedules(schedules());
|
|
213
305
|
callback.phase1(jsons, context);
|
|
214
306
|
await syncTodoItems();
|
|
307
|
+
await followSchedules(schedules(), callback);
|
|
215
308
|
await callback.phase2(publishJsons);
|
|
216
309
|
return await syncTodoItems();
|
|
217
310
|
}));
|
|
@@ -242,14 +335,39 @@ function makeOutboundJsonEventsHandler(consumedEventSchema, callback, publishJso
|
|
|
242
335
|
}));
|
|
243
336
|
}
|
|
244
337
|
|
|
245
|
-
function makeAutomationRegisteredHandler(entry, sliceName, callback) {
|
|
338
|
+
function makeAutomationRegisteredHandler(entry, sliceName, callback, schedule) {
|
|
246
339
|
let comp = `AutomationSlice(` + sliceName + `)`;
|
|
247
340
|
let publishJsons = makePublishJsons(entry.dcbQueueUrl, Stdlib_Option.getOr(entry.commandQueueIsFifo, false));
|
|
248
341
|
let syncTodoItems = makeSyncTodoItems(entry.queryDbTableName, callback.todoItems, AutomationSlice_Callback$ReventlessCore.todoRowSchema);
|
|
249
342
|
let loadTodoItems = makeLoadTodoItems(entry.queryDbTableName, callback.todoItems, AutomationSlice_Callback$ReventlessCore.todoRowSchema, comp);
|
|
343
|
+
let context = Stdlib_Option.getOr(entry.context, defaultContext(sliceName));
|
|
344
|
+
let step = {
|
|
345
|
+
contents: undefined
|
|
346
|
+
};
|
|
347
|
+
let schedules = () => {
|
|
348
|
+
let match = step.contents;
|
|
349
|
+
let match$1 = entry.schedulesTableName;
|
|
350
|
+
if (match !== undefined) {
|
|
351
|
+
return match;
|
|
352
|
+
}
|
|
353
|
+
if (match$1 === undefined) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
let match$2 = makeScheduleStore(match$1, callback.schedules, comp);
|
|
357
|
+
let s_load = match$2[0];
|
|
358
|
+
let s_sync = match$2[1];
|
|
359
|
+
let s_platform = platformSchedulesOf(sliceName);
|
|
360
|
+
let s = {
|
|
361
|
+
load: s_load,
|
|
362
|
+
sync: s_sync,
|
|
363
|
+
platform: s_platform
|
|
364
|
+
};
|
|
365
|
+
step.contents = s;
|
|
366
|
+
return s;
|
|
367
|
+
};
|
|
250
368
|
return {
|
|
251
369
|
registered: {
|
|
252
|
-
handler: StreamRoutedEntryPoint_Ops$ReventlessAws.toStreamHandler(makeAutomationJsonEventsHandler(
|
|
370
|
+
handler: StreamRoutedEntryPoint_Ops$ReventlessAws.toStreamHandler(makeAutomationJsonEventsHandler(context, callback, publishJsons, syncTodoItems, loadTodoItems, schedules)),
|
|
253
371
|
comp: comp
|
|
254
372
|
},
|
|
255
373
|
sweep: async () => {
|
|
@@ -257,7 +375,33 @@ function makeAutomationRegisteredHandler(entry, sliceName, callback) {
|
|
|
257
375
|
await callback.phase2(publishJsons);
|
|
258
376
|
return await syncTodoItems();
|
|
259
377
|
},
|
|
260
|
-
comp: comp
|
|
378
|
+
comp: comp,
|
|
379
|
+
name: sliceName,
|
|
380
|
+
tick: async (key, at) => {
|
|
381
|
+
await loadTodoItems();
|
|
382
|
+
await loadSchedules(schedules());
|
|
383
|
+
callback.phase1([AutomationSlice_Callback$ReventlessCore.tickEvent(key, at)], context);
|
|
384
|
+
await syncTodoItems();
|
|
385
|
+
let match = schedules();
|
|
386
|
+
if (match !== undefined) {
|
|
387
|
+
await match.sync();
|
|
388
|
+
}
|
|
389
|
+
await callback.phase2(publishJsons);
|
|
390
|
+
return await syncTodoItems();
|
|
391
|
+
},
|
|
392
|
+
reconcile: async () => {
|
|
393
|
+
let match = schedules();
|
|
394
|
+
if (match !== undefined) {
|
|
395
|
+
await match.load();
|
|
396
|
+
await AutomationSlice_Schedules$ReventlessCore.reconcile(callback.schedules, match.platform, schedule, Date.now());
|
|
397
|
+
return await match.sync();
|
|
398
|
+
}
|
|
399
|
+
let platform = platformSchedulesOf(sliceName);
|
|
400
|
+
let names = await platform.names();
|
|
401
|
+
for (let i = 0, i_finish = names.length; i < i_finish; ++i) {
|
|
402
|
+
await platform.removeName(names[i]);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
261
405
|
};
|
|
262
406
|
}
|
|
263
407
|
|
|
@@ -276,7 +420,10 @@ function makeOutboundRegisteredHandler(entry, sliceName, consumedEventSchema, ca
|
|
|
276
420
|
await callback.phase2(publishJsons, capabilities());
|
|
277
421
|
return await syncTodoItems();
|
|
278
422
|
},
|
|
279
|
-
comp: comp
|
|
423
|
+
comp: comp,
|
|
424
|
+
name: sliceName,
|
|
425
|
+
tick: undefined,
|
|
426
|
+
reconcile: undefined
|
|
280
427
|
};
|
|
281
428
|
}
|
|
282
429
|
|
|
@@ -293,6 +440,29 @@ async function runSweeps(slices) {
|
|
|
293
440
|
}
|
|
294
441
|
}
|
|
295
442
|
|
|
443
|
+
async function runTick(slices, invocation) {
|
|
444
|
+
let tick = Stdlib_Option.flatMap(slices.find(s => s.name === invocation.slice), s => s.tick);
|
|
445
|
+
if (tick === undefined) {
|
|
446
|
+
return Stdlib_JsError.throwWithMessage(`tick for ` + invocation.slice + `: no automation of that name here`);
|
|
447
|
+
}
|
|
448
|
+
let at = new Date(invocation.at).getTime();
|
|
449
|
+
if (Number.isNaN(at)) {
|
|
450
|
+
Stdlib_JsError.throwWithMessage(`tick for ` + invocation.slice + `: "` + invocation.at + `" is not a time`);
|
|
451
|
+
}
|
|
452
|
+
return await tick(invocation.key, at);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function reconcileSchedules(slices) {
|
|
456
|
+
if (Stdlib_Option.getOr(process.env["SCHEDULER_ROLE_ARN"], "") === "") {
|
|
457
|
+
Stdlib_JsError.throwWithMessage(`reconcile: SCHEDULER_ROLE_ARN is not set`);
|
|
458
|
+
}
|
|
459
|
+
let reconciles = Stdlib_Array.filterMap(slices, s => s.reconcile);
|
|
460
|
+
for (let i = 0, i_finish = reconciles.length; i < i_finish; ++i) {
|
|
461
|
+
await reconciles[i]();
|
|
462
|
+
}
|
|
463
|
+
return Effect.runSync(EffectLogger$ReventlessCore.logInfo("AutomationSliceRuntime", undefined, `reconcile: the schedules of ` + reconciles.length.toString() + ` automation(s) are in line`));
|
|
464
|
+
}
|
|
465
|
+
|
|
296
466
|
export {
|
|
297
467
|
strOf,
|
|
298
468
|
decodeContext,
|
|
@@ -305,12 +475,20 @@ export {
|
|
|
305
475
|
makePublishJsons,
|
|
306
476
|
makeSyncTodoItems,
|
|
307
477
|
makeLoadTodoItems,
|
|
478
|
+
scheduleTable,
|
|
479
|
+
makeScheduleStore,
|
|
480
|
+
ownArn,
|
|
481
|
+
platformSchedulesOf,
|
|
308
482
|
messagingEmailProvider,
|
|
309
483
|
capabilities,
|
|
484
|
+
loadSchedules,
|
|
485
|
+
followSchedules,
|
|
310
486
|
makeAutomationJsonEventsHandler,
|
|
311
487
|
makeOutboundJsonEventsHandler,
|
|
312
488
|
makeAutomationRegisteredHandler,
|
|
313
489
|
makeOutboundRegisteredHandler,
|
|
314
490
|
runSweeps,
|
|
491
|
+
runTick,
|
|
492
|
+
reconcileSchedules,
|
|
315
493
|
}
|
|
316
494
|
/* Stream Not a pure module */
|
|
@@ -48,6 +48,12 @@ type sliceInfo = {
|
|
|
48
48
|
// created *after* the slices that target it, so a lookup at registration time
|
|
49
49
|
// finds nothing. Only the name is knowable this early.
|
|
50
50
|
commandTargetName: option<string>,
|
|
51
|
+
// The automation's declared time; what the deploy's reconcile puts on the
|
|
52
|
+
// platform, and what re-runs it when it changes.
|
|
53
|
+
schedule: option<Reventless.Schedule.time>,
|
|
54
|
+
// The schedules store's table, empty for a slice that maps no `Clock`. Granted to
|
|
55
|
+
// the Lambda with the to-do tables, and named in the slice's handler config.
|
|
56
|
+
schedulesResources: array<ReventlessInfra.Adapter.resource>,
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
let bundledInfos: dict<sliceInfo> = Dict.make()
|
|
@@ -95,6 +101,8 @@ let registerAutomationSlice = (
|
|
|
95
101
|
~sourceTopics=Dict.make(),
|
|
96
102
|
~consumesDcbLog=true,
|
|
97
103
|
~commandTargetName=?,
|
|
104
|
+
~schedule=?,
|
|
105
|
+
~schedulesResources=[],
|
|
98
106
|
) =>
|
|
99
107
|
bundledInfos->Dict.set(
|
|
100
108
|
name,
|
|
@@ -108,6 +116,8 @@ let registerAutomationSlice = (
|
|
|
108
116
|
sourceTopics,
|
|
109
117
|
consumesDcbLog,
|
|
110
118
|
commandTargetName,
|
|
119
|
+
schedule,
|
|
120
|
+
schedulesResources,
|
|
111
121
|
},
|
|
112
122
|
)
|
|
113
123
|
|
|
@@ -319,6 +329,128 @@ let makeMessagingGrant = (~runtime: ReventlessCore.Runtime.environment<runtimePa
|
|
|
319
329
|
)
|
|
320
330
|
}
|
|
321
331
|
|
|
332
|
+
// The automations' schedules. The Lambda may manage the schedules in the stack's
|
|
333
|
+
// scheduler group and pass the scheduler role to them; the scheduler role may invoke
|
|
334
|
+
// the Lambda; and each deploy invokes the Lambda to reconcile the declared schedules
|
|
335
|
+
// (`<slice>.default`), so a schedule the platform refuses fails the deploy. The
|
|
336
|
+
// declared times and the code's hash are the invocation's input, so a changed time
|
|
337
|
+
// or a new build re-runs it.
|
|
338
|
+
let makeAutomationSchedules = (
|
|
339
|
+
~runtime: ReventlessCore.Runtime.environment<runtimeParts>,
|
|
340
|
+
~schedulerRoleArn: Pulumi.Output.t<string>,
|
|
341
|
+
~sourceCodeHash: string,
|
|
342
|
+
~opts,
|
|
343
|
+
) => {
|
|
344
|
+
let name = "AllAutomationSlices"
|
|
345
|
+
let customOpts = opts->ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions
|
|
346
|
+
let lambdaArn = runtime.parts.lambda->Pulumi.Output.flatMap(l => l.arn)
|
|
347
|
+
|
|
348
|
+
let manage = {
|
|
349
|
+
open PulumiAws.PolicyDocument
|
|
350
|
+
PulumiAws.IAM.RolePolicy.make(
|
|
351
|
+
~name=`${name}SchedulerPolicy`,
|
|
352
|
+
~args={
|
|
353
|
+
policy: schedulerRoleArn
|
|
354
|
+
->Pulumi.Output.apply(arn =>
|
|
355
|
+
PulumiAws.PolicyDocument.make(
|
|
356
|
+
~id=`${name}SchedulerPolicy`,
|
|
357
|
+
~statements=[
|
|
358
|
+
{
|
|
359
|
+
sid: "AllowManageSchedules",
|
|
360
|
+
effect: Allow,
|
|
361
|
+
actions: Actions(ScheduledPublisher_EventBridgeScheduler_Runtime.manageActions),
|
|
362
|
+
resources: Resource(
|
|
363
|
+
arn->ScheduledPublisher_EventBridgeScheduler_Runtime.schedulesOfRoleArn,
|
|
364
|
+
),
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
sid: "AllowPassSchedulerRole",
|
|
368
|
+
effect: Allow,
|
|
369
|
+
actions: Action("iam:PassRole"),
|
|
370
|
+
resources: Resource(arn),
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
sid: "AllowListSchedules",
|
|
374
|
+
effect: Allow,
|
|
375
|
+
actions: Actions(ScheduledPublisher_EventBridgeScheduler_Runtime.listActions),
|
|
376
|
+
resources: AllResources,
|
|
377
|
+
},
|
|
378
|
+
],
|
|
379
|
+
)->PulumiAws.PolicyDocument.toJsonString
|
|
380
|
+
)
|
|
381
|
+
->Pulumi.Output.asInput,
|
|
382
|
+
role: runtime.parts.lambdaRole.id->Pulumi.Output.asInput,
|
|
383
|
+
},
|
|
384
|
+
~opts=customOpts,
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// The group is named after the scheduler role, so its last ARN segment is the role's name.
|
|
389
|
+
let invoke = {
|
|
390
|
+
open PulumiAws.PolicyDocument
|
|
391
|
+
PulumiAws.IAM.RolePolicy.make(
|
|
392
|
+
~name=`${name}SchedulerInvokePolicy`,
|
|
393
|
+
~args={
|
|
394
|
+
policy: lambdaArn
|
|
395
|
+
->Pulumi.Output.apply(arn =>
|
|
396
|
+
PulumiAws.PolicyDocument.make(
|
|
397
|
+
~id=`${name}SchedulerInvokePolicy`,
|
|
398
|
+
~statements=[
|
|
399
|
+
{
|
|
400
|
+
sid: "AllowSchedulerToInvokeAutomations",
|
|
401
|
+
effect: Allow,
|
|
402
|
+
actions: Action("lambda:InvokeFunction"),
|
|
403
|
+
resources: Resource(arn),
|
|
404
|
+
},
|
|
405
|
+
],
|
|
406
|
+
)->PulumiAws.PolicyDocument.toJsonString
|
|
407
|
+
)
|
|
408
|
+
->Pulumi.Output.asInput,
|
|
409
|
+
role: schedulerRoleArn
|
|
410
|
+
->Pulumi.Output.apply(ScheduledPublisher_EventBridgeScheduler_Runtime.groupOfRoleArn)
|
|
411
|
+
->Pulumi.Output.asInput,
|
|
412
|
+
},
|
|
413
|
+
~opts=customOpts,
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
let declared =
|
|
418
|
+
bundledInfos
|
|
419
|
+
->Dict.toArray
|
|
420
|
+
->Array.filterMap(((slice, info)) =>
|
|
421
|
+
info.schedule->Option.map(time => (
|
|
422
|
+
slice,
|
|
423
|
+
time->Reventless.Util_Sury.toJson(Reventless.Schedule.timeSchema),
|
|
424
|
+
))
|
|
425
|
+
)
|
|
426
|
+
->Array.toSorted(((a, _), (b, _)) => String.compare(a, b))
|
|
427
|
+
->Dict.fromArray
|
|
428
|
+
->JSON.Encode.object
|
|
429
|
+
let input =
|
|
430
|
+
Dict.fromArray([
|
|
431
|
+
("reventlessReconcile", JSON.Encode.bool(true)),
|
|
432
|
+
("declared", declared),
|
|
433
|
+
("code", sourceCodeHash->JSON.Encode.string),
|
|
434
|
+
])
|
|
435
|
+
->JSON.Encode.object
|
|
436
|
+
->JSON.stringify
|
|
437
|
+
|
|
438
|
+
let _reconcile = PulumiAws.Lambda.Invocation.make(
|
|
439
|
+
~name=`${name}ReconcileSchedules`,
|
|
440
|
+
~args={
|
|
441
|
+
functionName: runtime.parts.lambda->Pulumi.Output.flatMap(l => l.name)->Pulumi.Output.asInput,
|
|
442
|
+
input: input->Pulumi.Input.make,
|
|
443
|
+
},
|
|
444
|
+
~opts={
|
|
445
|
+
...customOpts,
|
|
446
|
+
dependsOn: [
|
|
447
|
+
manage->Pulumi.Resource.makeFromJs,
|
|
448
|
+
invoke->Pulumi.Resource.makeFromJs,
|
|
449
|
+
]->Pulumi.Input.make,
|
|
450
|
+
},
|
|
451
|
+
)
|
|
452
|
+
}
|
|
453
|
+
|
|
322
454
|
let finished = ref(false)
|
|
323
455
|
|
|
324
456
|
// Legacy finalizer, kept only to satisfy `EventCollectorRuntime_Builder.T`.
|
|
@@ -445,7 +577,10 @@ let finish = () =>
|
|
|
445
577
|
// stream — the sole source for automation/outbound slices, which dispatch by
|
|
446
578
|
// `meta.service` inside the callback. `dcbEventLog` is passed so the parent and
|
|
447
579
|
// the stream URN are available synchronously here.
|
|
448
|
-
let finishWithDcbEventLog = (
|
|
580
|
+
let finishWithDcbEventLog = (
|
|
581
|
+
~schedulerRoleArn: Pulumi.Output.t<string>,
|
|
582
|
+
dcbEventLog: ReventlessCore.DcbEventLog.component,
|
|
583
|
+
) =>
|
|
449
584
|
if !finished.contents {
|
|
450
585
|
if bundledInfos->Dict.keysToArray->Array.length > 0 {
|
|
451
586
|
let dcbResource = dcbEventLog->ReventlessCore.Component.toPulumiResource
|
|
@@ -538,6 +673,7 @@ let finishWithDcbEventLog = (dcbEventLog: ReventlessCore.DcbEventLog.component)
|
|
|
538
673
|
|
|
539
674
|
bundledInfos->Dict.forEachWithKey((info, name) => {
|
|
540
675
|
info.queryDbResources->Array.forEach(r => allQueryDbResources->Array.push(r)->ignore)
|
|
676
|
+
info.schedulesResources->Array.forEach(r => allQueryDbResources->Array.push(r)->ignore)
|
|
541
677
|
// The queue this slice publishes its command to. An Aggregate registers
|
|
542
678
|
// its CommandTopic while plugin construction is still synchronous —
|
|
543
679
|
// before DCB construction registers the slices targeting it — so a
|
|
@@ -589,11 +725,16 @@ let finishWithDcbEventLog = (dcbEventLog: ReventlessCore.DcbEventLog.component)
|
|
|
589
725
|
// exactly — so it emits neither and inherits both.
|
|
590
726
|
let hasOwnUrns =
|
|
591
727
|
!info.consumesDcbLog || info.sourceTopics->Dict.keysToArray->Array.length > 0
|
|
592
|
-
let
|
|
728
|
+
let schedulesTableName = switch info.schedulesResources->Array.get(0) {
|
|
729
|
+
| Some(resource) => resource.name
|
|
730
|
+
| None => Pulumi.Output.make("")
|
|
731
|
+
}
|
|
732
|
+
let handlerJson = Pulumi.Output.all4((
|
|
593
733
|
info.queryDbTableName,
|
|
594
734
|
commandQueueUrl,
|
|
595
735
|
sourceUrnsFor(info),
|
|
596
|
-
|
|
736
|
+
schedulesTableName,
|
|
737
|
+
))->Pulumi.Output.apply(((tableName, queueUrl, urns, schedulesTable)) => {
|
|
597
738
|
let urnsFragment = hasOwnUrns
|
|
598
739
|
? `,"u":${urns->Array.map(JSON.Encode.string)->JSON.Encode.array->JSON.stringify}`
|
|
599
740
|
: ""
|
|
@@ -601,7 +742,8 @@ let finishWithDcbEventLog = (dcbEventLog: ReventlessCore.DcbEventLog.component)
|
|
|
601
742
|
| Some(_) => `,"k":"${queueUrl}","f":${commandQueueIsFifo ? "true" : "false"}`
|
|
602
743
|
| None => ""
|
|
603
744
|
}
|
|
604
|
-
|
|
745
|
+
let schedulesFragment = schedulesTable == "" ? "" : `,"s":"${schedulesTable}"`
|
|
746
|
+
`{"n":${nameJson},"q":"${tableName}"${urnsFragment}${queueFragment}${schedulesFragment}}`
|
|
605
747
|
})
|
|
606
748
|
let _ = handlerOutputs->Array.push(handlerJson)
|
|
607
749
|
})
|
|
@@ -645,6 +787,9 @@ let finishWithDcbEventLog = (dcbEventLog: ReventlessCore.DcbEventLog.component)
|
|
|
645
787
|
PluginRuntime_Builder.capabilityEnv()->Array.forEach(((name, value)) =>
|
|
646
788
|
envVars->Dict.set(name, value->Pulumi.Output.asInput)
|
|
647
789
|
)
|
|
790
|
+
// The stack's scheduler role, which also names its group: where the
|
|
791
|
+
// automations' schedules go.
|
|
792
|
+
envVars->Dict.set("SCHEDULER_ROLE_ARN", schedulerRoleArn->Pulumi.Output.asInput)
|
|
648
793
|
|
|
649
794
|
// Carries no Pulumi Output, so the archive is still built synchronously —
|
|
650
795
|
// the table names and urns that do are what stays in HANDLER_CONFIG.
|
|
@@ -680,6 +825,7 @@ let finishWithDcbEventLog = (dcbEventLog: ReventlessCore.DcbEventLog.component)
|
|
|
680
825
|
)
|
|
681
826
|
|
|
682
827
|
makeSweepSchedule(~runtime, ~opts)
|
|
828
|
+
makeAutomationSchedules(~runtime, ~schedulerRoleArn, ~sourceCodeHash, ~opts)
|
|
683
829
|
makeGeocoderGrant(~runtime, ~opts)
|
|
684
830
|
makeMessagingGrant(~runtime, ~opts)
|
|
685
831
|
| None =>
|