@reventlessdev/reventless-aws 3.0.0-alpha.223 → 3.0.0-alpha.224
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 +8 -0
- package/package.json +5 -5
- package/src/adapter/Adapter_Helpers.res +6 -0
- package/src/adapter/Adapter_Helpers.res.mjs +6 -0
- package/src/adapter/CommandTopic/CommandTopicChannel_Helpers.res +65 -47
- package/src/adapter/CommandTopic/CommandTopicChannel_Helpers.res.mjs +52 -39
- package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res +61 -59
- package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res.mjs +10 -16
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +32 -30
- package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +20 -22
- package/src/util/Util_DeadLetterQueue.res +13 -3
- package/src/util/Util_DeadLetterQueue.res.mjs +2 -2
- package/tests/CommandTopicQueuePolicyTest.res +65 -0
- package/tests/CommandTopicQueuePolicyTest.res.mjs +53 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.224 (2026-07-22)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **aws:** register the QueuePolicy -> Queue dependency so destroys order correctly ([81cf2b3](https://github.com/ReventlessDev/reventless-core/commit/81cf2b3198fea7ad280817d2414102089ba0f13d))
|
|
11
|
+
* **aws:** scope the CommandTopic queue policy's EventBridge send grant ([2f5f148](https://github.com/ReventlessDev/reventless-core/commit/2f5f148f4d73b2997633207f071c8d055960cc75))
|
|
12
|
+
|
|
13
|
+
|
|
6
14
|
# 3.0.0-alpha.223 (2026-07-22)
|
|
7
15
|
|
|
8
16
|
* refactor!: retire the stale "Admin" vocabulary for platform-owned resources ([2770bd9](https://github.com/ReventlessDev/reventless-core/commit/2770bd96f309142bdfc65b9f36bd1a3ef60641bd))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.224",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
"@reventlessdev/rescript-aws-sdk": "2.2.0-alpha.24",
|
|
12
12
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.30",
|
|
13
13
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.9",
|
|
14
|
-
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.17",
|
|
15
14
|
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.57",
|
|
15
|
+
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.17",
|
|
16
16
|
"@reventlessdev/rescript-uuid": "1.1.0-alpha.17",
|
|
17
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.104",
|
|
17
18
|
"@reventlessdev/reventless-core": "3.0.0-alpha.177",
|
|
18
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.80",
|
|
19
19
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.29",
|
|
20
|
-
"@reventlessdev/reventless-
|
|
21
|
-
"@reventlessdev/reventless-
|
|
20
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.80",
|
|
21
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.41"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"rescript": "12.3.0",
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// SQS queue ARN is `arn:aws:sqs:<region>:<accountId>:<name>` — segment index 4
|
|
2
|
+
// (0-based). Used to scope queue-policy service principals to this account via
|
|
3
|
+
// the aws:SourceAccount condition key.
|
|
4
|
+
let accountIdOfQueueArn = (queueArn: string) =>
|
|
5
|
+
queueArn->String.split(":")->Array.get(4)->Option.getOr("")
|
|
6
|
+
|
|
1
7
|
let dynamoDbStreamResources = eventTopicResources =>
|
|
2
8
|
eventTopicResources->ReventlessCore.Util.Adapter.filterSupportedResolvedResources([
|
|
3
9
|
AWS.DynamoDbStream.service,
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
3
4
|
import * as AWS$ReventlessAws from "./AWS.res.mjs";
|
|
4
5
|
import * as Util_Adapter$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Adapter.res.mjs";
|
|
5
6
|
|
|
7
|
+
function accountIdOfQueueArn(queueArn) {
|
|
8
|
+
return Stdlib_Option.getOr(queueArn.split(":")[4], "");
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
function dynamoDbStreamResources(eventTopicResources) {
|
|
7
12
|
return Util_Adapter$ReventlessCore.filterSupportedResolvedResources(eventTopicResources, [AWS$ReventlessAws.DynamoDbStream.service]);
|
|
8
13
|
}
|
|
@@ -29,6 +34,7 @@ function dynamoDbResources(resources) {
|
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
export {
|
|
37
|
+
accountIdOfQueueArn,
|
|
32
38
|
dynamoDbStreamResources,
|
|
33
39
|
snsResources,
|
|
34
40
|
sqsResources,
|
|
@@ -1,55 +1,73 @@
|
|
|
1
1
|
open PulumiAws.PolicyDocument
|
|
2
2
|
open Adapter_Helpers
|
|
3
3
|
|
|
4
|
+
let createQueuePolicyDocument = (~name, ~queueArn: string, ~lambdaArn: string) => {
|
|
5
|
+
let accountId = queueArn->accountIdOfQueueArn
|
|
6
|
+
PulumiAws.PolicyDocument.make(
|
|
7
|
+
~id=name ++ "QueuePolicy",
|
|
8
|
+
~statements=[
|
|
9
|
+
{
|
|
10
|
+
sid: "AllowLambdaToAccessQueue",
|
|
11
|
+
effect: Allow,
|
|
12
|
+
principal: Principals({
|
|
13
|
+
service: PrincipalId(AWS.Lambda.principal),
|
|
14
|
+
}),
|
|
15
|
+
actions: Actions(["sqs:ReceiveMessage", "sqs:DeleteMessage", "sqs:GetQueueAttributes"]),
|
|
16
|
+
resources: Resource(queueArn),
|
|
17
|
+
conditions: {
|
|
18
|
+
arnEquals: [("AWS:SourceArn", ConditionValue(lambdaArn))]->Dict.fromArray,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
// ScheduledPublisher creates EventBridge rules at runtime
|
|
23
|
+
// (PutRule/PutTargets) targeting this queue directly, and EventBridge
|
|
24
|
+
// authorises SQS targets against the queue's resource policy — so the
|
|
25
|
+
// grant cannot name a single rule ARN. Scope it to rules in this
|
|
26
|
+
// account instead: SourceAccount closes the cross-account hole,
|
|
27
|
+
// ArnLike narrows the in-account surface to Events rules.
|
|
28
|
+
sid: "AllowCloudWatchEventsToSendToQueue",
|
|
29
|
+
effect: Allow,
|
|
30
|
+
principal: Principals({
|
|
31
|
+
service: PrincipalId(AWS.CloudwatchEventRule.principal),
|
|
32
|
+
}),
|
|
33
|
+
actions: Actions(["sqs:SendMessage"]),
|
|
34
|
+
resources: Resource(queueArn),
|
|
35
|
+
conditions: {
|
|
36
|
+
stringEquals: [("aws:SourceAccount", ConditionValue(accountId))]->Dict.fromArray,
|
|
37
|
+
arnLike: [
|
|
38
|
+
("aws:SourceArn", ConditionValue(`arn:aws:events:*:${accountId}:rule/*`)),
|
|
39
|
+
]->Dict.fromArray,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
)->toJsonString
|
|
44
|
+
}
|
|
45
|
+
|
|
4
46
|
let createQueuePolicy = (
|
|
5
47
|
queue: PulumiAws.SQS.Queue.t,
|
|
6
48
|
name,
|
|
7
49
|
lambda: Pulumi.Output.t<PulumiAws.Lambda.Function.t>,
|
|
8
50
|
opts,
|
|
9
51
|
) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
resources: Resource(queueArn),
|
|
30
|
-
conditions: {
|
|
31
|
-
arnEquals: [("AWS:SourceArn", ConditionValue(lambdaArn))]->Dict.fromArray,
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
sid: "AllowCloudWatchEventsToSendToQueue",
|
|
36
|
-
effect: Allow,
|
|
37
|
-
principal: Principals({
|
|
38
|
-
service: PrincipalId("events.amazonaws.com"),
|
|
39
|
-
}),
|
|
40
|
-
actions: Actions(["sqs:SendMessage"]),
|
|
41
|
-
resources: Resource(queueArn),
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
)
|
|
45
|
-
->toJsonString
|
|
46
|
-
->Pulumi.Input.make
|
|
47
|
-
let _queuePolicy = PulumiAws.SQS.QueuePolicy.make(
|
|
48
|
-
~name,
|
|
49
|
-
~args={queueUrl: queueId->Pulumi.Input.make, policy: queuePolicyDocument},
|
|
50
|
-
~opts=Some(opts),
|
|
51
|
-
)
|
|
52
|
-
})
|
|
52
|
+
// The document is a *value* derived from resolved ARNs, so it is built in an
|
|
53
|
+
// apply. The QueuePolicy itself stays outside: passing `queue.id` as an Output
|
|
54
|
+
// is what registers the policy -> queue dependency, without which Pulumi has
|
|
55
|
+
// no ordering constraint and can delete the queue first on a replacement,
|
|
56
|
+
// leaving the policy's delete polling a queue that no longer exists.
|
|
57
|
+
let queuePolicyDocument =
|
|
58
|
+
(queue.arn, lambda->Pulumi.Output.flatMap(lambda => lambda.arn))
|
|
59
|
+
->Pulumi.Output.all2
|
|
60
|
+
->Pulumi.Output.apply(((queueArn, lambdaArn)) =>
|
|
61
|
+
createQueuePolicyDocument(~name, ~queueArn, ~lambdaArn)
|
|
62
|
+
)
|
|
63
|
+
let _queuePolicy = PulumiAws.SQS.QueuePolicy.make(
|
|
64
|
+
~name,
|
|
65
|
+
~args={
|
|
66
|
+
queueUrl: queue.id->Pulumi.Output.asInput,
|
|
67
|
+
policy: queuePolicyDocument->Pulumi.Output.asInput,
|
|
68
|
+
},
|
|
69
|
+
~opts=Some(opts),
|
|
70
|
+
)
|
|
53
71
|
}
|
|
54
72
|
|
|
55
73
|
let createLambdaPolicy = (
|
|
@@ -100,10 +118,10 @@ let createLambdaPolicy = (
|
|
|
100
118
|
"dynamodb:UpdateItem",
|
|
101
119
|
"dynamodb:DeleteItem",
|
|
102
120
|
"dynamodb:BatchWriteItem",
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
121
|
+
// Cross-partition fences emit read-only `ConditionCheck`
|
|
122
|
+
// entries inside `TransactWriteItems` when a write
|
|
123
|
+
// doesn't touch the cross-partition tag's partition
|
|
124
|
+
// itself; this needs its own IAM action.
|
|
107
125
|
"dynamodb:ConditionCheckItem",
|
|
108
126
|
]),
|
|
109
127
|
resources: Resources(
|
|
@@ -5,53 +5,65 @@ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
|
5
5
|
import * as Output$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Output.res.mjs";
|
|
6
6
|
import * as Pulumi from "@pulumi/pulumi";
|
|
7
7
|
import * as Lambda$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Lambda/Lambda.res.mjs";
|
|
8
|
+
import * as AWS$ReventlessAws from "../AWS.res.mjs";
|
|
8
9
|
import * as Adapter$ReventlessCore from "@reventlessdev/reventless-core/src/adapter/Adapter.res.mjs";
|
|
9
10
|
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
10
11
|
import * as Adapter_Helpers$ReventlessAws from "../Adapter_Helpers.res.mjs";
|
|
11
12
|
import * as Util_EventSourceMapping$ReventlessAws from "../../util/Util_EventSourceMapping.res.mjs";
|
|
12
13
|
|
|
14
|
+
function createQueuePolicyDocument(name, queueArn, lambdaArn) {
|
|
15
|
+
let accountId = Adapter_Helpers$ReventlessAws.accountIdOfQueueArn(queueArn);
|
|
16
|
+
return PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "QueuePolicy", [
|
|
17
|
+
{
|
|
18
|
+
Sid: "AllowLambdaToAccessQueue",
|
|
19
|
+
Principal: {
|
|
20
|
+
Service: AWS$ReventlessAws.Lambda.principal
|
|
21
|
+
},
|
|
22
|
+
Effect: "Allow",
|
|
23
|
+
Action: [
|
|
24
|
+
"sqs:ReceiveMessage",
|
|
25
|
+
"sqs:DeleteMessage",
|
|
26
|
+
"sqs:GetQueueAttributes"
|
|
27
|
+
],
|
|
28
|
+
Resource: queueArn,
|
|
29
|
+
Condition: {
|
|
30
|
+
ArnEquals: Object.fromEntries([[
|
|
31
|
+
"AWS:SourceArn",
|
|
32
|
+
lambdaArn
|
|
33
|
+
]])
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
Sid: "AllowCloudWatchEventsToSendToQueue",
|
|
38
|
+
Principal: {
|
|
39
|
+
Service: AWS$ReventlessAws.CloudwatchEventRule.principal
|
|
40
|
+
},
|
|
41
|
+
Effect: "Allow",
|
|
42
|
+
Action: ["sqs:SendMessage"],
|
|
43
|
+
Resource: queueArn,
|
|
44
|
+
Condition: {
|
|
45
|
+
StringEquals: Object.fromEntries([[
|
|
46
|
+
"aws:SourceAccount",
|
|
47
|
+
accountId
|
|
48
|
+
]]),
|
|
49
|
+
ArnLike: Object.fromEntries([[
|
|
50
|
+
"aws:SourceArn",
|
|
51
|
+
`arn:aws:events:*:` + accountId + `:rule/*`
|
|
52
|
+
]])
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
]));
|
|
56
|
+
}
|
|
57
|
+
|
|
13
58
|
function createQueuePolicy(queue, name, lambda, opts) {
|
|
14
|
-
Pulumi.all([
|
|
59
|
+
let queuePolicyDocument = Pulumi.all([
|
|
15
60
|
queue.arn,
|
|
16
|
-
queue.id,
|
|
17
61
|
Output$Pulumi.flatMap(lambda, lambda => lambda.arn)
|
|
18
|
-
]).apply(param =>
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Principal: {
|
|
24
|
-
Service: "lambda.amazonaws.com"
|
|
25
|
-
},
|
|
26
|
-
Effect: "Allow",
|
|
27
|
-
Action: [
|
|
28
|
-
"sqs:ReceiveMessage",
|
|
29
|
-
"sqs:DeleteMessage",
|
|
30
|
-
"sqs:GetQueueAttributes"
|
|
31
|
-
],
|
|
32
|
-
Resource: queueArn,
|
|
33
|
-
Condition: {
|
|
34
|
-
ArnEquals: Object.fromEntries([[
|
|
35
|
-
"AWS:SourceArn",
|
|
36
|
-
param[2]
|
|
37
|
-
]])
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
Sid: "AllowCloudWatchEventsToSendToQueue",
|
|
42
|
-
Principal: {
|
|
43
|
-
Service: "events.amazonaws.com"
|
|
44
|
-
},
|
|
45
|
-
Effect: "Allow",
|
|
46
|
-
Action: ["sqs:SendMessage"],
|
|
47
|
-
Resource: queueArn
|
|
48
|
-
}
|
|
49
|
-
]));
|
|
50
|
-
new (Aws.sqs.QueuePolicy)(name, {
|
|
51
|
-
policy: queuePolicyDocument,
|
|
52
|
-
queueUrl: param[1]
|
|
53
|
-
}, opts);
|
|
54
|
-
});
|
|
62
|
+
]).apply(param => createQueuePolicyDocument(name, param[0], param[1]));
|
|
63
|
+
new (Aws.sqs.QueuePolicy)(name, {
|
|
64
|
+
policy: queuePolicyDocument,
|
|
65
|
+
queueUrl: queue.id
|
|
66
|
+
}, opts);
|
|
55
67
|
}
|
|
56
68
|
|
|
57
69
|
function createLambdaPolicy(lambdaRole, name, queue, resources, opts) {
|
|
@@ -138,6 +150,7 @@ function subscribeLambda2SqsTopic(batchSize, lambda, name, queue, opts) {
|
|
|
138
150
|
}
|
|
139
151
|
|
|
140
152
|
export {
|
|
153
|
+
createQueuePolicyDocument,
|
|
141
154
|
createQueuePolicy,
|
|
142
155
|
createLambdaPolicy,
|
|
143
156
|
subscribeLambda2SqsTopic,
|
|
@@ -10,64 +10,62 @@ let toResources = (eventTopics: ReventlessCore.EventTopic.allOutputs) =>
|
|
|
10
10
|
->Array.flatMap(outputs => outputs.resources)
|
|
11
11
|
->ReventlessCore.Adapter.resourcesToResolvedOutput
|
|
12
12
|
|
|
13
|
-
let createQueuePolicy = (queue: PulumiAws.SQS.Queue.t, name,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
arnLike: [
|
|
49
|
-
("aws:SourceArn", ConditionValue(`arn:aws:sns:*:${accountId}:*EventTopic-*`)),
|
|
50
|
-
]->Dict.fromArray,
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
],
|
|
54
|
-
)
|
|
55
|
-
->toJsonString
|
|
56
|
-
->Pulumi.Input.make
|
|
57
|
-
let _queuePolicy = {
|
|
58
|
-
PulumiAws.SQS.QueuePolicy.make(
|
|
59
|
-
~name,
|
|
60
|
-
~args={
|
|
61
|
-
queueUrl: queueId->Pulumi.Input.make,
|
|
62
|
-
policy: queuePolicyDocument,
|
|
13
|
+
let createQueuePolicy = (queue: PulumiAws.SQS.Queue.t, name, opts) => {
|
|
14
|
+
// The document is a *value* derived from the resolved ARN, so it is built in
|
|
15
|
+
// an apply. The QueuePolicy itself stays outside: passing `queue.id` as an
|
|
16
|
+
// Output is what registers the policy -> queue dependency, without which
|
|
17
|
+
// Pulumi has no ordering constraint and can delete the queue first on a
|
|
18
|
+
// replacement, leaving the policy's delete polling a queue that is gone.
|
|
19
|
+
let queuePolicyDocument = queue.arn->Pulumi.Output.apply(queueArn => {
|
|
20
|
+
// Scopes cross-plugin SNS sources to this account (aws:SourceAccount) and
|
|
21
|
+
// to topic names following the Reventless EventTopic naming convention
|
|
22
|
+
// (aws:SourceArn arnLike).
|
|
23
|
+
let accountId = queueArn->accountIdOfQueueArn
|
|
24
|
+
PulumiAws.PolicyDocument.make(
|
|
25
|
+
~id=name ++ "QueuePolicy",
|
|
26
|
+
~statements=[
|
|
27
|
+
// Single statement allows SendMessage from any SNS topic owned by
|
|
28
|
+
// this AWS account whose name matches the Reventless EventTopic
|
|
29
|
+
// naming convention. A per-topic arnEquals list would require a
|
|
30
|
+
// redeploy of the receiving plugin whenever the platform's
|
|
31
|
+
// manageSubscriptions hook creates a cross-plugin SNS subscription at
|
|
32
|
+
// runtime. Security boundary: SourceAccount keeps third-party-account
|
|
33
|
+
// topics out; ArnLike + SNS service principal further narrows accepted
|
|
34
|
+
// senders to in-account EventTopic resources.
|
|
35
|
+
{
|
|
36
|
+
sid: "AllowReceiveSnsEvents",
|
|
37
|
+
principal: Principals({
|
|
38
|
+
service: PrincipalIds([AWS.SNS.principal]),
|
|
39
|
+
}),
|
|
40
|
+
effect: Allow,
|
|
41
|
+
actions: Actions(["sqs:SendMessage"]),
|
|
42
|
+
resources: Resource(queueArn),
|
|
43
|
+
conditions: {
|
|
44
|
+
stringEquals: [("aws:SourceAccount", ConditionValue(accountId))]->Dict.fromArray,
|
|
45
|
+
arnLike: [
|
|
46
|
+
("aws:SourceArn", ConditionValue(`arn:aws:sns:*:${accountId}:*EventTopic-*`)),
|
|
47
|
+
]->Dict.fromArray,
|
|
63
48
|
},
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
)->toJsonString
|
|
52
|
+
})
|
|
53
|
+
let _queuePolicy = PulumiAws.SQS.QueuePolicy.make(
|
|
54
|
+
~name,
|
|
55
|
+
~args={
|
|
56
|
+
queueUrl: queue.id->Pulumi.Output.asInput,
|
|
57
|
+
policy: queuePolicyDocument->Pulumi.Output.asInput,
|
|
58
|
+
},
|
|
59
|
+
~opts=Some(opts),
|
|
60
|
+
)
|
|
68
61
|
}
|
|
69
62
|
|
|
70
|
-
let subscribeQueue2SnsTopic = (
|
|
63
|
+
let subscribeQueue2SnsTopic = (
|
|
64
|
+
queue,
|
|
65
|
+
name,
|
|
66
|
+
resources: array<ReventlessInfra.Adapter.resolvedResource>,
|
|
67
|
+
opts,
|
|
68
|
+
) => {
|
|
71
69
|
let _snsTopicSubscriptions = resources->Array.map(resource => {
|
|
72
70
|
log.debug(~comp="EventCollector", `subscribeToSnsTopic: ${name} -> ${resource.name}`)
|
|
73
71
|
let subscription = Util_SQS.subscribeToSnsTopic(
|
|
@@ -82,17 +80,21 @@ let subscribeQueue2SnsTopic = (queue, name, resources: array<ReventlessInfra.Ada
|
|
|
82
80
|
}
|
|
83
81
|
|
|
84
82
|
let connectSqsQueue2SnsTopics = (queue: PulumiAws.SQS.Queue.t, name, eventTopics, opts) => {
|
|
83
|
+
// The policy grants SendMessage to any in-account EventTopic by naming
|
|
84
|
+
// convention, so it does not depend on the resolved topic resources and is
|
|
85
|
+
// created outside the apply. Only the SNS subscriptions need them.
|
|
86
|
+
queue->createQueuePolicy(name, opts)
|
|
85
87
|
let _ =
|
|
86
88
|
(eventTopics->toResources, queue.id)
|
|
87
89
|
->Pulumi.Output.all2
|
|
88
90
|
->Pulumi.Output.apply(((eventTopicResources, queueId)) => {
|
|
89
91
|
log.debug(
|
|
90
92
|
~comp="EventCollector",
|
|
91
|
-
`connectSqsQueue2SnsTopics ${queueId}: ${eventTopicResources
|
|
93
|
+
`connectSqsQueue2SnsTopics ${queueId}: ${eventTopicResources
|
|
94
|
+
->Array.length
|
|
95
|
+
->Int.toString} topic resource(s)`,
|
|
92
96
|
)
|
|
93
|
-
|
|
94
|
-
queue->createQueuePolicy(name, snsResources, opts)
|
|
95
|
-
queue->subscribeQueue2SnsTopic(name, snsResources, opts)
|
|
97
|
+
queue->subscribeQueue2SnsTopic(name, eventTopicResources->snsResources, opts)
|
|
96
98
|
})
|
|
97
99
|
}
|
|
98
100
|
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Aws from "@pulumi/aws";
|
|
4
4
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
|
-
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
5
|
import * as Pulumi from "@pulumi/pulumi";
|
|
7
6
|
import * as Lambda$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Lambda/Lambda.res.mjs";
|
|
8
7
|
import * as AWS$ReventlessAws from "../AWS.res.mjs";
|
|
@@ -20,14 +19,10 @@ function toResources(eventTopics) {
|
|
|
20
19
|
return Adapter$ReventlessCore.resourcesToResolvedOutput(Object.values(eventTopics).flatMap(outputs => outputs.resources));
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
function createQueuePolicy(queue, name,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
]).apply(param => {
|
|
28
|
-
let queueArn = param[0];
|
|
29
|
-
let accountId = Stdlib_Option.getOr(queueArn.split(":")[4], "");
|
|
30
|
-
let queuePolicyDocument = PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "QueuePolicy", [{
|
|
22
|
+
function createQueuePolicy(queue, name, opts) {
|
|
23
|
+
let queuePolicyDocument = queue.arn.apply(queueArn => {
|
|
24
|
+
let accountId = Adapter_Helpers$ReventlessAws.accountIdOfQueueArn(queueArn);
|
|
25
|
+
return PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "QueuePolicy", [{
|
|
31
26
|
Sid: "AllowReceiveSnsEvents",
|
|
32
27
|
Principal: {
|
|
33
28
|
Service: [AWS$ReventlessAws.SNS.principal]
|
|
@@ -46,11 +41,11 @@ function createQueuePolicy(queue, name, _resources, opts) {
|
|
|
46
41
|
]])
|
|
47
42
|
}
|
|
48
43
|
}]));
|
|
49
|
-
new (Aws.sqs.QueuePolicy)(name, {
|
|
50
|
-
policy: queuePolicyDocument,
|
|
51
|
-
queueUrl: param[1]
|
|
52
|
-
}, opts);
|
|
53
44
|
});
|
|
45
|
+
new (Aws.sqs.QueuePolicy)(name, {
|
|
46
|
+
policy: queuePolicyDocument,
|
|
47
|
+
queueUrl: queue.id
|
|
48
|
+
}, opts);
|
|
54
49
|
}
|
|
55
50
|
|
|
56
51
|
function subscribeQueue2SnsTopic(queue, name, resources, opts) {
|
|
@@ -62,15 +57,14 @@ function subscribeQueue2SnsTopic(queue, name, resources, opts) {
|
|
|
62
57
|
}
|
|
63
58
|
|
|
64
59
|
function connectSqsQueue2SnsTopics(queue, name, eventTopics, opts) {
|
|
60
|
+
createQueuePolicy(queue, name, opts);
|
|
65
61
|
Pulumi.all([
|
|
66
62
|
toResources(eventTopics),
|
|
67
63
|
queue.id
|
|
68
64
|
]).apply(param => {
|
|
69
65
|
let eventTopicResources = param[0];
|
|
70
66
|
log.debug("EventCollector", undefined, `connectSqsQueue2SnsTopics ` + param[1] + `: ` + eventTopicResources.length.toString() + ` topic resource(s)`);
|
|
71
|
-
|
|
72
|
-
createQueuePolicy(queue, name, snsResources, opts);
|
|
73
|
-
subscribeQueue2SnsTopic(queue, name, snsResources, opts);
|
|
67
|
+
subscribeQueue2SnsTopic(queue, name, Adapter_Helpers$ReventlessAws.snsResources(eventTopicResources), opts);
|
|
74
68
|
});
|
|
75
69
|
}
|
|
76
70
|
|
|
@@ -114,38 +114,40 @@ let make = (
|
|
|
114
114
|
|
|
115
115
|
// SQS queue policy — allow SNS to send messages
|
|
116
116
|
let snsResource = eventTopicOutputs.resources->Array.getUnsafe(0)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
// The document is a *value* derived from resolved ARNs, so it is built in an
|
|
118
|
+
// apply. The QueuePolicy itself stays outside: passing `queue.id` as an Output
|
|
119
|
+
// is what registers the policy -> queue dependency, without which Pulumi has
|
|
120
|
+
// no ordering constraint and can delete the queue first on a replacement,
|
|
121
|
+
// leaving the policy's delete polling a queue that no longer exists.
|
|
122
|
+
let queuePolicyDocument =
|
|
123
|
+
(queue.arn, snsResource.urn)
|
|
124
|
+
->Pulumi.Output.all2
|
|
125
|
+
->Pulumi.Output.apply(((queueArn, snsUrn)) => {
|
|
121
126
|
open PolicyDocument
|
|
122
|
-
|
|
123
|
-
~
|
|
124
|
-
~
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
("aws:SourceArn", ConditionValues([snsUrn])),
|
|
138
|
-
]->Dict.fromArray,
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
],
|
|
142
|
-
)
|
|
143
|
-
->PolicyDocument.toJsonString
|
|
144
|
-
->Pulumi.Input.make,
|
|
145
|
-
},
|
|
146
|
-
~opts=Some(opts),
|
|
147
|
-
)
|
|
127
|
+
PolicyDocument.make(
|
|
128
|
+
~id=name ++ "EventLogSubQueuePolicy",
|
|
129
|
+
~statements=[
|
|
130
|
+
{
|
|
131
|
+
sid: "AllowSNSSend",
|
|
132
|
+
principal: Principals({service: PrincipalIds([AWS.SNS.principal])}),
|
|
133
|
+
effect: Allow,
|
|
134
|
+
actions: Actions(["sqs:SendMessage"]),
|
|
135
|
+
resources: Resource(queueArn),
|
|
136
|
+
conditions: {
|
|
137
|
+
arnEquals: [("aws:SourceArn", ConditionValues([snsUrn]))]->Dict.fromArray,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
)->PolicyDocument.toJsonString
|
|
148
142
|
})
|
|
143
|
+
let _queuePolicy = SQS.QueuePolicy.make(
|
|
144
|
+
~name=name ++ "EventLogSubQueuePolicy",
|
|
145
|
+
~args={
|
|
146
|
+
queueUrl: queue.id->Pulumi.Output.asInput,
|
|
147
|
+
policy: queuePolicyDocument->Pulumi.Output.asInput,
|
|
148
|
+
},
|
|
149
|
+
~opts=Some(opts),
|
|
150
|
+
)
|
|
149
151
|
|
|
150
152
|
// SNS → SQS subscription (raw delivery so body IS the event JSON)
|
|
151
153
|
let _subscription = Util_SQS.subscribeToSnsTopic(
|
|
@@ -85,30 +85,28 @@ function make(name, topicName, eventTopicOutputs, eventsApi, opts) {
|
|
|
85
85
|
sqsManagedSseEnabled: false
|
|
86
86
|
}, opts);
|
|
87
87
|
let snsResource = eventTopicOutputs.resources[0];
|
|
88
|
-
Pulumi.all([
|
|
88
|
+
let queuePolicyDocument = Pulumi.all([
|
|
89
89
|
queue.arn,
|
|
90
|
-
queue.id,
|
|
91
90
|
snsResource.urn
|
|
92
|
-
]).apply(param => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
});
|
|
91
|
+
]).apply(param => PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "EventLogSubQueuePolicy", [{
|
|
92
|
+
Sid: "AllowSNSSend",
|
|
93
|
+
Principal: {
|
|
94
|
+
Service: [AWS$ReventlessAws.SNS.principal]
|
|
95
|
+
},
|
|
96
|
+
Effect: "Allow",
|
|
97
|
+
Action: ["sqs:SendMessage"],
|
|
98
|
+
Resource: param[0],
|
|
99
|
+
Condition: {
|
|
100
|
+
ArnEquals: Object.fromEntries([[
|
|
101
|
+
"aws:SourceArn",
|
|
102
|
+
[param[1]]
|
|
103
|
+
]])
|
|
104
|
+
}
|
|
105
|
+
}])));
|
|
106
|
+
new (Aws.sqs.QueuePolicy)(name + "EventLogSubQueuePolicy", {
|
|
107
|
+
policy: queuePolicyDocument,
|
|
108
|
+
queueUrl: queue.id
|
|
109
|
+
}, opts);
|
|
112
110
|
Util_SQS$ReventlessAws.subscribeToSnsTopic(queue, name + "EventLogSub", name, snsResource, opts);
|
|
113
111
|
let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "EventLogSubRole", Pulumi.output(AWS$ReventlessAws.Lambda.principal), AWS_Tags$ReventlessAws.make(name + "EventLogSubRole", EventTopic$ReventlessCore.componentType, "Identity", undefined, name, undefined, undefined, undefined), opts);
|
|
114
112
|
Pulumi.all([
|
|
@@ -122,7 +122,7 @@ let createQueuePolicyDocument = (name, queueArn: string, handlerArn: string) =>
|
|
|
122
122
|
let _ =
|
|
123
123
|
(queue.id, queue.arn, fifoQueue.id, fifoQueue.arn, handler.arn)
|
|
124
124
|
->Pulumi.Output.all5
|
|
125
|
-
->Pulumi.Output.apply(((
|
|
125
|
+
->Pulumi.Output.apply(((_queueId, queueArn, _fifoQueueId, fifoQueueArn, handlerArn)) => {
|
|
126
126
|
let lambdaPolicyDocument = PulumiAws.PolicyDocument.make(
|
|
127
127
|
~id=name ++ "SQSPolicy",
|
|
128
128
|
~statements=[
|
|
@@ -152,10 +152,20 @@ let _ =
|
|
|
152
152
|
~opts,
|
|
153
153
|
)
|
|
154
154
|
|
|
155
|
+
// `queueUrl` takes the queue's Output, not the resolved `queueId` string:
|
|
156
|
+
// that is what registers the policy -> queue dependency, without which
|
|
157
|
+
// Pulumi has no ordering constraint and can delete the queue first on a
|
|
158
|
+
// replacement, leaving the policy's delete polling a queue that is gone.
|
|
159
|
+
//
|
|
160
|
+
// Unlike the channel helpers, these two stay inside the apply. This module
|
|
161
|
+
// creates its resources at import time and this apply resolves late enough
|
|
162
|
+
// to race Jest's teardown; reshaping it (dropping the now-unused ids to
|
|
163
|
+
// narrow the tuple) shifts that timing and crashes the AWS unit suites, so
|
|
164
|
+
// `_queueId` / `_fifoQueueId` are deliberately kept.
|
|
155
165
|
let _attachQueuePolicy = PulumiAws.SQS.QueuePolicy.make(
|
|
156
166
|
~name,
|
|
157
167
|
~args={
|
|
158
|
-
queueUrl:
|
|
168
|
+
queueUrl: queue.id->Pulumi.Output.asInput,
|
|
159
169
|
policy: createQueuePolicyDocument(name, queueArn, handlerArn),
|
|
160
170
|
},
|
|
161
171
|
~opts=Some(opts),
|
|
@@ -163,7 +173,7 @@ let _ =
|
|
|
163
173
|
let _attachFifoQueuePolicy = PulumiAws.SQS.QueuePolicy.make(
|
|
164
174
|
~name=nameFifo,
|
|
165
175
|
~args={
|
|
166
|
-
queueUrl:
|
|
176
|
+
queueUrl: fifoQueue.id->Pulumi.Output.asInput,
|
|
167
177
|
policy: createQueuePolicyDocument(nameFifo, fifoQueueArn, handlerArn),
|
|
168
178
|
},
|
|
169
179
|
~opts=Some(opts),
|
|
@@ -132,11 +132,11 @@ Pulumi.all([
|
|
|
132
132
|
}, opts);
|
|
133
133
|
new (Aws.sqs.QueuePolicy)(name, {
|
|
134
134
|
policy: createQueuePolicyDocument(name, queueArn, handlerArn),
|
|
135
|
-
queueUrl:
|
|
135
|
+
queueUrl: queue.id
|
|
136
136
|
}, opts);
|
|
137
137
|
new (Aws.sqs.QueuePolicy)(nameFifo, {
|
|
138
138
|
policy: createQueuePolicyDocument(nameFifo, fifoQueueArn, handlerArn),
|
|
139
|
-
queueUrl:
|
|
139
|
+
queueUrl: fifoQueue.id
|
|
140
140
|
}, opts);
|
|
141
141
|
});
|
|
142
142
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
open JestGlobals
|
|
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.
|
|
8
|
+
|
|
9
|
+
let queueArn = "arn:aws:sqs:eu-west-1:123456789012:CatalogAggrCmdTopic-5150050"
|
|
10
|
+
let lambdaArn = "arn:aws:lambda:eu-west-1:123456789012:function:AllAggregates-1a2b3c4"
|
|
11
|
+
|
|
12
|
+
let policy =
|
|
13
|
+
CommandTopicChannel_Helpers.createQueuePolicyDocument(
|
|
14
|
+
~name="CatalogAggrCmdTopic",
|
|
15
|
+
~queueArn,
|
|
16
|
+
~lambdaArn,
|
|
17
|
+
)->JSON.parseOrThrow
|
|
18
|
+
|
|
19
|
+
let field = (json, key) =>
|
|
20
|
+
json->JSON.Decode.object->Option.flatMap(o => o->Dict.get(key))
|
|
21
|
+
|
|
22
|
+
let statement = sid =>
|
|
23
|
+
policy
|
|
24
|
+
->field("Statement")
|
|
25
|
+
->Option.flatMap(JSON.Decode.array)
|
|
26
|
+
->Option.getOr([])
|
|
27
|
+
->Array.find(s => s->field("Sid")->Option.flatMap(JSON.Decode.string) == Some(sid))
|
|
28
|
+
|
|
29
|
+
let condition = (sid, operator, key) =>
|
|
30
|
+
statement(sid)
|
|
31
|
+
->Option.flatMap(s => s->field("Condition"))
|
|
32
|
+
->Option.flatMap(c => c->field(operator))
|
|
33
|
+
->Option.flatMap(o => o->field(key))
|
|
34
|
+
->Option.flatMap(JSON.Decode.string)
|
|
35
|
+
|
|
36
|
+
describe("CommandTopic queue policy — EventBridge grant", () => {
|
|
37
|
+
testSync("is scoped to this account", () => {
|
|
38
|
+
expect(
|
|
39
|
+
condition("AllowCloudWatchEventsToSendToQueue", "StringEquals", "aws:SourceAccount"),
|
|
40
|
+
)->toEqual(Some("123456789012"))
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
testSync("is scoped to EventBridge rule ARNs in this account", () => {
|
|
44
|
+
expect(condition("AllowCloudWatchEventsToSendToQueue", "ArnLike", "aws:SourceArn"))->toEqual(
|
|
45
|
+
Some("arn:aws:events:*:123456789012:rule/*"),
|
|
46
|
+
)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
testSync("names the EventBridge service principal", () => {
|
|
50
|
+
let principal =
|
|
51
|
+
statement("AllowCloudWatchEventsToSendToQueue")
|
|
52
|
+
->Option.flatMap(s => s->field("Principal"))
|
|
53
|
+
->Option.flatMap(p => p->field("Service"))
|
|
54
|
+
->Option.flatMap(JSON.Decode.string)
|
|
55
|
+
expect(principal)->toEqual(Some("events.amazonaws.com"))
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe("CommandTopic queue policy — Lambda grant", () => {
|
|
60
|
+
testSync("stays pinned to the consuming function ARN", () => {
|
|
61
|
+
expect(condition("AllowLambdaToAccessQueue", "ArnEquals", "AWS:SourceArn"))->toEqual(
|
|
62
|
+
Some(lambdaArn),
|
|
63
|
+
)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
4
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
6
|
+
import * as CommandTopicChannel_Helpers$ReventlessAws from "../src/adapter/CommandTopic/CommandTopicChannel_Helpers.res.mjs";
|
|
7
|
+
|
|
8
|
+
let queueArn = "arn:aws:sqs:eu-west-1:123456789012:CatalogAggrCmdTopic-5150050";
|
|
9
|
+
|
|
10
|
+
let lambdaArn = "arn:aws:lambda:eu-west-1:123456789012:function:AllAggregates-1a2b3c4";
|
|
11
|
+
|
|
12
|
+
let policy = JSON.parse(CommandTopicChannel_Helpers$ReventlessAws.createQueuePolicyDocument("CatalogAggrCmdTopic", queueArn, lambdaArn));
|
|
13
|
+
|
|
14
|
+
function field(json, key) {
|
|
15
|
+
return Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(json), o => o[key]);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function statement(sid) {
|
|
19
|
+
return Stdlib_Option.getOr(Stdlib_Option.flatMap(field(policy, "Statement"), Stdlib_JSON.Decode.array), []).find(s => Primitive_object.equal(Stdlib_Option.flatMap(field(s, "Sid"), Stdlib_JSON.Decode.string), sid));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function condition(sid, operator, key) {
|
|
23
|
+
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
|
+
|
|
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");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
globalThis.describe("CommandTopic queue policy — Lambda grant", () => {
|
|
40
|
+
globalThis.test("stays pinned to the consuming function ARN", () => {
|
|
41
|
+
globalThis.expect(condition("AllowLambdaToAccessQueue", "ArnEquals", "AWS:SourceArn")).toEqual(lambdaArn);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
queueArn,
|
|
47
|
+
lambdaArn,
|
|
48
|
+
policy,
|
|
49
|
+
field,
|
|
50
|
+
statement,
|
|
51
|
+
condition,
|
|
52
|
+
}
|
|
53
|
+
/* policy Not a pure module */
|