@reventlessdev/reventless-aws 3.0.0-alpha.380 → 3.0.0-alpha.382

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 (35) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +11 -11
  3. package/src/Platform.res +3 -3
  4. package/src/adapter/AWS.res +7 -0
  5. package/src/adapter/AWS.res.mjs +17 -3
  6. package/src/adapter/AWS_Tags.res +8 -0
  7. package/src/adapter/AWS_Tags.res.mjs +5 -0
  8. package/src/adapter/CommandTopic/CommandTopicChannel_Helpers.res +20 -40
  9. package/src/adapter/CommandTopic/CommandTopicChannel_Helpers.res.mjs +14 -39
  10. package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +4 -2
  11. package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res.mjs +3 -3
  12. package/src/adapter/Runtime/PluginExtensionPointEntryPoint.res +5 -4
  13. package/src/adapter/Runtime/PluginExtensionPointEntryPoint.res.mjs +7 -3
  14. package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res +10 -12
  15. package/src/adapter/Runtime/TaskBucketEntryPoint_Ops.res.mjs +3 -3
  16. package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res +8 -6
  17. package/src/adapter/Runtime/TaskRuntime_Builder_PerBucket.res.mjs +4 -3
  18. package/src/adapter/ScheduledPublisher/ScheduledPublisher.res +2 -2
  19. package/src/adapter/ScheduledPublisher/ScheduledPublisher.res.mjs +4 -4
  20. package/src/adapter/ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler.res +73 -0
  21. package/src/adapter/ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler.res.mjs +40 -0
  22. package/src/adapter/ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler_Runtime.res +89 -0
  23. package/src/adapter/ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler_Runtime.res.mjs +126 -0
  24. package/src/components/Scheduler.res +1 -1
  25. package/src/components/Scheduler.res.mjs +2 -2
  26. package/tests/CommandTopicQueuePolicyTest.res +12 -25
  27. package/tests/CommandTopicQueuePolicyTest.res.mjs +5 -10
  28. package/tests/ScheduledPublisher_EventBridgeSchedulerTest.res +65 -0
  29. package/tests/ScheduledPublisher_EventBridgeSchedulerTest.res.mjs +115 -0
  30. package/tests/TaskBucketEntryPoint_OpsTest.res +1 -1
  31. package/tests/TaskBucketEntryPoint_OpsTest.res.mjs +6 -3
  32. package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents.res +0 -64
  33. package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents.res.mjs +0 -43
  34. package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents_Runtime.res +0 -93
  35. package/src/adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents_Runtime.res.mjs +0 -85
@@ -0,0 +1,73 @@
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
+ // A schedule group refuses the empty values platform-scoped tags carry.
46
+ tags: AWS.Tags.makeDict(
47
+ ~name=name ++ "ScheduleGroup",
48
+ ~kind=ReventlessCore.ComponentType.Platform,
49
+ ~role=Identity,
50
+ ~scope=Platform,
51
+ )
52
+ ->AWS.Tags.withoutEmpty
53
+ ->Pulumi.Input.make,
54
+ },
55
+ ~opts,
56
+ )
57
+
58
+ {
59
+ resource: ReventlessInfra.Adapter.make(
60
+ // urn carries the scheduler role ARN — bundled Lambdas read it via
61
+ // Scheduler.outputs.resource.urn, for the target's role and the group's name.
62
+ ~name=role.name,
63
+ ~id=role.id,
64
+ ~urn=role.arn,
65
+ ~service="EventBridgeScheduler"->Pulumi.Output.make,
66
+ ~resourceType="aws:scheduler:ScheduleGroup"->Pulumi.Output.make,
67
+ ),
68
+ operations: role.arn->Pulumi.Output.apply((roleArn): ReventlessInfra.Scheduler.operations => {
69
+ createSchedule: ScheduledPublisher_EventBridgeScheduler_Runtime.createSchedule(~roleArn),
70
+ deleteSchedule: ScheduledPublisher_EventBridgeScheduler_Runtime.deleteSchedule(~roleArn),
71
+ }),
72
+ }
73
+ }
@@ -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: AWS_Tags$ReventlessAws.withoutEmpty(AWS_Tags$ReventlessAws.makeDict(name + "ScheduleGroup", "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined))
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
+ }
@@ -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(ScheduledPublisher_CloudWatchEvents)
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 ScheduledPublisher_CloudWatchEvents$ReventlessAws from "../adapter/ScheduledPublisher/ScheduledPublisher_CloudWatchEvents.res.mjs";
4
+ import * as ScheduledPublisher_EventBridgeScheduler$ReventlessAws from "../adapter/ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler.res.mjs";
5
5
 
6
- let include = Scheduler_Builder$ReventlessCore.Make(ScheduledPublisher_CloudWatchEvents$ReventlessAws);
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's service-principal scoping. The
4
- // EventBridge grant cannot name a single rule ARN — ScheduledPublisher creates
5
- // rules at runtime and EventBridge authorises SQS targets against the queue's
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 — EventBridge grant", () => {
36
- testSync("is scoped to this account", () => {
37
- expect(
38
- condition("AllowCloudWatchEventsToSendToQueue", "StringEquals", "aws:SourceAccount"),
39
- )->toEqual(Some("123456789012"))
40
- })
41
-
42
- testSync("is scoped to EventBridge rule ARNs in this account", () => {
43
- expect(condition("AllowCloudWatchEventsToSendToQueue", "ArnLike", "aws:SourceArn"))->toEqual(
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 — EventBridge grant", () => {
27
- globalThis.test("is scoped to this account", () => {
28
- globalThis.expect(condition("AllowCloudWatchEventsToSendToQueue", "StringEquals", "aws:SourceAccount")).toEqual("123456789012");
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,65 @@
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
+
61
+ testSync("a schedule group's tags carry no empty value, which AWS refuses", () => {
62
+ let tags = Dict.fromArray([("Name", "SchedulerScheduleGroup"), ("reventless:plugin", "")])
63
+ expect(tags->AWS.Tags.withoutEmpty->Dict.toArray)->toEqual([("Name", "SchedulerScheduleGroup")])
64
+ })
65
+ })
@@ -0,0 +1,115 @@
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 AWS_Tags$ReventlessAws from "../src/adapter/AWS_Tags.res.mjs";
5
+ import * as ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws from "../src/adapter/ScheduledPublisher/ScheduledPublisher_EventBridgeScheduler_Runtime.res.mjs";
6
+
7
+ let roleArn = "arn:aws:iam::123456789012:role/SchedulerRole-1a2b3c4";
8
+
9
+ let queueArn = "arn:aws:sqs:eu-central-1:123456789012:CatalogAggrCmdTopic-5150050";
10
+
11
+ let fifoArn = "arn:aws:sqs:eu-central-1:123456789012:CatalogAggrCmdTopic-5150050.fifo";
12
+
13
+ let setAt = new Date("2026-10-15T14:02:00Z").getTime();
14
+
15
+ function input(targetArnOpt, time) {
16
+ let targetArn = targetArnOpt !== undefined ? targetArnOpt : queueArn;
17
+ return ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws.scheduleInput(roleArn, targetArn, setAt, {
18
+ name: "disconnect-p1",
19
+ time: time,
20
+ payload: `{"a":1}`
21
+ });
22
+ }
23
+
24
+ globalThis.describe("ScheduledPublisher_EventBridgeScheduler_Runtime", () => {
25
+ globalThis.test("puts the schedule into the group named after the role", () => {
26
+ let i = input(undefined, {
27
+ TAG: "Every",
28
+ _0: {
29
+ TAG: "Minutes",
30
+ _0: 5
31
+ }
32
+ });
33
+ globalThis.expect(i.GroupName).toEqual("SchedulerRole-1a2b3c4");
34
+ globalThis.expect(i.Target.RoleArn).toBe(roleArn);
35
+ globalThis.expect(i.Target.Input).toEqual(`{"a":1}`);
36
+ globalThis.expect(i.FlexibleTimeWindow.Mode).toBe("OFF");
37
+ });
38
+ globalThis.test("starts an interval one interval after it is set", () => {
39
+ let i = input(undefined, {
40
+ TAG: "Every",
41
+ _0: {
42
+ TAG: "Minutes",
43
+ _0: 5
44
+ }
45
+ });
46
+ globalThis.expect(i.ScheduleExpression).toBe("rate(5 minutes)");
47
+ globalThis.expect(Stdlib_Option.map(i.StartDate, prim => prim.getTime())).toEqual(setAt + 5 * 60000);
48
+ globalThis.expect(i.ActionAfterCompletion).toEqual("NONE");
49
+ });
50
+ globalThis.test("deletes a once-only schedule after it fires, read in its zone", () => {
51
+ let i = input(undefined, {
52
+ TAG: "Once",
53
+ moment: {
54
+ TAG: "After",
55
+ _0: {
56
+ TAG: "Minutes",
57
+ _0: 10
58
+ }
59
+ },
60
+ zone: "Europe/Berlin"
61
+ });
62
+ globalThis.expect(i.ScheduleExpression).toBe("at(2026-10-15T16:12:00)");
63
+ globalThis.expect(i.ScheduleExpressionTimezone).toEqual("Europe/Berlin");
64
+ globalThis.expect(i.ActionAfterCompletion).toEqual("DELETE");
65
+ });
66
+ globalThis.test("gives a FIFO queue a message group, a standard queue none", () => {
67
+ let fifo = input(fifoArn, {
68
+ TAG: "Every",
69
+ _0: {
70
+ TAG: "Hours",
71
+ _0: 1
72
+ }
73
+ });
74
+ globalThis.expect(Stdlib_Option.flatMap(fifo.Target.SqsParameters, p => p.MessageGroupId)).toEqual("disconnect-p1");
75
+ globalThis.expect(input(undefined, {
76
+ TAG: "Every",
77
+ _0: {
78
+ TAG: "Hours",
79
+ _0: 1
80
+ }
81
+ }).Target.SqsParameters).toEqual(undefined);
82
+ });
83
+ globalThis.test("grants the schedules of the role's group, or of every group in the account", () => {
84
+ globalThis.expect(ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws.schedulesOfRoleArn(roleArn)).toBe("arn:aws:scheduler:*:123456789012:schedule/SchedulerRole-1a2b3c4/*");
85
+ globalThis.expect(ScheduledPublisher_EventBridgeScheduler_Runtime$ReventlessAws.schedulesOfAccount("123456789012")).toBe("arn:aws:scheduler:*:123456789012:schedule/SchedulerRole-*/*");
86
+ });
87
+ globalThis.test("a schedule group's tags carry no empty value, which AWS refuses", () => {
88
+ let tags = Object.fromEntries([
89
+ [
90
+ "Name",
91
+ "SchedulerScheduleGroup"
92
+ ],
93
+ [
94
+ "reventless:plugin",
95
+ ""
96
+ ]
97
+ ]);
98
+ globalThis.expect(Object.entries(AWS_Tags$ReventlessAws.withoutEmpty(tags))).toEqual([[
99
+ "Name",
100
+ "SchedulerScheduleGroup"
101
+ ]]);
102
+ });
103
+ });
104
+
105
+ let Runtime;
106
+
107
+ export {
108
+ Runtime,
109
+ roleArn,
110
+ queueArn,
111
+ fifoArn,
112
+ setAt,
113
+ input,
114
+ }
115
+ /* 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
- rate: Reventless.Schedule.Minutes(5),
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
- rate: {
95
- TAG: "Minutes",
96
- _0: 5
94
+ time: {
95
+ TAG: "Every",
96
+ _0: {
97
+ TAG: "Minutes",
98
+ _0: 5
99
+ }
97
100
  },
98
101
  payload: "{}"
99
102
  };