@reventlessdev/reventless-aws 3.0.0-alpha.222 → 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 +23 -0
- package/package.json +6 -6
- package/src/Platform.res +3 -3
- package/src/Platform.res.mjs +3 -3
- package/src/adapter/Adapter_Helpers.res +6 -0
- package/src/adapter/Adapter_Helpers.res.mjs +6 -0
- package/src/adapter/CommandGenerator/CommandGeneratorResolvers_AppSync.res +1 -1
- 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/adapter/Mcp/MCP_Lambda.res +4 -2
- package/src/adapter/Mcp/MCP_Lambda.res.mjs +3 -2
- package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +1 -1
- package/src/adapter/Runtime/{AdminEventCollectorEntryPoint.mjs → EventCollectorEntryPoint.mjs} +5 -5
- package/src/components/Api/AppSync_SdlDecorate.res +1 -1
- package/src/plugin/cloner/ClonerRunner_Fargate.res +1 -1
- package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +2 -2
- package/src/plugin/runtime/PluginRuntime_Builder.res +9 -5
- package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +3 -2
- package/src/util/Util_DeadLetterQueue.res +13 -3
- package/src/util/Util_DeadLetterQueue.res.mjs +2 -2
- package/tests/AppSync_AdapterTest.res +7 -7
- package/tests/AppSync_AdapterTest.res.mjs +8 -8
- package/tests/CommandTopicQueuePolicyTest.res +65 -0
- package/tests/CommandTopicQueuePolicyTest.res.mjs +53 -0
- package/tests/MCP_LambdaTest.res +1 -1
- package/tests/MCP_LambdaTest.res.mjs +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,29 @@
|
|
|
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
|
+
|
|
14
|
+
# 3.0.0-alpha.223 (2026-07-22)
|
|
15
|
+
|
|
16
|
+
* refactor!: retire the stale "Admin" vocabulary for platform-owned resources ([2770bd9](https://github.com/ReventlessDev/reventless-core/commit/2770bd96f309142bdfc65b9f36bd1a3ef60641bd))
|
|
17
|
+
|
|
18
|
+
### BREAKING CHANGES
|
|
19
|
+
|
|
20
|
+
* platform-owned resources are renamed Admin* -> Platform*
|
|
21
|
+
(PlatformDcbEventLog, PlatformDcbCmdTopic, PlatformEventColl). This replaces
|
|
22
|
+
a DynamoDB table, an SQS queue and an SNS topic — wipe and redeploy rather
|
|
23
|
+
than migrate. Both alpha AdminDcbEventLog tables held 0 items at the time of
|
|
24
|
+
the rename. MCP resource URI templates change from Admin/<field>/{id} to
|
|
25
|
+
Platform/<field>/{id}; MCP tool names are unaffected.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
6
29
|
# 3.0.0-alpha.222 (2026-07-22)
|
|
7
30
|
|
|
8
31
|
* refactor!: retire the stale "core" vocabulary for platform things ([34e7480](https://github.com/ReventlessDev/reventless-core/commit/34e7480992bd58906a250b0a1ce6ff2c5ba45260))
|
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": {
|
|
@@ -9,16 +9,16 @@
|
|
|
9
9
|
"sury": "11.0.0-alpha.4",
|
|
10
10
|
"uuid": "^13.0.0",
|
|
11
11
|
"@reventlessdev/rescript-aws-sdk": "2.2.0-alpha.24",
|
|
12
|
-
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.57",
|
|
13
12
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.30",
|
|
14
13
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.9",
|
|
14
|
+
"@reventlessdev/rescript-pulumi-aws": "2.4.0-alpha.57",
|
|
15
15
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.17",
|
|
16
16
|
"@reventlessdev/rescript-uuid": "1.1.0-alpha.17",
|
|
17
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.176",
|
|
18
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.40",
|
|
19
|
-
"@reventlessdev/reventless-interop": "3.0.0-alpha.29",
|
|
20
17
|
"@reventlessdev/reventless-infra": "3.0.0-alpha.104",
|
|
21
|
-
"@reventlessdev/reventless-
|
|
18
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.177",
|
|
19
|
+
"@reventlessdev/reventless-interop": "3.0.0-alpha.29",
|
|
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",
|
package/src/Platform.res
CHANGED
|
@@ -154,7 +154,7 @@ module MakeWithConfig = (
|
|
|
154
154
|
let adminSourceSdl = (): string =>
|
|
155
155
|
assembleCanonicalSourceSdl(
|
|
156
156
|
~baseFragment=AppSync_Adapter.injectAwsAuthAll(
|
|
157
|
-
ReventlessCore.
|
|
157
|
+
ReventlessCore.Platform_AdminApi.baseFragment(~cloner=Config.cloner),
|
|
158
158
|
~group="Admin",
|
|
159
159
|
),
|
|
160
160
|
)
|
|
@@ -850,7 +850,7 @@ module MakeWithConfig = (
|
|
|
850
850
|
// admin-prefixed `Platform_Plugin` / `Platform_Plugins` SDL fields declared by
|
|
851
851
|
// PluginBaseFragment.queryEntries. Field-name alignment is handled by the
|
|
852
852
|
// queryFieldNamesRegistry entries that Platform_Admin.construct populates
|
|
853
|
-
// from
|
|
853
|
+
// from Platform_AdminApi.queryEntries before this builder runs.
|
|
854
854
|
module PluginReadModel = ReadModel_Builder_Single_Stream.Make(
|
|
855
855
|
ReventlessCore.PluginsReadModelSpec,
|
|
856
856
|
PluginReadModelMappings,
|
|
@@ -1124,7 +1124,7 @@ module MakeWithConfig = (
|
|
|
1124
1124
|
|
|
1125
1125
|
module PluginExtensionPoint = Plugin_ExtensionPoint_Builder.MakeWithConfig({
|
|
1126
1126
|
// Cross-plugin SNS subscription management runs in the bundled
|
|
1127
|
-
// AdminEventCollector Lambda (
|
|
1127
|
+
// AdminEventCollector Lambda (EventCollectorEntryPoint.mjs), not in
|
|
1128
1128
|
// this deploy-time EP Lambda — which only handles incoming commands
|
|
1129
1129
|
// (Heartbeat, ForwardCommand). None here keeps the deploy-time path
|
|
1130
1130
|
// unchanged; the .mjs entry point supplies a real implementation.
|
package/src/Platform.res.mjs
CHANGED
|
@@ -15,7 +15,6 @@ import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js
|
|
|
15
15
|
import * as Plugin$ReventlessAws from "./components/Plugin.res.mjs";
|
|
16
16
|
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
17
17
|
import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
|
|
18
|
-
import * as AdminApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/AdminApi.res.mjs";
|
|
19
18
|
import * as Scheduler$ReventlessAws from "./components/Scheduler.res.mjs";
|
|
20
19
|
import * as Aggregate$ReventlessCore from "@reventlessdev/reventless-core/src/components/Aggregate/Aggregate.res.mjs";
|
|
21
20
|
import * as Component$ReventlessCore from "@reventlessdev/reventless-core/src/components/Component.res.mjs";
|
|
@@ -44,6 +43,7 @@ import * as NoEventMappings$ReventlessInfra from "@reventlessdev/reventless-infr
|
|
|
44
43
|
import * as Util_HostUiDomain$ReventlessAws from "./util/Util_HostUiDomain.res.mjs";
|
|
45
44
|
import * as DcbEventLogStorage$ReventlessAws from "./adapter/DcbEventLog/DcbEventLogStorage.res.mjs";
|
|
46
45
|
import * as ExtensionMapping$ReventlessInfra from "@reventlessdev/reventless-infra/src/types/ExtensionMapping.res.mjs";
|
|
46
|
+
import * as Platform_AdminApi$ReventlessCore from "@reventlessdev/reventless-core/src/admin/Platform_AdminApi.res.mjs";
|
|
47
47
|
import * as PluginsProjection$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginsProjection.res.mjs";
|
|
48
48
|
import * as StateTopic_AppSync$ReventlessAws from "./adapter/StateTopic/StateTopic_AppSync.res.mjs";
|
|
49
49
|
import * as AppSync_SdlDecorate$ReventlessAws from "./components/Api/AppSync_SdlDecorate.res.mjs";
|
|
@@ -148,7 +148,7 @@ function MakeWithConfig(Config) {
|
|
|
148
148
|
subscriptionSources: []
|
|
149
149
|
});
|
|
150
150
|
let adminSourceSdl = () => {
|
|
151
|
-
let baseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(
|
|
151
|
+
let baseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(Platform_AdminApi$ReventlessCore.baseFragment(Config.cloner), "Admin", undefined);
|
|
152
152
|
return AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(baseFragment));
|
|
153
153
|
};
|
|
154
154
|
let match;
|
|
@@ -1123,7 +1123,7 @@ function Make($star) {
|
|
|
1123
1123
|
subscriptionSources: []
|
|
1124
1124
|
});
|
|
1125
1125
|
let adminSourceSdl = () => {
|
|
1126
|
-
let baseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(
|
|
1126
|
+
let baseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(Platform_AdminApi$ReventlessCore.baseFragment(false), "Admin", undefined);
|
|
1127
1127
|
return AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(baseFragment));
|
|
1128
1128
|
};
|
|
1129
1129
|
let match;
|
|
@@ -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,
|
|
@@ -190,7 +190,7 @@ let makeDcb = (
|
|
|
190
190
|
~fieldNames: array<string>,
|
|
191
191
|
~tags: array<string>,
|
|
192
192
|
// Admin (onAdminApi) DCB slices expose ONLY their mutation fields (declared in the
|
|
193
|
-
// static
|
|
193
|
+
// static Platform_AdminApi.baseFragment). The static base does NOT declare their `on<Field>`
|
|
194
194
|
// subscription counterparts, so creating Source-C subscription resolvers would fail
|
|
195
195
|
// with CreateResolver NotFound. Suppress subscriptions for admin; plugins keep them
|
|
196
196
|
// (their generated fragment declares the matching subscription fields).
|
|
@@ -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
|
|