@reventlessdev/reventless-aws 3.0.0-alpha.380 → 3.0.0-alpha.381
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 +12 -0
- package/package.json +11 -11
- package/src/Platform.res +3 -3
- package/src/adapter/AWS.res +7 -0
- package/src/adapter/AWS.res.mjs +17 -3
- package/src/adapter/CommandTopic/CommandTopicChannel_Helpers.res +20 -40
- package/src/adapter/CommandTopic/CommandTopicChannel_Helpers.res.mjs +14 -39
- package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +4 -2
- package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res.mjs +3 -3
- package/src/adapter/Runtime/PluginExtensionPointEntryPoint.res +5 -4
- package/src/adapter/Runtime/PluginExtensionPointEntryPoint.res.mjs +7 -3
- package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res +10 -12
- package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res.mjs +3 -3
- package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res +8 -6
- package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res.mjs +4 -3
- package/src/adapter/ScheduledPublisher/ScheduledPublisher.res +2 -2
- package/src/adapter/ScheduledPublisher/ScheduledPublisher.res.mjs +4 -4
- package/src/adapter/ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler.res +65 -0
- package/src/adapter/ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler.res.mjs +40 -0
- package/src/adapter/ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler_Runtime.res +89 -0
- package/src/adapter/ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler_Runtime.res.mjs +126 -0
- package/src/components/Scheduler.res +1 -1
- package/src/components/Scheduler.res.mjs +2 -2
- package/tests/CommandTopicQueuePolicyTest.res +12 -25
- package/tests/CommandTopicQueuePolicyTest.res.mjs +5 -10
- package/tests/ScheduledPublisher_EventBridgeSchedulerTest.res +60 -0
- package/tests/ScheduledPublisher_EventBridgeSchedulerTest.res.mjs +98 -0
- package/tests/TaskBucketEntryPoint_OpsTest.res +1 -1
- package/tests/TaskBucketEntryPoint_OpsTest.res.mjs +6 -3
- package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents.res +0 -64
- package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents.res.mjs +0 -43
- package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents_Runtime.res +0 -93
- package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents_Runtime.res.mjs +0 -85
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
let make: ReventlessCore.Scheduler_Adapter.scheduledPublisherMaker = (~name, ~opts) => {
|
|
2
|
+
let tags = name =>
|
|
3
|
+
AWS.Tags.make(
|
|
4
|
+
~name,
|
|
5
|
+
~kind=ReventlessCore.ComponentType.Platform,
|
|
6
|
+
~role=Identity,
|
|
7
|
+
~scope=Platform,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// The role EventBridge Scheduler assumes to deliver a schedule's payload to its queue.
|
|
11
|
+
let role = PulumiAws.IAM.Role.makeWithDefaultPolicy(
|
|
12
|
+
~name=ScheduledPublisher_EventBridgeScheduler_Runtime.roleName,
|
|
13
|
+
~servicePrincipal=AWS.EventBridgeScheduler.principal->Pulumi.Output.make,
|
|
14
|
+
~tags=tags(ScheduledPublisher_EventBridgeScheduler_Runtime.roleName),
|
|
15
|
+
~opts,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
let _sendPolicy = PulumiAws.IAM.RolePolicy.make(
|
|
19
|
+
~name=name ++ "SchedulerSendPolicy",
|
|
20
|
+
~args={
|
|
21
|
+
policy: {
|
|
22
|
+
open PulumiAws.PolicyDocument
|
|
23
|
+
PulumiAws.PolicyDocument.make(
|
|
24
|
+
~id=name ++ "SchedulerSendPolicy",
|
|
25
|
+
~statements=[
|
|
26
|
+
{
|
|
27
|
+
sid: "AllowSchedulerToSendToQueues",
|
|
28
|
+
effect: Allow,
|
|
29
|
+
actions: Action("sqs:SendMessage"),
|
|
30
|
+
resources: AllResources,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
)->toJsonString
|
|
34
|
+
}->Pulumi.Input.make,
|
|
35
|
+
role: role.id->Pulumi.Output.asInput,
|
|
36
|
+
},
|
|
37
|
+
~opts,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
// Named after the role: the runtime finds the group from the role ARN it is given.
|
|
41
|
+
let _group = PulumiAws.Scheduler.ScheduleGroup.make(
|
|
42
|
+
~name=name ++ "ScheduleGroup",
|
|
43
|
+
~args={
|
|
44
|
+
name: role.name->Pulumi.Output.asInput,
|
|
45
|
+
tags: tags(name ++ "ScheduleGroup"),
|
|
46
|
+
},
|
|
47
|
+
~opts,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
{
|
|
51
|
+
resource: ReventlessInfra.Adapter.make(
|
|
52
|
+
// urn carries the scheduler role ARN — bundled Lambdas read it via
|
|
53
|
+
// Scheduler.outputs.resource.urn, for the target's role and the group's name.
|
|
54
|
+
~name=role.name,
|
|
55
|
+
~id=role.id,
|
|
56
|
+
~urn=role.arn,
|
|
57
|
+
~service="EventBridgeScheduler"->Pulumi.Output.make,
|
|
58
|
+
~resourceType="aws:scheduler:ScheduleGroup"->Pulumi.Output.make,
|
|
59
|
+
),
|
|
60
|
+
operations: role.arn->Pulumi.Output.apply((roleArn): ReventlessInfra.Scheduler.operations => {
|
|
61
|
+
createSchedule: ScheduledPublisher_EventBridgeScheduler_Runtime.createSchedule(~roleArn),
|
|
62
|
+
deleteSchedule: ScheduledPublisher_EventBridgeScheduler_Runtime.deleteSchedule(~roleArn),
|
|
63
|
+
}),
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Aws from "@pulumi/aws";
|
|
4
|
+
import * as IAM$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/IAM.res.mjs";
|
|
5
|
+
import * as Pulumi from "@pulumi/pulumi";
|
|
6
|
+
import * as AWS$ReventlessAws from "../AWS.res.mjs";
|
|
7
|
+
import * as AWS_Tags$ReventlessAws from "../AWS_Tags.res.mjs";
|
|
8
|
+
import * as Adapter$ReventlessInfra from "@reventlessdev/reventless-infra/src/adapter/Adapter.res.mjs";
|
|
9
|
+
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
10
|
+
import * as ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws from "./ScheduledPublisher_EventBridgeScheduler_Runtime.res.mjs";
|
|
11
|
+
|
|
12
|
+
function make(name, opts) {
|
|
13
|
+
let tags = name => AWS_Tags$ReventlessAws.make(name, "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined);
|
|
14
|
+
let role = IAM$PulumiAws.Role.makeWithDefaultPolicy(ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws.roleName, Pulumi.output(AWS$ReventlessAws.EventBridgeScheduler.principal), tags(ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws.roleName), opts);
|
|
15
|
+
new (Aws.iam.RolePolicy)(name + "SchedulerSendPolicy", {
|
|
16
|
+
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "SchedulerSendPolicy", [{
|
|
17
|
+
Sid: "AllowSchedulerToSendToQueues",
|
|
18
|
+
Effect: "Allow",
|
|
19
|
+
Action: "sqs:SendMessage",
|
|
20
|
+
Resource: "*"
|
|
21
|
+
}])),
|
|
22
|
+
role: role.id
|
|
23
|
+
}, opts);
|
|
24
|
+
new (Aws.scheduler.ScheduleGroup)(name + "ScheduleGroup", {
|
|
25
|
+
name: role.name,
|
|
26
|
+
tags: tags(name + "ScheduleGroup")
|
|
27
|
+
}, opts);
|
|
28
|
+
return {
|
|
29
|
+
resource: Adapter$ReventlessInfra.make(role.name, role.id, role.arn, Pulumi.output("EventBridgeScheduler"), undefined, undefined, undefined, Pulumi.output("aws:scheduler:ScheduleGroup"), undefined, undefined),
|
|
30
|
+
operations: role.arn.apply(roleArn => ({
|
|
31
|
+
createSchedule: ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws.createSchedule(roleArn),
|
|
32
|
+
deleteSchedule: ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws.deleteSchedule(roleArn)
|
|
33
|
+
}))
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export {
|
|
38
|
+
make,
|
|
39
|
+
}
|
|
40
|
+
/* @pulumi/aws Not a pure module */
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
open AwsSdk.EventBridgeScheduler
|
|
2
|
+
|
|
3
|
+
// The schedule group is named after the scheduler role, so the role ARN every caller
|
|
4
|
+
// already receives also names the group its schedules go into. Pulumi names the role
|
|
5
|
+
// `<roleName>-<suffix>`.
|
|
6
|
+
let roleName = "SchedulerRole"
|
|
7
|
+
|
|
8
|
+
let groupOfRoleArn = roleArn => roleArn->String.split("/")->Array.at(-1)->Option.getOr("")
|
|
9
|
+
|
|
10
|
+
/** What a Lambda that sets schedules is granted: these actions on `schedulesOfRoleArn`. */
|
|
11
|
+
let manageActions = [
|
|
12
|
+
"scheduler:CreateSchedule",
|
|
13
|
+
"scheduler:UpdateSchedule",
|
|
14
|
+
"scheduler:DeleteSchedule",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
/** The schedules in the group of the scheduler role `roleArn` (`arn:aws:iam::<account>:role/<name>`). */
|
|
18
|
+
let schedulesOfRoleArn = roleArn => {
|
|
19
|
+
let account = roleArn->String.split(":")->Array.get(4)->Option.getOr("*")
|
|
20
|
+
`arn:aws:scheduler:*:${account}:schedule/${roleArn->groupOfRoleArn}/*`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The schedules in every scheduler group of `account`, for a grant that cannot know the role. */
|
|
24
|
+
let schedulesOfAccount = account => `arn:aws:scheduler:*:${account}:schedule/${roleName}-*/*`
|
|
25
|
+
|
|
26
|
+
// A FIFO queue refuses a message without a group id.
|
|
27
|
+
let isFifoQueue = arn => arn->String.endsWith(".fifo")
|
|
28
|
+
|
|
29
|
+
let scheduleInput = (
|
|
30
|
+
~roleArn,
|
|
31
|
+
~targetArn,
|
|
32
|
+
~setAt,
|
|
33
|
+
schedule: Reventless.Schedule.schedule,
|
|
34
|
+
): scheduleInput => {
|
|
35
|
+
let aws = schedule.time->ReventlessCore.ScheduleTime.toAws(~setAt)
|
|
36
|
+
{
|
|
37
|
+
name: schedule.name,
|
|
38
|
+
groupName: roleArn->groupOfRoleArn,
|
|
39
|
+
scheduleExpression: aws.expression,
|
|
40
|
+
scheduleExpressionTimezone: ?aws.timezone,
|
|
41
|
+
startDate: ?(aws.startDate->Option.map(Date.fromTime)),
|
|
42
|
+
flexibleTimeWindow: {mode: "OFF"},
|
|
43
|
+
actionAfterCompletion: aws.deleteAfterCompletion ? "DELETE" : "NONE",
|
|
44
|
+
target: {
|
|
45
|
+
arn: targetArn,
|
|
46
|
+
roleArn,
|
|
47
|
+
input: schedule.payload,
|
|
48
|
+
sqsParameters: ?(targetArn->isFifoQueue ? Some({messageGroupId: schedule.name}) : None),
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let failWithoutQueue = action => {
|
|
54
|
+
let err = `${action} not possible: no Queue configured !`
|
|
55
|
+
ReventlessCore.EffectLogger.logError(~comp=__MODULE__, err)
|
|
56
|
+
->Effect.flatMap(_ => Effect.sync(() => JsError.throwWithMessage(err)))
|
|
57
|
+
->Effect.runPromise
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Creates the schedule, or replaces the one of that name, as a classic rule's `PutRule` did. */
|
|
61
|
+
let createSchedule = (~roleArn: string): ReventlessCore.Scheduler.createSchedule =>
|
|
62
|
+
async (queueResources, schedule) =>
|
|
63
|
+
switch queueResources {
|
|
64
|
+
| [] => await failWithoutQueue("createSchedule")
|
|
65
|
+
| resources =>
|
|
66
|
+
ReventlessCore.ScheduleTime.validate(schedule.time)
|
|
67
|
+
let resource = resources->Array.getUnsafe(0) // FIXME
|
|
68
|
+
let input = schedule->scheduleInput(~roleArn, ~targetArn=resource.urn, ~setAt=Date.now())
|
|
69
|
+
switch await CreateScheduleCommand.make(input)->CreateScheduleCommand.send {
|
|
70
|
+
| _ => ()
|
|
71
|
+
| exception JsExn(err) if err->ErrorName.is(ErrorName.conflict) =>
|
|
72
|
+
let _ = await UpdateScheduleCommand.make(input)->UpdateScheduleCommand.send
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Deletes the schedule; one already gone, such as a once-only time that fired, is no error. */
|
|
77
|
+
let deleteSchedule = (~roleArn: string): ReventlessCore.Scheduler.deleteSchedule =>
|
|
78
|
+
async (queueResources, name) =>
|
|
79
|
+
switch queueResources {
|
|
80
|
+
| [] => await failWithoutQueue("deleteSchedule")
|
|
81
|
+
| _ =>
|
|
82
|
+
switch await DeleteScheduleCommand.make({
|
|
83
|
+
name,
|
|
84
|
+
groupName: roleArn->groupOfRoleArn,
|
|
85
|
+
})->DeleteScheduleCommand.send {
|
|
86
|
+
| _ => ()
|
|
87
|
+
| exception JsExn(err) if err->ErrorName.is(ErrorName.resourceNotFound) => ()
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/adapter/ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler_Runtime.res.mjs
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
6
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
7
|
+
import * as ClientScheduler from "@aws-sdk/client-scheduler";
|
|
8
|
+
import * as EffectLogger$ReventlessCore from "@reventlessdev/reventless-core/src/util/EffectLogger.res.mjs";
|
|
9
|
+
import * as EventBridgeScheduler$AwsSdk from "@reventlessdev/rescript-aws-sdk/src/EventBridgeScheduler.res.mjs";
|
|
10
|
+
import * as ScheduleTime$ReventlessCore from "@reventlessdev/reventless-core/src/util/ScheduleTime.res.mjs";
|
|
11
|
+
|
|
12
|
+
let roleName = "SchedulerRole";
|
|
13
|
+
|
|
14
|
+
function groupOfRoleArn(roleArn) {
|
|
15
|
+
return Stdlib_Option.getOr(roleArn.split("/").at(-1), "");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let manageActions = [
|
|
19
|
+
"scheduler:CreateSchedule",
|
|
20
|
+
"scheduler:UpdateSchedule",
|
|
21
|
+
"scheduler:DeleteSchedule"
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function schedulesOfRoleArn(roleArn) {
|
|
25
|
+
let account = Stdlib_Option.getOr(roleArn.split(":")[4], "*");
|
|
26
|
+
return `arn:aws:scheduler:*:` + account + `:schedule/` + groupOfRoleArn(roleArn) + `/*`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function schedulesOfAccount(account) {
|
|
30
|
+
return `arn:aws:scheduler:*:` + account + `:schedule/` + roleName + `-*/*`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isFifoQueue(arn) {
|
|
34
|
+
return arn.endsWith(".fifo");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function scheduleInput(roleArn, targetArn, setAt, schedule) {
|
|
38
|
+
let aws = ScheduleTime$ReventlessCore.toAws(schedule.time, setAt);
|
|
39
|
+
return {
|
|
40
|
+
Name: schedule.name,
|
|
41
|
+
GroupName: groupOfRoleArn(roleArn),
|
|
42
|
+
ScheduleExpression: aws.expression,
|
|
43
|
+
ScheduleExpressionTimezone: aws.timezone,
|
|
44
|
+
StartDate: Stdlib_Option.map(aws.startDate, prim => new Date(prim)),
|
|
45
|
+
FlexibleTimeWindow: {
|
|
46
|
+
Mode: "OFF"
|
|
47
|
+
},
|
|
48
|
+
ActionAfterCompletion: aws.deleteAfterCompletion ? "DELETE" : "NONE",
|
|
49
|
+
Target: {
|
|
50
|
+
Arn: targetArn,
|
|
51
|
+
RoleArn: roleArn,
|
|
52
|
+
Input: schedule.payload,
|
|
53
|
+
SqsParameters: targetArn.endsWith(".fifo") ? ({
|
|
54
|
+
MessageGroupId: schedule.name
|
|
55
|
+
}) : undefined
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function failWithoutQueue(action) {
|
|
61
|
+
let err = action + ` not possible: no Queue configured !`;
|
|
62
|
+
return Effect.runPromise(Effect.flatMap(EffectLogger$ReventlessCore.logError("ScheduledPublisher_EventBridgeScheduler_Runtime-ReventlessAws", undefined, err), () => Effect.sync(() => Stdlib_JsError.throwWithMessage(err))));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function createSchedule(roleArn) {
|
|
66
|
+
return async (queueResources, schedule) => {
|
|
67
|
+
if (queueResources.length === 0) {
|
|
68
|
+
return await failWithoutQueue("createSchedule");
|
|
69
|
+
}
|
|
70
|
+
ScheduleTime$ReventlessCore.validate(schedule.time);
|
|
71
|
+
let resource = queueResources[0];
|
|
72
|
+
let input = scheduleInput(roleArn, resource.urn, Date.now(), schedule);
|
|
73
|
+
try {
|
|
74
|
+
await EventBridgeScheduler$AwsSdk.CreateScheduleCommand.send(new ClientScheduler.CreateScheduleCommand(input));
|
|
75
|
+
return;
|
|
76
|
+
} catch (raw_err) {
|
|
77
|
+
let err = Primitive_exceptions.internalToException(raw_err);
|
|
78
|
+
if (err.RE_EXN_ID === "JsExn") {
|
|
79
|
+
if (EventBridgeScheduler$AwsSdk.ErrorName.is(err._1, EventBridgeScheduler$AwsSdk.ErrorName.conflict)) {
|
|
80
|
+
await EventBridgeScheduler$AwsSdk.UpdateScheduleCommand.send(new ClientScheduler.UpdateScheduleCommand(input));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
throw err;
|
|
84
|
+
}
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function deleteSchedule(roleArn) {
|
|
91
|
+
return async (queueResources, name) => {
|
|
92
|
+
if (queueResources.length === 0) {
|
|
93
|
+
return await failWithoutQueue("deleteSchedule");
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
await EventBridgeScheduler$AwsSdk.DeleteScheduleCommand.send(new ClientScheduler.DeleteScheduleCommand({
|
|
97
|
+
Name: name,
|
|
98
|
+
GroupName: groupOfRoleArn(roleArn)
|
|
99
|
+
}));
|
|
100
|
+
return;
|
|
101
|
+
} catch (raw_err) {
|
|
102
|
+
let err = Primitive_exceptions.internalToException(raw_err);
|
|
103
|
+
if (err.RE_EXN_ID === "JsExn") {
|
|
104
|
+
if (EventBridgeScheduler$AwsSdk.ErrorName.is(err._1, EventBridgeScheduler$AwsSdk.ErrorName.resourceNotFound)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export {
|
|
115
|
+
roleName,
|
|
116
|
+
groupOfRoleArn,
|
|
117
|
+
manageActions,
|
|
118
|
+
schedulesOfRoleArn,
|
|
119
|
+
schedulesOfAccount,
|
|
120
|
+
isFifoQueue,
|
|
121
|
+
scheduleInput,
|
|
122
|
+
failWithoutQueue,
|
|
123
|
+
createSchedule,
|
|
124
|
+
deleteSchedule,
|
|
125
|
+
}
|
|
126
|
+
/* effect/Effect Not a pure module */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
include ReventlessCore.Scheduler_Builder.Make(
|
|
1
|
+
include ReventlessCore.Scheduler_Builder.Make(ScheduledPublisher_EventBridgeScheduler)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Scheduler_Builder$ReventlessCore from "@reventlessdev/reventless-core/src/components/Scheduler/Scheduler_Builder.res.mjs";
|
|
4
|
-
import * as
|
|
4
|
+
import * as ScheduledPublisher_EventBridgeScheduler$ReventlessAws from "../adapter/ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler.res.mjs";
|
|
5
5
|
|
|
6
|
-
let include = Scheduler_Builder$ReventlessCore.Make(
|
|
6
|
+
let include = Scheduler_Builder$ReventlessCore.Make(ScheduledPublisher_EventBridgeScheduler$ReventlessAws);
|
|
7
7
|
|
|
8
8
|
let make = include.make;
|
|
9
9
|
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
open JestGlobals
|
|
2
2
|
|
|
3
|
-
// Guards the CommandTopic queue policy
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// resource policy — so the condition keys are the only bound on who may send
|
|
7
|
-
// into a command topic. A dropped condition is invisible at deploy time.
|
|
3
|
+
// Guards the CommandTopic queue policy. EventBridge Scheduler sends as the
|
|
4
|
+
// scheduler role, which holds sqs:SendMessage itself, so the queue admits no
|
|
5
|
+
// service but its consuming Lambda. A widened policy is invisible at deploy time.
|
|
8
6
|
|
|
9
7
|
let queueArn = "arn:aws:sqs:eu-west-1:123456789012:CatalogAggrCmdTopic-5150050"
|
|
10
8
|
let lambdaArn = "arn:aws:lambda:eu-west-1:123456789012:function:AllAggregates-1a2b3c4"
|
|
@@ -32,26 +30,15 @@ let condition = (sid, operator, key) =>
|
|
|
32
30
|
->Option.flatMap(o => o->field(key))
|
|
33
31
|
->Option.flatMap(JSON.Decode.string)
|
|
34
32
|
|
|
35
|
-
describe("CommandTopic queue policy —
|
|
36
|
-
testSync("
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
expect(
|
|
44
|
-
Some("arn:aws:events:*:123456789012:rule/*"),
|
|
45
|
-
)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
testSync("names the EventBridge service principal", () => {
|
|
49
|
-
let principal =
|
|
50
|
-
statement("AllowCloudWatchEventsToSendToQueue")
|
|
51
|
-
->Option.flatMap(s => s->field("Principal"))
|
|
52
|
-
->Option.flatMap(p => p->field("Service"))
|
|
53
|
-
->Option.flatMap(JSON.Decode.string)
|
|
54
|
-
expect(principal)->toEqual(Some("events.amazonaws.com"))
|
|
33
|
+
describe("CommandTopic queue policy — services", () => {
|
|
34
|
+
testSync("admits only the consuming Lambda", () => {
|
|
35
|
+
let sids =
|
|
36
|
+
policy
|
|
37
|
+
->field("Statement")
|
|
38
|
+
->Option.flatMap(JSON.Decode.array)
|
|
39
|
+
->Option.getOr([])
|
|
40
|
+
->Array.filterMap(s => s->field("Sid")->Option.flatMap(JSON.Decode.string))
|
|
41
|
+
expect(sids)->toEqual(["AllowLambdaToAccessQueue"])
|
|
55
42
|
})
|
|
56
43
|
})
|
|
57
44
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
4
|
+
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
4
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
6
|
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
6
7
|
import * as CommandTopicChannel_Helpers$ReventlessAws from "../src/adapter/CommandTopic/CommandTopicChannel_Helpers.res.mjs";
|
|
@@ -23,16 +24,10 @@ function condition(sid, operator, key) {
|
|
|
23
24
|
return Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(statement(sid), s => field(s, "Condition")), c => field(c, operator)), o => field(o, key)), Stdlib_JSON.Decode.string);
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
globalThis.describe("CommandTopic queue policy —
|
|
27
|
-
globalThis.test("
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
globalThis.test("is scoped to EventBridge rule ARNs in this account", () => {
|
|
31
|
-
globalThis.expect(condition("AllowCloudWatchEventsToSendToQueue", "ArnLike", "aws:SourceArn")).toEqual("arn:aws:events:*:123456789012:rule/*");
|
|
32
|
-
});
|
|
33
|
-
globalThis.test("names the EventBridge service principal", () => {
|
|
34
|
-
let principal = Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_Option.flatMap(statement("AllowCloudWatchEventsToSendToQueue"), s => field(s, "Principal")), p => field(p, "Service")), Stdlib_JSON.Decode.string);
|
|
35
|
-
globalThis.expect(principal).toEqual("events.amazonaws.com");
|
|
27
|
+
globalThis.describe("CommandTopic queue policy — services", () => {
|
|
28
|
+
globalThis.test("admits only the consuming Lambda", () => {
|
|
29
|
+
let sids = Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(field(policy, "Statement"), Stdlib_JSON.Decode.array), []), s => Stdlib_Option.flatMap(field(s, "Sid"), Stdlib_JSON.Decode.string));
|
|
30
|
+
globalThis.expect(sids).toEqual(["AllowLambdaToAccessQueue"]);
|
|
36
31
|
});
|
|
37
32
|
});
|
|
38
33
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
open JestGlobals
|
|
2
|
+
open AwsSdk.EventBridgeScheduler
|
|
3
|
+
|
|
4
|
+
// What EventBridge Scheduler is given for a schedule; the times themselves are
|
|
5
|
+
// ScheduleTime's, tested in core.
|
|
6
|
+
|
|
7
|
+
module Runtime = ScheduledPublisher_EventBridgeScheduler_Runtime
|
|
8
|
+
|
|
9
|
+
let roleArn = "arn:aws:iam::123456789012:role/SchedulerRole-1a2b3c4"
|
|
10
|
+
let queueArn = "arn:aws:sqs:eu-central-1:123456789012:CatalogAggrCmdTopic-5150050"
|
|
11
|
+
let fifoArn = "arn:aws:sqs:eu-central-1:123456789012:CatalogAggrCmdTopic-5150050.fifo"
|
|
12
|
+
let setAt = Date.fromString("2026-10-15T14:02:00Z")->Date.getTime
|
|
13
|
+
|
|
14
|
+
let input = (~targetArn=queueArn, time) =>
|
|
15
|
+
{Reventless.Schedule.name: "disconnect-p1", time, payload: `{"a":1}`}->Runtime.scheduleInput(
|
|
16
|
+
~roleArn,
|
|
17
|
+
~targetArn,
|
|
18
|
+
~setAt,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
describe("ScheduledPublisher_EventBridgeScheduler_Runtime", () => {
|
|
22
|
+
testSync("puts the schedule into the group named after the role", () => {
|
|
23
|
+
let i = input(Every(Minutes(5)))
|
|
24
|
+
expect(i.groupName)->toEqual(Some("SchedulerRole-1a2b3c4"))
|
|
25
|
+
expect(i.target.roleArn)->toBe(roleArn)
|
|
26
|
+
expect(i.target.input)->toEqual(Some(`{"a":1}`))
|
|
27
|
+
expect(i.flexibleTimeWindow.mode)->toBe("OFF")
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
testSync("starts an interval one interval after it is set", () => {
|
|
31
|
+
let i = input(Every(Minutes(5)))
|
|
32
|
+
expect(i.scheduleExpression)->toBe("rate(5 minutes)")
|
|
33
|
+
expect(i.startDate->Option.map(Date.getTime))->toEqual(Some(setAt +. 5. *. 60_000.))
|
|
34
|
+
expect(i.actionAfterCompletion)->toEqual(Some("NONE"))
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
testSync("deletes a once-only schedule after it fires, read in its zone", () => {
|
|
38
|
+
let i = input(Once({moment: After(Minutes(10)), zone: "Europe/Berlin"}))
|
|
39
|
+
expect(i.scheduleExpression)->toBe("at(2026-10-15T16:12:00)")
|
|
40
|
+
expect(i.scheduleExpressionTimezone)->toEqual(Some("Europe/Berlin"))
|
|
41
|
+
expect(i.actionAfterCompletion)->toEqual(Some("DELETE"))
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
testSync("gives a FIFO queue a message group, a standard queue none", () => {
|
|
45
|
+
let fifo = input(~targetArn=fifoArn, Every(Hours(1)))
|
|
46
|
+
expect(fifo.target.sqsParameters->Option.flatMap(p => p.messageGroupId))->toEqual(
|
|
47
|
+
Some("disconnect-p1"),
|
|
48
|
+
)
|
|
49
|
+
expect(input(Every(Hours(1))).target.sqsParameters)->toEqual(None)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
testSync("grants the schedules of the role's group, or of every group in the account", () => {
|
|
53
|
+
expect(roleArn->Runtime.schedulesOfRoleArn)->toBe(
|
|
54
|
+
"arn:aws:scheduler:*:123456789012:schedule/SchedulerRole-1a2b3c4/*",
|
|
55
|
+
)
|
|
56
|
+
expect("123456789012"->Runtime.schedulesOfAccount)->toBe(
|
|
57
|
+
"arn:aws:scheduler:*:123456789012:schedule/SchedulerRole-*/*",
|
|
58
|
+
)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
4
|
+
import * as ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws from "../src/adapter/ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler_Runtime.res.mjs";
|
|
5
|
+
|
|
6
|
+
let roleArn = "arn:aws:iam::123456789012:role/SchedulerRole-1a2b3c4";
|
|
7
|
+
|
|
8
|
+
let queueArn = "arn:aws:sqs:eu-central-1:123456789012:CatalogAggrCmdTopic-5150050";
|
|
9
|
+
|
|
10
|
+
let fifoArn = "arn:aws:sqs:eu-central-1:123456789012:CatalogAggrCmdTopic-5150050.fifo";
|
|
11
|
+
|
|
12
|
+
let setAt = new Date("2026-10-15T14:02:00Z").getTime();
|
|
13
|
+
|
|
14
|
+
function input(targetArnOpt, time) {
|
|
15
|
+
let targetArn = targetArnOpt !== undefined ? targetArnOpt : queueArn;
|
|
16
|
+
return ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws.scheduleInput(roleArn, targetArn, setAt, {
|
|
17
|
+
name: "disconnect-p1",
|
|
18
|
+
time: time,
|
|
19
|
+
payload: `{"a":1}`
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
globalThis.describe("ScheduledPublisher_EventBridgeScheduler_Runtime", () => {
|
|
24
|
+
globalThis.test("puts the schedule into the group named after the role", () => {
|
|
25
|
+
let i = input(undefined, {
|
|
26
|
+
TAG: "Every",
|
|
27
|
+
_0: {
|
|
28
|
+
TAG: "Minutes",
|
|
29
|
+
_0: 5
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
globalThis.expect(i.GroupName).toEqual("SchedulerRole-1a2b3c4");
|
|
33
|
+
globalThis.expect(i.Target.RoleArn).toBe(roleArn);
|
|
34
|
+
globalThis.expect(i.Target.Input).toEqual(`{"a":1}`);
|
|
35
|
+
globalThis.expect(i.FlexibleTimeWindow.Mode).toBe("OFF");
|
|
36
|
+
});
|
|
37
|
+
globalThis.test("starts an interval one interval after it is set", () => {
|
|
38
|
+
let i = input(undefined, {
|
|
39
|
+
TAG: "Every",
|
|
40
|
+
_0: {
|
|
41
|
+
TAG: "Minutes",
|
|
42
|
+
_0: 5
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
globalThis.expect(i.ScheduleExpression).toBe("rate(5 minutes)");
|
|
46
|
+
globalThis.expect(Stdlib_Option.map(i.StartDate, prim => prim.getTime())).toEqual(setAt + 5 * 60000);
|
|
47
|
+
globalThis.expect(i.ActionAfterCompletion).toEqual("NONE");
|
|
48
|
+
});
|
|
49
|
+
globalThis.test("deletes a once-only schedule after it fires, read in its zone", () => {
|
|
50
|
+
let i = input(undefined, {
|
|
51
|
+
TAG: "Once",
|
|
52
|
+
moment: {
|
|
53
|
+
TAG: "After",
|
|
54
|
+
_0: {
|
|
55
|
+
TAG: "Minutes",
|
|
56
|
+
_0: 10
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
zone: "Europe/Berlin"
|
|
60
|
+
});
|
|
61
|
+
globalThis.expect(i.ScheduleExpression).toBe("at(2026-10-15T16:12:00)");
|
|
62
|
+
globalThis.expect(i.ScheduleExpressionTimezone).toEqual("Europe/Berlin");
|
|
63
|
+
globalThis.expect(i.ActionAfterCompletion).toEqual("DELETE");
|
|
64
|
+
});
|
|
65
|
+
globalThis.test("gives a FIFO queue a message group, a standard queue none", () => {
|
|
66
|
+
let fifo = input(fifoArn, {
|
|
67
|
+
TAG: "Every",
|
|
68
|
+
_0: {
|
|
69
|
+
TAG: "Hours",
|
|
70
|
+
_0: 1
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
globalThis.expect(Stdlib_Option.flatMap(fifo.Target.SqsParameters, p => p.MessageGroupId)).toEqual("disconnect-p1");
|
|
74
|
+
globalThis.expect(input(undefined, {
|
|
75
|
+
TAG: "Every",
|
|
76
|
+
_0: {
|
|
77
|
+
TAG: "Hours",
|
|
78
|
+
_0: 1
|
|
79
|
+
}
|
|
80
|
+
}).Target.SqsParameters).toEqual(undefined);
|
|
81
|
+
});
|
|
82
|
+
globalThis.test("grants the schedules of the role's group, or of every group in the account", () => {
|
|
83
|
+
globalThis.expect(ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws.schedulesOfRoleArn(roleArn)).toBe("arn:aws:scheduler:*:123456789012:schedule/SchedulerRole-1a2b3c4/*");
|
|
84
|
+
globalThis.expect(ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws.schedulesOfAccount("123456789012")).toBe("arn:aws:scheduler:*:123456789012:schedule/SchedulerRole-*/*");
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
let Runtime;
|
|
89
|
+
|
|
90
|
+
export {
|
|
91
|
+
Runtime,
|
|
92
|
+
roleArn,
|
|
93
|
+
queueArn,
|
|
94
|
+
fifoArn,
|
|
95
|
+
setAt,
|
|
96
|
+
input,
|
|
97
|
+
}
|
|
98
|
+
/* setAt Not a pure module */
|
|
@@ -62,7 +62,7 @@ describe("TaskBucketEntryPoint_Ops.buildPublishCommands", () => {
|
|
|
62
62
|
describe("TaskBucketEntryPoint_Ops.dispatchTaskActions", () => {
|
|
63
63
|
let schedule: Reventless.Schedule.schedule = {
|
|
64
64
|
name: "sweep",
|
|
65
|
-
|
|
65
|
+
time: Reventless.Schedule.Every(Minutes(5)),
|
|
66
66
|
payload: "{}",
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -91,9 +91,12 @@ globalThis.describe("TaskBucketEntryPoint_Ops.buildPublishCommands", () => {
|
|
|
91
91
|
globalThis.describe("TaskBucketEntryPoint_Ops.dispatchTaskActions", () => {
|
|
92
92
|
let schedule = {
|
|
93
93
|
name: "sweep",
|
|
94
|
-
|
|
95
|
-
TAG: "
|
|
96
|
-
_0:
|
|
94
|
+
time: {
|
|
95
|
+
TAG: "Every",
|
|
96
|
+
_0: {
|
|
97
|
+
TAG: "Minutes",
|
|
98
|
+
_0: 5
|
|
99
|
+
}
|
|
97
100
|
},
|
|
98
101
|
payload: "{}"
|
|
99
102
|
};
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
let make: ReventlessCore.Scheduler_Adapter.scheduledPublisherMaker = (~name, ~opts) => {
|
|
2
|
-
let role = PulumiAws.IAM.Role.makeWithDefaultPolicy(
|
|
3
|
-
~name="CloudWatchEventsRole",
|
|
4
|
-
~servicePrincipal=AWS.CloudwatchEventRule.principal->Pulumi.Output.make,
|
|
5
|
-
~tags=AWS.Tags.make(
|
|
6
|
-
~name="CloudWatchEventsRole",
|
|
7
|
-
~kind=ReventlessCore.ComponentType.Platform,
|
|
8
|
-
~role=Identity,
|
|
9
|
-
~scope=Platform,
|
|
10
|
-
),
|
|
11
|
-
~opts,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
let _policy = PulumiAws.IAM.Policy.make(
|
|
15
|
-
~name=name ++ "CloudWatchEventsPolicy",
|
|
16
|
-
~args={
|
|
17
|
-
PulumiAws.IAM.Policy.policy: role.arn
|
|
18
|
-
->Pulumi.Output.apply(roleArn => {
|
|
19
|
-
open PulumiAws.PolicyDocument
|
|
20
|
-
PulumiAws.PolicyDocument.make(
|
|
21
|
-
~id=name ++ "CloudWatchEventsPolicy",
|
|
22
|
-
~statements=[
|
|
23
|
-
{
|
|
24
|
-
sid: "AllowCloudWatchEvents",
|
|
25
|
-
effect: Allow,
|
|
26
|
-
actions: Action("events:*"),
|
|
27
|
-
resources: AllResources,
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
sid: "AllowPassRole",
|
|
31
|
-
effect: Allow,
|
|
32
|
-
actions: Action("iam:PassRole"),
|
|
33
|
-
resources: Resource(`${roleArn}`),
|
|
34
|
-
},
|
|
35
|
-
],
|
|
36
|
-
)->toJsonString
|
|
37
|
-
})
|
|
38
|
-
->Pulumi.Output.asInput,
|
|
39
|
-
tags: AWS.Tags.make(
|
|
40
|
-
~name=name ++ "CloudWatchEventsPolicy",
|
|
41
|
-
~kind=ReventlessCore.ComponentType.Platform,
|
|
42
|
-
~role=Identity,
|
|
43
|
-
~scope=Platform,
|
|
44
|
-
),
|
|
45
|
-
},
|
|
46
|
-
~opts,
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
{
|
|
50
|
-
resource: ReventlessInfra.Adapter.make(
|
|
51
|
-
// urn carries the CloudWatch Events role ARN — bundled Lambdas read it
|
|
52
|
-
// via Scheduler.outputs.resource.urn to call PutRule with the right roleArn.
|
|
53
|
-
~name=role.name,
|
|
54
|
-
~id=role.id,
|
|
55
|
-
~urn=role.arn,
|
|
56
|
-
~service="CloudWatchEvents"->Pulumi.Output.make,
|
|
57
|
-
~resourceType="aws:cloudwatch:EventRule"->Pulumi.Output.make,
|
|
58
|
-
),
|
|
59
|
-
operations: role.arn->Pulumi.Output.apply((roleArn): ReventlessInfra.Scheduler.operations => {
|
|
60
|
-
createSchedule: ScheduledPublisher_CloudWatchEvents_Runtime.createSchedule(~roleArn),
|
|
61
|
-
deleteSchedule: ScheduledPublisher_CloudWatchEvents_Runtime.deleteSchedule,
|
|
62
|
-
}),
|
|
63
|
-
}
|
|
64
|
-
}
|