@reventlessdev/reventless-aws 3.0.0-alpha.251 → 3.0.0-alpha.252
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 +11 -0
- package/package.json +5 -5
- package/src/Platform.res +58 -68
- package/src/Platform.res.mjs +60 -102
- package/src/adapter/Geocoder/Geocoder_AwsLocation_Ops.res +13 -11
- package/src/adapter/Geocoder/Geocoder_AwsLocation_Ops.res.mjs +16 -21
- package/src/adapter/Upload/Upload_Presign_S3.res +179 -92
- package/src/adapter/Upload/Upload_Presign_S3.res.mjs +129 -39
- package/src/adapter/Upload/Upload_Presign_S3_Ops.res +227 -104
- package/src/adapter/Upload/Upload_Presign_S3_Ops.res.mjs +195 -75
- package/src/util/Util_DeadLetterQueue.res +26 -0
- package/src/util/Util_DeadLetterQueue.res.mjs +9 -0
- package/tests/Upload_ReleaseRuleTest.res +90 -0
- package/tests/Upload_ReleaseRuleTest.res.mjs +110 -0
|
@@ -12,25 +12,11 @@ function getEnv(k) {
|
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
function
|
|
16
|
-
return Object.fromEntries([
|
|
17
|
-
[
|
|
15
|
+
function jsonHeaders() {
|
|
16
|
+
return Object.fromEntries([[
|
|
18
17
|
"content-type",
|
|
19
18
|
"application/json"
|
|
20
|
-
]
|
|
21
|
-
[
|
|
22
|
-
"access-control-allow-origin",
|
|
23
|
-
"*"
|
|
24
|
-
],
|
|
25
|
-
[
|
|
26
|
-
"access-control-allow-methods",
|
|
27
|
-
"GET,OPTIONS"
|
|
28
|
-
],
|
|
29
|
-
[
|
|
30
|
-
"access-control-allow-headers",
|
|
31
|
-
"*"
|
|
32
|
-
]
|
|
33
|
-
]);
|
|
19
|
+
]]);
|
|
34
20
|
}
|
|
35
21
|
|
|
36
22
|
function readQueryParam(event) {
|
|
@@ -59,7 +45,10 @@ async function handler(event) {
|
|
|
59
45
|
if (indexName === "" || q === "") {
|
|
60
46
|
return {
|
|
61
47
|
statusCode: 200,
|
|
62
|
-
headers:
|
|
48
|
+
headers: Object.fromEntries([[
|
|
49
|
+
"content-type",
|
|
50
|
+
"application/json"
|
|
51
|
+
]]),
|
|
63
52
|
body: "[]"
|
|
64
53
|
};
|
|
65
54
|
}
|
|
@@ -101,7 +90,10 @@ async function handler(event) {
|
|
|
101
90
|
});
|
|
102
91
|
return {
|
|
103
92
|
statusCode: 200,
|
|
104
|
-
headers:
|
|
93
|
+
headers: Object.fromEntries([[
|
|
94
|
+
"content-type",
|
|
95
|
+
"application/json"
|
|
96
|
+
]]),
|
|
105
97
|
body: JSON.stringify(results)
|
|
106
98
|
};
|
|
107
99
|
} catch (raw_exn) {
|
|
@@ -109,7 +101,10 @@ async function handler(event) {
|
|
|
109
101
|
console.error("Geocoder: search failed", exn);
|
|
110
102
|
return {
|
|
111
103
|
statusCode: 200,
|
|
112
|
-
headers:
|
|
104
|
+
headers: Object.fromEntries([[
|
|
105
|
+
"content-type",
|
|
106
|
+
"application/json"
|
|
107
|
+
]]),
|
|
113
108
|
body: "[]"
|
|
114
109
|
};
|
|
115
110
|
}
|
|
@@ -117,7 +112,7 @@ async function handler(event) {
|
|
|
117
112
|
|
|
118
113
|
export {
|
|
119
114
|
getEnv,
|
|
120
|
-
|
|
115
|
+
jsonHeaders,
|
|
121
116
|
readQueryParam,
|
|
122
117
|
handler,
|
|
123
118
|
}
|
|
@@ -1,93 +1,119 @@
|
|
|
1
|
-
// Direct-to-S3 upload
|
|
1
|
+
// Direct-to-S3 upload service, mounted on the platform GraphQL API.
|
|
2
2
|
//
|
|
3
|
-
// Deploy-time only: `make` provisions
|
|
3
|
+
// Deploy-time only: `make` provisions one compiled-EntryPoint Lambda (a plain
|
|
4
4
|
// `Lambda.Function` whose code archive re-exports `handler` from the compiled,
|
|
5
5
|
// type-checked runtime module `Upload_Presign_S3_Ops` and ships the shared ESM
|
|
6
6
|
// resolve-hook loader), an IAM execution role scoped to CloudWatch Logs +
|
|
7
|
-
// `s3:PutObject` on the
|
|
8
|
-
//
|
|
7
|
+
// `s3:PutObject`/`s3:DeleteObject`/`s3:GetObject` on the declared stores' prefixes,
|
|
8
|
+
// an AppSync Lambda data source, and two resolvers — `Mutation.Upload_Presign` and
|
|
9
|
+
// `Mutation.Upload_Release` — on the platform API.
|
|
9
10
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
11
|
+
// There is no Function URL and no anonymous surface: authentication is the platform
|
|
12
|
+
// API's Cognito authorizer, and the verified caller identity reaches the handler in
|
|
13
|
+
// the resolver payload (see the JS resolver code below). This is route B of
|
|
14
|
+
// [docs/plans/upload-release-path.md]; the anonymous Function URL and the unverified
|
|
15
|
+
// `decodeJwtSub` the mint side used to carry are gone.
|
|
16
|
+
//
|
|
17
|
+
// Why an EntryPoint and not a Pulumi `CallbackFunction`: the handler needs the AWS
|
|
18
|
+
// SDK v3 S3 presigner. A serialized closure bakes the deploy machine's
|
|
19
|
+
// version-specific SDK internals into the archive but then resolves `@smithy/*` and
|
|
20
|
+
// `@aws-sdk/*` transitives from independently-versioned layer/runtime sources that
|
|
21
|
+
// disagree at cold start. Shipping the compiled `_Ops` module with bare `@aws-sdk/*`
|
|
22
|
+
// imports, resolved through the resolve-hook, loads one internally consistent SDK.
|
|
23
|
+
// The runtime logic lives in [Upload_Presign_S3_Ops.res].
|
|
20
24
|
|
|
21
25
|
open PulumiAws
|
|
22
26
|
|
|
23
|
-
type serviceOutputs = {
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
type serviceOutputs = {resources: array<Pulumi.Output.t<string>>}
|
|
28
|
+
|
|
29
|
+
// A store the service can presign into and release from. `qualified` is the
|
|
30
|
+
// `{plugin}.{store}` name the caller passes as the mutation's `store` argument;
|
|
31
|
+
// `bucketName` is the physical bucket; `servedPrefix` is the prefix keys are rooted
|
|
32
|
+
// at (and therefore the prefix IAM is scoped to).
|
|
33
|
+
type uploadStore = {
|
|
34
|
+
qualified: string,
|
|
35
|
+
bucketName: Pulumi.Input.t<string>,
|
|
36
|
+
servedPrefix: string,
|
|
26
37
|
}
|
|
27
38
|
|
|
28
39
|
/**
|
|
29
|
-
The prefix presigned object keys are rooted at, and therefore the prefix the
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
Exported so the serve side reads the same constant the mint side writes. The two
|
|
33
|
-
have to agree exactly: a mismatch deploys green and 404s every object, because
|
|
34
|
-
nothing at deploy time compares a minted key against a cache behavior.
|
|
40
|
+
The prefix presigned object keys are rooted at, and therefore the prefix the store
|
|
41
|
+
must be served under for the returned `/{key}` ref to resolve. Exported so the serve
|
|
42
|
+
side reads the same constant the mint side writes.
|
|
35
43
|
*/
|
|
36
44
|
let defaultServedPrefix = "uploads"
|
|
37
45
|
|
|
46
|
+
// JS resolver code (APPSYNC_JS runtime): pass the caller's arguments and the
|
|
47
|
+
// authorizer-verified identity to the Lambda, tagging the operation so one Lambda
|
|
48
|
+
// serves both mutations. CORS and auth belong to the API, not to this code.
|
|
49
|
+
let invokeCode = (operation: string): Pulumi.Input.t<string> =>
|
|
50
|
+
`import { util } from '@aws-appsync/utils';
|
|
51
|
+
export function request(ctx) {
|
|
52
|
+
const id = ctx.identity;
|
|
53
|
+
return {
|
|
54
|
+
operation: 'Invoke',
|
|
55
|
+
payload: {
|
|
56
|
+
operation: '${operation}',
|
|
57
|
+
arguments: ctx.args,
|
|
58
|
+
identity: id != null && id.sub != null
|
|
59
|
+
? { sub: id.sub, username: id.username, claims: id.claims }
|
|
60
|
+
: null
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function response(ctx) {
|
|
65
|
+
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
|
|
66
|
+
return ctx.result;
|
|
67
|
+
}
|
|
68
|
+
`->Pulumi.Input.make
|
|
69
|
+
|
|
38
70
|
let make = (
|
|
39
|
-
~
|
|
40
|
-
~
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
~servedPrefix: string=defaultServedPrefix,
|
|
45
|
-
// Resource-name stem. Defaults to the single-service name this adapter had
|
|
46
|
-
// when a deployment could only have one store, so the existing service keeps
|
|
47
|
-
// its identity; declared stores each pass their own, because there is one
|
|
48
|
-
// service per store.
|
|
49
|
-
//
|
|
50
|
-
// One service per store rather than one service with wildcard write across
|
|
51
|
-
// stores: a single wide-scoped presigner reintroduces exactly the blast radius
|
|
52
|
-
// the per-store split exists to remove, and it does so invisibly — every
|
|
53
|
-
// upload still works.
|
|
54
|
-
~name: string="UploadPresignService",
|
|
55
|
-
~opts=?,
|
|
71
|
+
~api: Pulumi.Output.t<AppSync.GraphQLApi.t>,
|
|
72
|
+
~stores: array<uploadStore>,
|
|
73
|
+
~releaseWindowSeconds: int=900,
|
|
74
|
+
~name: string="UploadService",
|
|
75
|
+
~opts: Pulumi.ComponentResource.options,
|
|
56
76
|
): serviceOutputs => {
|
|
57
|
-
let
|
|
58
|
-
let opts =
|
|
59
|
-
opts->Option.map(ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions)
|
|
77
|
+
let opts = opts->ReventlessCore.Util.Pulumi.ComponentResourceOptions.toCustomResourceOptions
|
|
60
78
|
|
|
61
79
|
let lambdaRole = IAM.Role.makeWithDefaultPolicy(
|
|
62
|
-
~name=
|
|
80
|
+
~name=name ++ "Lambda",
|
|
63
81
|
~servicePrincipal=AWS.Lambda.principal->Pulumi.Output.make,
|
|
64
82
|
~tags=AWS.Tags.make(
|
|
65
|
-
~name=
|
|
83
|
+
~name=name ++ "Lambda",
|
|
66
84
|
~kind=ReventlessCore.ComponentType.Platform,
|
|
67
85
|
~role=Identity,
|
|
68
86
|
~scope=Platform,
|
|
69
87
|
),
|
|
70
|
-
~opts
|
|
88
|
+
~opts,
|
|
71
89
|
)
|
|
72
90
|
|
|
73
|
-
//
|
|
74
|
-
//
|
|
91
|
+
// Resolve every store's physical bucket name once; both the env map the handler
|
|
92
|
+
// reads and the IAM the presign/release grant needs are built from it.
|
|
93
|
+
let resolvedStores =
|
|
94
|
+
stores
|
|
95
|
+
->Array.map(s =>
|
|
96
|
+
s.bucketName
|
|
97
|
+
->Pulumi.Output.fromInput
|
|
98
|
+
->Pulumi.Output.apply(b => (s.qualified, b, s.servedPrefix))
|
|
99
|
+
)
|
|
100
|
+
->Pulumi.Output.all
|
|
101
|
+
|
|
102
|
+
// CloudWatch Logs plus least-privilege S3 on each declared store's own keys.
|
|
75
103
|
//
|
|
76
|
-
// Scoped to `{bucket}/{servedPrefix}
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
104
|
+
// Scoped to `{bucket}/{servedPrefix}/*` per store, never the whole bucket:
|
|
105
|
+
// `PutObject` presigns the upload, `DeleteObject` performs the release, and
|
|
106
|
+
// `GetObject` backs the `HeadObject` the age check reads. One Lambda covers every
|
|
107
|
+
// declared store (route B1) — but its reach is still the union of those prefixes,
|
|
108
|
+
// not a wildcard, so it cannot touch a key outside a declared store.
|
|
81
109
|
let _policy =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
->Pulumi.Output.apply(b => {
|
|
85
|
-
let arn = `arn:aws:s3:::${b}/${servedPrefix}/*`
|
|
110
|
+
resolvedStores->Pulumi.Output.apply(list => {
|
|
111
|
+
let arns = list->Array.map(((_, bucket, prefix)) => `arn:aws:s3:::${bucket}/${prefix}/*`)
|
|
86
112
|
let _ = IAM.RolePolicy.make(
|
|
87
|
-
~name
|
|
113
|
+
~name=name ++ "LambdaPolicy",
|
|
88
114
|
~args={
|
|
89
115
|
policy: PolicyDocument.make(
|
|
90
|
-
~id
|
|
116
|
+
~id=name ++ "LambdaPolicy",
|
|
91
117
|
~statements=[
|
|
92
118
|
{
|
|
93
119
|
sid: "AllowLambdaLogging",
|
|
@@ -96,10 +122,10 @@ let make = (
|
|
|
96
122
|
resources: Resource("arn:aws:logs:*:*:*"),
|
|
97
123
|
},
|
|
98
124
|
{
|
|
99
|
-
sid: "
|
|
125
|
+
sid: "AllowUploadObjectAccess",
|
|
100
126
|
effect: Allow,
|
|
101
|
-
actions:
|
|
102
|
-
resources:
|
|
127
|
+
actions: Actions(["s3:PutObject", "s3:DeleteObject", "s3:GetObject"]),
|
|
128
|
+
resources: Resources(arns),
|
|
103
129
|
},
|
|
104
130
|
],
|
|
105
131
|
)
|
|
@@ -107,10 +133,27 @@ let make = (
|
|
|
107
133
|
->Pulumi.Input.make,
|
|
108
134
|
role: lambdaRole.id->Pulumi.Output.asInput,
|
|
109
135
|
},
|
|
110
|
-
~opts
|
|
136
|
+
~opts,
|
|
111
137
|
)
|
|
112
138
|
})
|
|
113
139
|
|
|
140
|
+
// `UPLOAD_STORES` — the qualified-name → {bucket, prefix} map the handler resolves
|
|
141
|
+
// the caller's `store` argument against.
|
|
142
|
+
let uploadStoresJson =
|
|
143
|
+
resolvedStores->Pulumi.Output.apply(list =>
|
|
144
|
+
list
|
|
145
|
+
->Array.map(((qualified, bucket, prefix)) => (
|
|
146
|
+
qualified,
|
|
147
|
+
Dict.fromArray([
|
|
148
|
+
("bucket", JSON.Encode.string(bucket)),
|
|
149
|
+
("prefix", JSON.Encode.string(prefix)),
|
|
150
|
+
])->JSON.Encode.object,
|
|
151
|
+
))
|
|
152
|
+
->Dict.fromArray
|
|
153
|
+
->JSON.Encode.object
|
|
154
|
+
->JSON.stringify
|
|
155
|
+
)
|
|
156
|
+
|
|
114
157
|
// Bundle reventless-aws (the compiled `_Ops` handler lives inside it) and
|
|
115
158
|
// re-export its `handler`; buildCodeArchive also ships the ESM resolve-hook.
|
|
116
159
|
let packageDirs = Dict.fromArray([
|
|
@@ -131,7 +174,7 @@ let make = (
|
|
|
131
174
|
->Pulumi.Input.make
|
|
132
175
|
|
|
133
176
|
let lambda = Lambda.Function.make(
|
|
134
|
-
~name=
|
|
177
|
+
~name=name ++ "Lambda",
|
|
135
178
|
~args={
|
|
136
179
|
handler: "index.handler"->Pulumi.Input.make,
|
|
137
180
|
runtime: "nodejs22.x"->Pulumi.Input.make,
|
|
@@ -142,7 +185,7 @@ let make = (
|
|
|
142
185
|
timeout: 30->Pulumi.Input.make,
|
|
143
186
|
layers,
|
|
144
187
|
tags: AWS.Tags.make(
|
|
145
|
-
~name=
|
|
188
|
+
~name=name ++ "Lambda",
|
|
146
189
|
~kind=ReventlessCore.ComponentType.Platform,
|
|
147
190
|
~role=Runtime,
|
|
148
191
|
~scope=Platform,
|
|
@@ -151,43 +194,87 @@ let make = (
|
|
|
151
194
|
{
|
|
152
195
|
Lambda.Function.variables: Dict.fromArray([
|
|
153
196
|
("Environment", Pulumi.Pulumi.getStackName()->Pulumi.Input.make),
|
|
154
|
-
("
|
|
155
|
-
("
|
|
197
|
+
("UPLOAD_STORES", uploadStoresJson->Pulumi.Output.asInput),
|
|
198
|
+
("RELEASE_WINDOW_SECONDS", releaseWindowSeconds->Int.toString->Pulumi.Input.make),
|
|
156
199
|
("NODE_OPTIONS", Util_Bundle.esmLoaderNodeOptions->Pulumi.Input.make),
|
|
157
200
|
("ESM_FALLBACK_DIRS", Util_Bundle.esmFallbackDirs->Pulumi.Input.make),
|
|
158
201
|
]),
|
|
159
202
|
}: Lambda.Function.functionEnvironment
|
|
160
203
|
)->Pulumi.Input.make,
|
|
161
204
|
},
|
|
162
|
-
~opts
|
|
205
|
+
~opts,
|
|
163
206
|
)
|
|
164
207
|
|
|
165
|
-
let
|
|
166
|
-
~name
|
|
167
|
-
~
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
208
|
+
let dataSourceRole = IAM.Role.makeWithDefaultPolicy(
|
|
209
|
+
~name=name ++ "DataSource",
|
|
210
|
+
~servicePrincipal=AWS.AppSync.principal->Pulumi.Output.make,
|
|
211
|
+
~tags=AWS.Tags.make(
|
|
212
|
+
~name=name ++ "DataSource",
|
|
213
|
+
~kind=ReventlessCore.ComponentType.Platform,
|
|
214
|
+
~role=Identity,
|
|
215
|
+
~scope=Platform,
|
|
216
|
+
),
|
|
217
|
+
~opts,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
let _ =
|
|
221
|
+
(lambda.arn, dataSourceRole.id)
|
|
222
|
+
->Pulumi.Output.all2
|
|
223
|
+
->Pulumi.Output.apply(((lambdaArn, dataSourceRoleId)) => {
|
|
224
|
+
let _attach = IAM.RolePolicy.make(
|
|
225
|
+
~name=name ++ "DataSource",
|
|
226
|
+
~args={
|
|
227
|
+
policy: PolicyDocument.make(
|
|
228
|
+
~id=name ++ "DataSourcePolicy",
|
|
229
|
+
~statements=[
|
|
230
|
+
{
|
|
231
|
+
sid: "AllowDataSourceInvokeLambda",
|
|
232
|
+
effect: Allow,
|
|
233
|
+
actions: Action("lambda:InvokeFunction"),
|
|
234
|
+
resources: Resource(lambdaArn),
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
)
|
|
238
|
+
->PolicyDocument.toJsonString
|
|
182
239
|
->Pulumi.Input.make,
|
|
183
|
-
|
|
184
|
-
|
|
240
|
+
role: dataSourceRoleId->Pulumi.Input.make,
|
|
241
|
+
},
|
|
242
|
+
~opts,
|
|
243
|
+
)
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
let dataSource = AppSync.DataSource.make(
|
|
247
|
+
~name=name ++ "DataSource",
|
|
248
|
+
~args={
|
|
249
|
+
type_: AWS_LAMBDA,
|
|
250
|
+
apiId: api->Pulumi.Output.flatMap(api => api.id)->Pulumi.Output.asInput,
|
|
251
|
+
lambdaConfig: {
|
|
252
|
+
AppSync.DataSource.functionArn: lambda.arn->Pulumi.Output.asInput,
|
|
253
|
+
}->Pulumi.Input.make,
|
|
254
|
+
serviceRoleArn: dataSourceRole.arn->Pulumi.Output.asInput,
|
|
185
255
|
},
|
|
186
|
-
~opts
|
|
256
|
+
~opts=Some(opts),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
let _presignResolver = AppSync_Resolver_Native.makeUnitJsResolver(
|
|
260
|
+
~name=name ++ "Presign",
|
|
261
|
+
~api,
|
|
262
|
+
~dataSourceName=dataSource.name->Pulumi.Output.asInput,
|
|
263
|
+
~type_="Mutation"->Pulumi.Input.make,
|
|
264
|
+
~field="Upload_Presign"->Pulumi.Input.make,
|
|
265
|
+
~code=invokeCode("presign"),
|
|
266
|
+
~opts,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
let _releaseResolver = AppSync_Resolver_Native.makeUnitJsResolver(
|
|
270
|
+
~name=name ++ "Release",
|
|
271
|
+
~api,
|
|
272
|
+
~dataSourceName=dataSource.name->Pulumi.Output.asInput,
|
|
273
|
+
~type_="Mutation"->Pulumi.Input.make,
|
|
274
|
+
~field="Upload_Release"->Pulumi.Input.make,
|
|
275
|
+
~code=invokeCode("release"),
|
|
276
|
+
~opts,
|
|
187
277
|
)
|
|
188
278
|
|
|
189
|
-
{
|
|
190
|
-
url: functionUrl.functionUrl,
|
|
191
|
-
resources: [lambda.arn, functionUrl.functionArn],
|
|
192
|
-
}
|
|
279
|
+
{resources: [lambda.arn]}
|
|
193
280
|
}
|
|
@@ -2,28 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Aws from "@pulumi/aws";
|
|
4
4
|
import * as IAM$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/IAM.res.mjs";
|
|
5
|
+
import * as Output$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Output.res.mjs";
|
|
5
6
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
7
|
import * as Pulumi from "@pulumi/pulumi";
|
|
7
8
|
import * as Lambda$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/Lambda/Lambda.res.mjs";
|
|
8
|
-
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
9
9
|
import * as AWS$ReventlessAws from "../AWS.res.mjs";
|
|
10
10
|
import * as AWS_Tags$ReventlessAws from "../AWS_Tags.res.mjs";
|
|
11
11
|
import * as PolicyDocument$PulumiAws from "@reventlessdev/rescript-pulumi-aws/src/IAM/PolicyDocument.res.mjs";
|
|
12
12
|
import * as Util_Bundle$ReventlessAws from "../../util/Util_Bundle.res.mjs";
|
|
13
13
|
import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
|
|
14
|
+
import * as AppSync_Resolver_Native$ReventlessAws from "../Api/AppSync_Resolver_Native.res.mjs";
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
function invokeCode(operation) {
|
|
17
|
+
return `import { util } from '@aws-appsync/utils';
|
|
18
|
+
export function request(ctx) {
|
|
19
|
+
const id = ctx.identity;
|
|
20
|
+
return {
|
|
21
|
+
operation: 'Invoke',
|
|
22
|
+
payload: {
|
|
23
|
+
operation: '` + operation + `',
|
|
24
|
+
arguments: ctx.args,
|
|
25
|
+
identity: id != null && id.sub != null
|
|
26
|
+
? { sub: id.sub, username: id.username, claims: id.claims }
|
|
27
|
+
: null
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function response(ctx) {
|
|
32
|
+
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
|
|
33
|
+
return ctx.result;
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
36
|
+
}
|
|
16
37
|
|
|
17
|
-
function make(
|
|
18
|
-
let
|
|
19
|
-
let
|
|
20
|
-
let
|
|
21
|
-
let
|
|
22
|
-
let
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
38
|
+
function make(api, stores, releaseWindowSecondsOpt, nameOpt, opts) {
|
|
39
|
+
let releaseWindowSeconds = releaseWindowSecondsOpt !== undefined ? releaseWindowSecondsOpt : 900;
|
|
40
|
+
let name = nameOpt !== undefined ? nameOpt : "UploadService";
|
|
41
|
+
let opts$1 = Util_Pulumi$ReventlessCore.ComponentResourceOptions.toCustomResourceOptions(opts);
|
|
42
|
+
let lambdaRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "Lambda", Pulumi.output(AWS$ReventlessAws.Lambda.principal), AWS_Tags$ReventlessAws.make(name + "Lambda", "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined), opts$1);
|
|
43
|
+
let resolvedStores = Pulumi.all(stores.map(s => s.bucketName.apply(b => [
|
|
44
|
+
s.qualified,
|
|
45
|
+
b,
|
|
46
|
+
s.servedPrefix
|
|
47
|
+
])));
|
|
48
|
+
resolvedStores.apply(list => {
|
|
49
|
+
let arns = list.map(param => `arn:aws:s3:::` + param[1] + `/` + param[2] + `/*`);
|
|
50
|
+
new (Aws.iam.RolePolicy)(name + "LambdaPolicy", {
|
|
51
|
+
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "LambdaPolicy", [
|
|
27
52
|
{
|
|
28
53
|
Sid: "AllowLambdaLogging",
|
|
29
54
|
Effect: "Allow",
|
|
@@ -35,22 +60,39 @@ function make(bucketName, corsOriginsOpt, servedPrefixOpt, nameOpt, opts) {
|
|
|
35
60
|
Resource: "arn:aws:logs:*:*:*"
|
|
36
61
|
},
|
|
37
62
|
{
|
|
38
|
-
Sid: "
|
|
63
|
+
Sid: "AllowUploadObjectAccess",
|
|
39
64
|
Effect: "Allow",
|
|
40
|
-
Action:
|
|
41
|
-
|
|
65
|
+
Action: [
|
|
66
|
+
"s3:PutObject",
|
|
67
|
+
"s3:DeleteObject",
|
|
68
|
+
"s3:GetObject"
|
|
69
|
+
],
|
|
70
|
+
Resource: arns
|
|
42
71
|
}
|
|
43
72
|
])),
|
|
44
73
|
role: lambdaRole.id
|
|
45
|
-
}, opts$1
|
|
74
|
+
}, opts$1);
|
|
46
75
|
});
|
|
76
|
+
let uploadStoresJson = resolvedStores.apply(list => JSON.stringify(Object.fromEntries(list.map(param => [
|
|
77
|
+
param[0],
|
|
78
|
+
Object.fromEntries([
|
|
79
|
+
[
|
|
80
|
+
"bucket",
|
|
81
|
+
param[1]
|
|
82
|
+
],
|
|
83
|
+
[
|
|
84
|
+
"prefix",
|
|
85
|
+
param[2]
|
|
86
|
+
]
|
|
87
|
+
])
|
|
88
|
+
]))));
|
|
47
89
|
let packageDirs = Object.fromEntries([[
|
|
48
90
|
"@reventlessdev/reventless-aws",
|
|
49
91
|
Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
|
|
50
92
|
]]);
|
|
51
93
|
let match = Util_Bundle$ReventlessAws.buildCodeArchive("@reventlessdev/reventless-aws/src/adapter/Upload/Upload_Presign_S3_Ops.res.mjs", packageDirs, undefined);
|
|
52
94
|
let layers = Stdlib_Option.getOr(Stdlib_Option.map(Lambda$PulumiAws.reventlessLayerArn, arn => [arn]), []);
|
|
53
|
-
let lambda = new (Aws.lambda.Function)(name, {
|
|
95
|
+
let lambda = new (Aws.lambda.Function)(name + "Lambda", {
|
|
54
96
|
handler: "index.handler",
|
|
55
97
|
runtime: "nodejs22.x",
|
|
56
98
|
code: match.code,
|
|
@@ -58,7 +100,7 @@ function make(bucketName, corsOriginsOpt, servedPrefixOpt, nameOpt, opts) {
|
|
|
58
100
|
memorySize: 256,
|
|
59
101
|
timeout: 30,
|
|
60
102
|
layers: layers,
|
|
61
|
-
tags: AWS_Tags$ReventlessAws.make(name, "Platform", "Runtime", "Platform", undefined, undefined, undefined, undefined),
|
|
103
|
+
tags: AWS_Tags$ReventlessAws.make(name + "Lambda", "Platform", "Runtime", "Platform", undefined, undefined, undefined, undefined),
|
|
62
104
|
environment: {
|
|
63
105
|
variables: Object.fromEntries([
|
|
64
106
|
[
|
|
@@ -66,12 +108,12 @@ function make(bucketName, corsOriginsOpt, servedPrefixOpt, nameOpt, opts) {
|
|
|
66
108
|
Pulumi.getStack()
|
|
67
109
|
],
|
|
68
110
|
[
|
|
69
|
-
"
|
|
70
|
-
|
|
111
|
+
"UPLOAD_STORES",
|
|
112
|
+
uploadStoresJson
|
|
71
113
|
],
|
|
72
114
|
[
|
|
73
|
-
"
|
|
74
|
-
|
|
115
|
+
"RELEASE_WINDOW_SECONDS",
|
|
116
|
+
releaseWindowSeconds.toString()
|
|
75
117
|
],
|
|
76
118
|
[
|
|
77
119
|
"NODE_OPTIONS",
|
|
@@ -84,30 +126,78 @@ function make(bucketName, corsOriginsOpt, servedPrefixOpt, nameOpt, opts) {
|
|
|
84
126
|
])
|
|
85
127
|
},
|
|
86
128
|
sourceCodeHash: match.sourceCodeHash
|
|
87
|
-
}, opts$1
|
|
88
|
-
let
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
129
|
+
}, opts$1);
|
|
130
|
+
let dataSourceRole = IAM$PulumiAws.Role.makeWithDefaultPolicy(name + "DataSource", Pulumi.output(AWS$ReventlessAws.AppSync.principal), AWS_Tags$ReventlessAws.make(name + "DataSource", "Platform", "Identity", "Platform", undefined, undefined, undefined, undefined), opts$1);
|
|
131
|
+
Pulumi.all([
|
|
132
|
+
lambda.arn,
|
|
133
|
+
dataSourceRole.id
|
|
134
|
+
]).apply(param => {
|
|
135
|
+
new (Aws.iam.RolePolicy)(name + "DataSource", {
|
|
136
|
+
policy: PolicyDocument$PulumiAws.toJsonString(PolicyDocument$PulumiAws.make(undefined, name + "DataSourcePolicy", [{
|
|
137
|
+
Sid: "AllowDataSourceInvokeLambda",
|
|
138
|
+
Effect: "Allow",
|
|
139
|
+
Action: "lambda:InvokeFunction",
|
|
140
|
+
Resource: param[0]
|
|
141
|
+
}])),
|
|
142
|
+
role: param[1]
|
|
143
|
+
}, opts$1);
|
|
144
|
+
});
|
|
145
|
+
let dataSource = new (Aws.appsync.DataSource)(name + "DataSource", {
|
|
146
|
+
type: "AWS_LAMBDA",
|
|
147
|
+
apiId: Output$Pulumi.flatMap(api, api => api.id),
|
|
148
|
+
lambdaConfig: {
|
|
149
|
+
functionArn: lambda.arn
|
|
150
|
+
},
|
|
151
|
+
serviceRoleArn: dataSourceRole.arn
|
|
152
|
+
}, opts$1);
|
|
153
|
+
AppSync_Resolver_Native$ReventlessAws.makeUnitJsResolver(name + "Presign", api, dataSource.name, "Mutation", "Upload_Presign", `import { util } from '@aws-appsync/utils';
|
|
154
|
+
export function request(ctx) {
|
|
155
|
+
const id = ctx.identity;
|
|
156
|
+
return {
|
|
157
|
+
operation: 'Invoke',
|
|
158
|
+
payload: {
|
|
159
|
+
operation: '` + "presign" + `',
|
|
160
|
+
arguments: ctx.args,
|
|
161
|
+
identity: id != null && id.sub != null
|
|
162
|
+
? { sub: id.sub, username: id.username, claims: id.claims }
|
|
163
|
+
: null
|
|
98
164
|
}
|
|
99
|
-
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
export function response(ctx) {
|
|
168
|
+
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
|
|
169
|
+
return ctx.result;
|
|
170
|
+
}
|
|
171
|
+
`, opts$1);
|
|
172
|
+
AppSync_Resolver_Native$ReventlessAws.makeUnitJsResolver(name + "Release", api, dataSource.name, "Mutation", "Upload_Release", `import { util } from '@aws-appsync/utils';
|
|
173
|
+
export function request(ctx) {
|
|
174
|
+
const id = ctx.identity;
|
|
100
175
|
return {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
176
|
+
operation: 'Invoke',
|
|
177
|
+
payload: {
|
|
178
|
+
operation: '` + "release" + `',
|
|
179
|
+
arguments: ctx.args,
|
|
180
|
+
identity: id != null && id.sub != null
|
|
181
|
+
? { sub: id.sub, username: id.username, claims: id.claims }
|
|
182
|
+
: null
|
|
183
|
+
}
|
|
106
184
|
};
|
|
107
185
|
}
|
|
186
|
+
export function response(ctx) {
|
|
187
|
+
if (ctx.error) util.error(ctx.error.message, ctx.error.type);
|
|
188
|
+
return ctx.result;
|
|
189
|
+
}
|
|
190
|
+
`, opts$1);
|
|
191
|
+
return {
|
|
192
|
+
resources: [lambda.arn]
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let defaultServedPrefix = "uploads";
|
|
108
197
|
|
|
109
198
|
export {
|
|
110
199
|
defaultServedPrefix,
|
|
200
|
+
invokeCode,
|
|
111
201
|
make,
|
|
112
202
|
}
|
|
113
203
|
/* @pulumi/aws Not a pure module */
|