@reventlessdev/reventless-aws 3.0.0-alpha.272 → 3.0.0-alpha.274
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 +19 -0
- package/package.json +8 -7
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +15 -13
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +3 -2
- package/src/adapter/Api/Platform_UIFragments_Lambda.res +15 -13
- package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +3 -2
- package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res +8 -1
- package/src/adapter/EventCollector/EventCollectorChannel_Helpers.res.mjs +3 -2
- package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res +15 -13
- package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res.mjs +3 -2
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res +6 -12
- package/src/adapter/QueryDb/QueryDbResolvers_AppSync.res.mjs +4 -8
- package/src/adapter/QueryDb/QueryInterceptor_Provisioning.res +155 -0
- package/src/adapter/QueryDb/QueryInterceptor_Provisioning.res.mjs +92 -0
- package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res +4 -9
- package/src/adapter/Runtime/EventCollectorEntryPoint_Ops.res.mjs +8 -2
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +32 -62
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +7 -20
- package/src/adapter/StateTopic/StateTopic_AppSync.res +15 -13
- package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +3 -2
- package/src/adapter/Upload/Upload_Claim_S3.res +15 -13
- package/src/adapter/Upload/Upload_Claim_S3.res.mjs +3 -2
- package/src/adapter/Upload/Upload_Presign_S3.res +15 -13
- package/src/adapter/Upload/Upload_Presign_S3.res.mjs +3 -2
- package/src/components/Api/AppSync_Adapter.res +13 -9
- package/src/components/Api/AppSync_Adapter.res.mjs +4 -3
- package/src/util/Util_LambdaLogging.res +83 -21
- package/src/util/Util_LambdaLogging.res.mjs +21 -8
- package/src/util/Util_LogGroup_Adopting.res +480 -0
- package/src/util/Util_LogGroup_Adopting.res.mjs +365 -0
- package/src/util/Util_LogGroup_Adopting_Js.mjs +20 -0
- package/src/util/Util_LogRetention.res +11 -12
- package/tests/Util_LambdaLoggingTest.res +48 -0
- package/tests/Util_LambdaLoggingTest.res.mjs +30 -0
- package/tests/Util_LogGroup_AdoptingTest.res +169 -0
- package/tests/Util_LogGroup_AdoptingTest.res.mjs +208 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Alias$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Alias.res.mjs";
|
|
4
|
+
import * as Stdlib_JsExn from "@rescript/runtime/lib/es6/Stdlib_JsExn.js";
|
|
5
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
|
+
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
7
|
+
import * as Stdlib_Nullable from "@rescript/runtime/lib/es6/Stdlib_Nullable.js";
|
|
8
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
9
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
10
|
+
import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
|
|
11
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
12
|
+
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
13
|
+
import * as Util_LogGroup_Adopting_JsMjs from "./Util_LogGroup_Adopting_Js.mjs";
|
|
14
|
+
import * as IndexJs from "@pulumi/pulumi/dynamic/index.js";
|
|
15
|
+
|
|
16
|
+
let log = Logger$ReventlessCore.fromEnv();
|
|
17
|
+
|
|
18
|
+
function newCommand(prim0, prim1) {
|
|
19
|
+
return Util_LogGroup_Adopting_JsMjs.newCommand(prim0, prim1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function newClient(prim) {
|
|
23
|
+
return Util_LogGroup_Adopting_JsMjs.newClient(prim);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function rethrow(prim) {
|
|
27
|
+
return Util_LogGroup_Adopting_JsMjs.rethrow(prim);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let _sdk = {
|
|
31
|
+
contents: undefined
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
let _client = {
|
|
35
|
+
contents: undefined
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
async function getSdk() {
|
|
39
|
+
let m = _sdk.contents;
|
|
40
|
+
if (m !== undefined) {
|
|
41
|
+
return m;
|
|
42
|
+
}
|
|
43
|
+
let m$1 = await import("@aws-sdk/client-cloudwatch-logs");
|
|
44
|
+
_sdk.contents = m$1;
|
|
45
|
+
return m$1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function getClient() {
|
|
49
|
+
let c = _client.contents;
|
|
50
|
+
if (c !== undefined) {
|
|
51
|
+
return Primitive_option.valFromOption(c);
|
|
52
|
+
}
|
|
53
|
+
let sdk = await getSdk();
|
|
54
|
+
let c$1 = Util_LogGroup_Adopting_JsMjs.newClient(sdk.CloudWatchLogsClient);
|
|
55
|
+
_client.contents = Primitive_option.some(c$1);
|
|
56
|
+
return c$1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function matchesAws(jsErr, code) {
|
|
60
|
+
let match = jsErr.name;
|
|
61
|
+
let match$1 = Stdlib_JsExn.message(jsErr);
|
|
62
|
+
if (match == null) {
|
|
63
|
+
if (match$1 !== undefined) {
|
|
64
|
+
return match$1.includes(code);
|
|
65
|
+
} else {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
} else if (match === code) {
|
|
69
|
+
return true;
|
|
70
|
+
} else if (match$1 !== undefined) {
|
|
71
|
+
return match$1.includes(code);
|
|
72
|
+
} else {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isAlreadyExistsError(jsErr) {
|
|
78
|
+
return matchesAws(jsErr, "ResourceAlreadyExistsException");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isAlreadyGoneError(jsErr) {
|
|
82
|
+
return matchesAws(jsErr, "ResourceNotFoundException");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let transientNames = [
|
|
86
|
+
"OperationAbortedException",
|
|
87
|
+
"ThrottlingException",
|
|
88
|
+
"TooManyRequestsException",
|
|
89
|
+
"ServiceUnavailableException"
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
function isRetryableError(jsErr) {
|
|
93
|
+
return transientNames.some(code => matchesAws(jsErr, code));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function runWithRetry(attemptOpt, maxAttemptsOpt, delayMsOpt, maxDelayMsOpt, makeCall) {
|
|
97
|
+
let attempt = attemptOpt !== undefined ? attemptOpt : 0;
|
|
98
|
+
let maxAttempts = maxAttemptsOpt !== undefined ? maxAttemptsOpt : 5;
|
|
99
|
+
let delayMs = delayMsOpt !== undefined ? delayMsOpt : 1000;
|
|
100
|
+
let maxDelayMs = maxDelayMsOpt !== undefined ? maxDelayMsOpt : 8000;
|
|
101
|
+
try {
|
|
102
|
+
return await makeCall();
|
|
103
|
+
} catch (raw_exn) {
|
|
104
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
105
|
+
let jsExn = Stdlib_JsExn.fromException(exn);
|
|
106
|
+
let isRetryable = attempt < maxAttempts && Stdlib_Option.mapOr(jsExn, false, isRetryableError);
|
|
107
|
+
if (isRetryable) {
|
|
108
|
+
await new Promise((resolve, param) => {
|
|
109
|
+
setTimeout(resolve, delayMs);
|
|
110
|
+
});
|
|
111
|
+
let nextDelay = (delayMs << 1);
|
|
112
|
+
return await runWithRetry(attempt + 1 | 0, maxAttempts, nextDelay > maxDelayMs ? maxDelayMs : nextDelay, maxDelayMs, makeCall);
|
|
113
|
+
}
|
|
114
|
+
if (jsExn !== undefined) {
|
|
115
|
+
return Util_LogGroup_Adopting_JsMjs.rethrow(Primitive_option.valFromOption(jsExn));
|
|
116
|
+
}
|
|
117
|
+
throw exn;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let marker = "reventless:adopting-log-group";
|
|
122
|
+
|
|
123
|
+
async function describe(logGroupName) {
|
|
124
|
+
let sdk = await getSdk();
|
|
125
|
+
let client = await getClient();
|
|
126
|
+
let result = await runWithRetry(undefined, undefined, undefined, undefined, () => client.send(Util_LogGroup_Adopting_JsMjs.newCommand(sdk.DescribeLogGroupsCommand, {
|
|
127
|
+
logGroupNamePrefix: logGroupName
|
|
128
|
+
})));
|
|
129
|
+
return Stdlib_Option.getOr(result.logGroups, []).find(g => Primitive_object.equal(g.logGroupName, logGroupName));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function taggableArn(arn) {
|
|
133
|
+
if (arn.endsWith(":*")) {
|
|
134
|
+
return arn.slice(0, arn.length - 2 | 0);
|
|
135
|
+
} else {
|
|
136
|
+
return arn;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function applyRetention(logGroupName, retentionInDays) {
|
|
141
|
+
let sdk = await getSdk();
|
|
142
|
+
let client = await getClient();
|
|
143
|
+
return await runWithRetry(undefined, undefined, undefined, undefined, () => {
|
|
144
|
+
if (retentionInDays > 0) {
|
|
145
|
+
return client.send(Util_LogGroup_Adopting_JsMjs.newCommand(sdk.PutRetentionPolicyCommand, {
|
|
146
|
+
logGroupName: logGroupName,
|
|
147
|
+
retentionInDays: retentionInDays
|
|
148
|
+
}));
|
|
149
|
+
} else {
|
|
150
|
+
return client.send(Util_LogGroup_Adopting_JsMjs.newCommand(sdk.DeleteRetentionPolicyCommand, logGroupName));
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function applyTags(arn, tags) {
|
|
156
|
+
let sdk = await getSdk();
|
|
157
|
+
let client = await getClient();
|
|
158
|
+
return await runWithRetry(undefined, undefined, undefined, undefined, () => client.send(Util_LogGroup_Adopting_JsMjs.newCommand(sdk.TagResourceCommand, {
|
|
159
|
+
resourceArn: taggableArn(arn),
|
|
160
|
+
tags: tags
|
|
161
|
+
})));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function create(inputs) {
|
|
165
|
+
let sdk = await getSdk();
|
|
166
|
+
let client = await getClient();
|
|
167
|
+
let tags = inputs.tags;
|
|
168
|
+
let retentionInDays = inputs.retentionInDays;
|
|
169
|
+
let logGroupName = inputs.logGroupName;
|
|
170
|
+
let adopted;
|
|
171
|
+
try {
|
|
172
|
+
await runWithRetry(undefined, undefined, undefined, undefined, () => client.send(Util_LogGroup_Adopting_JsMjs.newCommand(sdk.CreateLogGroupCommand, {
|
|
173
|
+
logGroupName: logGroupName,
|
|
174
|
+
tags: tags
|
|
175
|
+
})));
|
|
176
|
+
adopted = false;
|
|
177
|
+
} catch (raw_exn) {
|
|
178
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
179
|
+
if (Stdlib_Option.mapOr(Stdlib_JsExn.fromException(exn), false, isAlreadyExistsError)) {
|
|
180
|
+
adopted = true;
|
|
181
|
+
} else {
|
|
182
|
+
throw exn;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
let match = await describe(logGroupName);
|
|
186
|
+
let arn;
|
|
187
|
+
if (match !== undefined) {
|
|
188
|
+
let arn$1 = match.arn;
|
|
189
|
+
arn = arn$1 !== undefined ? arn$1 : Stdlib_JsError.throwWithMessage(`log group "` + logGroupName + `" is missing immediately after create/adopt`);
|
|
190
|
+
} else {
|
|
191
|
+
arn = Stdlib_JsError.throwWithMessage(`log group "` + logGroupName + `" is missing immediately after create/adopt`);
|
|
192
|
+
}
|
|
193
|
+
if (adopted) {
|
|
194
|
+
await applyTags(arn, tags);
|
|
195
|
+
log.info("Util_LogGroup_Adopting", undefined, `adopted existing log group "` + logGroupName + `"; applied retention ` + retentionInDays.toString() + `d and tags`);
|
|
196
|
+
}
|
|
197
|
+
await applyRetention(logGroupName, retentionInDays);
|
|
198
|
+
return {
|
|
199
|
+
id: logGroupName,
|
|
200
|
+
outs: {
|
|
201
|
+
logGroupName: logGroupName,
|
|
202
|
+
arn: arn,
|
|
203
|
+
retentionInDays: retentionInDays,
|
|
204
|
+
tags: tags,
|
|
205
|
+
managedBy: marker
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function update(_id, _olds, news) {
|
|
211
|
+
let tags = news.tags;
|
|
212
|
+
let retentionInDays = news.retentionInDays;
|
|
213
|
+
let logGroupName = news.logGroupName;
|
|
214
|
+
let match = await describe(logGroupName);
|
|
215
|
+
let arn;
|
|
216
|
+
if (match !== undefined) {
|
|
217
|
+
let arn$1 = match.arn;
|
|
218
|
+
arn = arn$1 !== undefined ? arn$1 : undefined;
|
|
219
|
+
} else {
|
|
220
|
+
arn = undefined;
|
|
221
|
+
}
|
|
222
|
+
if (arn !== undefined) {
|
|
223
|
+
await applyTags(arn, tags);
|
|
224
|
+
await applyRetention(logGroupName, retentionInDays);
|
|
225
|
+
} else {
|
|
226
|
+
log.warn("Util_LogGroup_Adopting", undefined, `log group "` + logGroupName + `" was gone at update; recreating it`);
|
|
227
|
+
await create(news);
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
outs: {
|
|
231
|
+
logGroupName: logGroupName,
|
|
232
|
+
arn: Stdlib_Nullable.fromOption(arn),
|
|
233
|
+
retentionInDays: retentionInDays,
|
|
234
|
+
tags: tags,
|
|
235
|
+
managedBy: marker
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function delete_(id, _props) {
|
|
241
|
+
let sdk = await getSdk();
|
|
242
|
+
let client = await getClient();
|
|
243
|
+
try {
|
|
244
|
+
return await runWithRetry(undefined, undefined, undefined, undefined, () => client.send(Util_LogGroup_Adopting_JsMjs.newCommand(sdk.DeleteLogGroupCommand, {
|
|
245
|
+
logGroupName: id
|
|
246
|
+
})));
|
|
247
|
+
} catch (raw_exn) {
|
|
248
|
+
let exn = Primitive_exceptions.internalToException(raw_exn);
|
|
249
|
+
if (Stdlib_Option.mapOr(Stdlib_JsExn.fromException(exn), false, isAlreadyGoneError)) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
throw exn;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function sameTags(a, b) {
|
|
257
|
+
let normalise = d => {
|
|
258
|
+
let entries = Object.entries(d);
|
|
259
|
+
entries.sort((param, param$1) => Primitive_string.compare(param[0], param$1[0]));
|
|
260
|
+
return entries.map(param => param[0] + `=` + param[1]).join("");
|
|
261
|
+
};
|
|
262
|
+
return normalise(a) === normalise(b);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function diff_(_id, olds, news) {
|
|
266
|
+
let oldName = olds.logGroupName;
|
|
267
|
+
let replaces = (oldName == null) ? [] : (
|
|
268
|
+
oldName !== news.logGroupName ? ["logGroupName"] : []
|
|
269
|
+
);
|
|
270
|
+
let notNormalisedYet = Stdlib_Option.isNone(Primitive_option.fromNullable(olds.managedBy));
|
|
271
|
+
let days = olds.retentionInDays;
|
|
272
|
+
let retentionChanged = (days == null) ? false : days !== news.retentionInDays;
|
|
273
|
+
let tags = olds.tags;
|
|
274
|
+
let tagsChanged = (tags == null) ? false : !sameTags(tags, news.tags);
|
|
275
|
+
return {
|
|
276
|
+
changes: replaces.length !== 0 || notNormalisedYet || retentionChanged || tagsChanged,
|
|
277
|
+
replaces: replaces,
|
|
278
|
+
deleteBeforeReplace: false
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function read_(id, props) {
|
|
283
|
+
let live = await describe(id);
|
|
284
|
+
if (live !== undefined) {
|
|
285
|
+
return {
|
|
286
|
+
id: id,
|
|
287
|
+
props: {
|
|
288
|
+
logGroupName: id,
|
|
289
|
+
arn: Stdlib_Nullable.fromOption(live.arn),
|
|
290
|
+
retentionInDays: Stdlib_Option.getOr(live.retentionInDays, 0),
|
|
291
|
+
tags: props.tags,
|
|
292
|
+
managedBy: props.managedBy
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
} else {
|
|
296
|
+
log.info("Util_LogGroup_Adopting", undefined, `log group "` + id + `" is gone; reporting drift`);
|
|
297
|
+
return {};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
let provider = {
|
|
302
|
+
create: create,
|
|
303
|
+
update: update,
|
|
304
|
+
delete: delete_,
|
|
305
|
+
diff: diff_,
|
|
306
|
+
read: read_
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
function resourcePropsOf(props) {
|
|
310
|
+
return {
|
|
311
|
+
logGroupName: props.logGroupName,
|
|
312
|
+
retentionInDays: props.retentionInDays,
|
|
313
|
+
tags: props.tags,
|
|
314
|
+
name: undefined,
|
|
315
|
+
arn: undefined,
|
|
316
|
+
managedBy: undefined
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function make(name, props, opts) {
|
|
321
|
+
let migrationAlias = Alias$Pulumi.make(name, "aws:cloudwatch/logGroup:LogGroup", undefined);
|
|
322
|
+
let finalOpts;
|
|
323
|
+
if (opts !== undefined) {
|
|
324
|
+
let newrecord = {...opts};
|
|
325
|
+
newrecord.aliases = [migrationAlias];
|
|
326
|
+
finalOpts = newrecord;
|
|
327
|
+
} else {
|
|
328
|
+
finalOpts = {
|
|
329
|
+
aliases: [migrationAlias]
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
return new IndexJs.Resource(provider, name, resourcePropsOf(props), finalOpts);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export {
|
|
336
|
+
log,
|
|
337
|
+
newCommand,
|
|
338
|
+
newClient,
|
|
339
|
+
rethrow,
|
|
340
|
+
_sdk,
|
|
341
|
+
_client,
|
|
342
|
+
getSdk,
|
|
343
|
+
getClient,
|
|
344
|
+
matchesAws,
|
|
345
|
+
isAlreadyExistsError,
|
|
346
|
+
isAlreadyGoneError,
|
|
347
|
+
transientNames,
|
|
348
|
+
isRetryableError,
|
|
349
|
+
runWithRetry,
|
|
350
|
+
marker,
|
|
351
|
+
describe,
|
|
352
|
+
taggableArn,
|
|
353
|
+
applyRetention,
|
|
354
|
+
applyTags,
|
|
355
|
+
create,
|
|
356
|
+
update,
|
|
357
|
+
delete_,
|
|
358
|
+
sameTags,
|
|
359
|
+
diff_,
|
|
360
|
+
read_,
|
|
361
|
+
provider,
|
|
362
|
+
resourcePropsOf,
|
|
363
|
+
make,
|
|
364
|
+
}
|
|
365
|
+
/* log Not a pure module */
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Untyped JS primitives for Util_LogGroup_Adopting.
|
|
2
|
+
//
|
|
3
|
+
// The AWS SDK is imported dynamically there (a Pulumi dynamic provider
|
|
4
|
+
// serialises its whole captured closure into stack state, and a statically
|
|
5
|
+
// captured SDK does not survive that), so its command classes arrive as plain
|
|
6
|
+
// values on a module namespace and cannot be reached through `@new external`.
|
|
7
|
+
// These three helpers are the only untyped reflection that needs, kept out of
|
|
8
|
+
// the ReScript source.
|
|
9
|
+
|
|
10
|
+
/** `new Ctor(input)` — the SDK exports plain classes. */
|
|
11
|
+
export const newCommand = (Ctor, input) => new Ctor(input)
|
|
12
|
+
|
|
13
|
+
/** `new Ctor()` — the client constructor takes no required argument. */
|
|
14
|
+
export const newClient = Ctor => new Ctor()
|
|
15
|
+
|
|
16
|
+
/** Rethrows a JavaScript error as-is, bypassing the ReScript exception wrapper,
|
|
17
|
+
so Pulumi prints AWS's own message rather than "error: undefined". */
|
|
18
|
+
export const rethrow = e => {
|
|
19
|
+
throw e
|
|
20
|
+
}
|
|
@@ -62,19 +62,18 @@ let logLevelFor = (
|
|
|
62
62
|
Whether the framework creates a *managed* CloudWatch log group for this stack, or
|
|
63
63
|
leaves Lambda/AppSync to auto-create an unmanaged one.
|
|
64
64
|
|
|
65
|
-
**Managed for every stack by default
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
`ResourceAlreadyExists` hazard
|
|
69
|
-
|
|
65
|
+
**Managed for every stack by default**, and for Lambda there is nothing to adopt
|
|
66
|
+
on any stack: the group is created *before* the function and under a name the
|
|
67
|
+
program chooses (`Util_LambdaLogging.logGroupNameFor`), which is never the name
|
|
68
|
+
Lambda auto-creates. The `ResourceAlreadyExists` hazard that made this gate
|
|
69
|
+
necessary is structural for AppSync only, whose group name comes from a
|
|
70
|
+
server-assigned api id.
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
import is done. That escape hatch is the "prepared for the future" seam — turning
|
|
77
|
-
a prod adoption into a config flip rather than an edit to this function.
|
|
72
|
+
`unmanagedLogGroupStacks` remains the escape hatch for a stack that has adopted
|
|
73
|
+
**nothing** — it makes the program stop *declaring* log groups. Do not reach for
|
|
74
|
+
it once a stack holds managed groups in state: "stop declaring them" reads to the
|
|
75
|
+
engine as removed-from-the-program, and the next deploy deletes them along with
|
|
76
|
+
their retention, tags and any attached metric filters.
|
|
78
77
|
*/
|
|
79
78
|
let managesLogGroup = (~stack: string, ~unmanagedStacks: array<string>=[]): bool =>
|
|
80
79
|
!(unmanagedStacks->Array.includes(stack))
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
open JestGlobals
|
|
2
|
+
|
|
3
|
+
// The managed log group's name is now chosen by the program rather than derived
|
|
4
|
+
// from the function's physical name output. That choice is what removes the race
|
|
5
|
+
// (the group can be created before the function, and pointed at from
|
|
6
|
+
// `loggingConfig`), so the properties it has to keep are worth pinning.
|
|
7
|
+
describe("Util_LambdaLogging.logGroupNameFor", () => {
|
|
8
|
+
let nameFor = (~project="online-shop-catalog-aws", ~stack="alpha", ~name) =>
|
|
9
|
+
Util_LambdaLogging.logGroupNameFor(~project, ~stack, ~name)
|
|
10
|
+
|
|
11
|
+
testSync("is the conventional Lambda group prefix", () =>
|
|
12
|
+
expect(nameFor(~name="AllReadModels"))->toBe(
|
|
13
|
+
"/aws/lambda/online-shop-catalog-aws-alpha-AllReadModels",
|
|
14
|
+
)
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
testSync("carries no Pulumi physical-name suffix, so a replaced function keeps its group", () => {
|
|
18
|
+
let before = nameFor(~name="AllReadModels")
|
|
19
|
+
let afterReplacement = nameFor(~name="AllReadModels")
|
|
20
|
+
expect(before)->toBe(afterReplacement)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
testSync("is stack-scoped, so environments of one project cannot collide", () =>
|
|
24
|
+
expect(nameFor(~stack="pr-42", ~name="AllReadModels"))->not_->toBe(
|
|
25
|
+
nameFor(~stack="alpha", ~name="AllReadModels"),
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
// The regression this scoping exists for. Every plugin of one platform is its
|
|
30
|
+
// own Pulumi PROJECT deployed under the SAME stack name, and components are
|
|
31
|
+
// named by role rather than by owner — so several projects each host an
|
|
32
|
+
// `AllStateViewSlices`. Scoping on the stack alone gave them one name, and
|
|
33
|
+
// every deploy after the first failed with ResourceAlreadyExistsException.
|
|
34
|
+
testSync("is project-scoped, so one platform's plugins cannot collide", () =>
|
|
35
|
+
expect(nameFor(~project="online-shop-catalog-aws", ~name="AllStateViewSlices"))
|
|
36
|
+
->not_
|
|
37
|
+
->toBe(nameFor(~project="online-shop-ordering-aws", ~name="AllStateViewSlices"))
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
testSync("differs from the group Lambda would auto-create for the same function", () => {
|
|
41
|
+
// Lambda auto-creates `/aws/lambda/<physical name>`, and the physical name
|
|
42
|
+
// always carries Pulumi's `-<7hex>` suffix. Not colliding with it is what
|
|
43
|
+
// makes the create unconditionally safe on a stack whose groups AWS already
|
|
44
|
+
// made.
|
|
45
|
+
let autoCreated = "/aws/lambda/AllReadModels-0287438"
|
|
46
|
+
expect(nameFor(~name="AllReadModels"))->not_->toBe(autoCreated)
|
|
47
|
+
})
|
|
48
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Util_LambdaLogging$ReventlessAws from "../src/util/Util_LambdaLogging.res.mjs";
|
|
4
|
+
|
|
5
|
+
globalThis.describe("Util_LambdaLogging.logGroupNameFor", () => {
|
|
6
|
+
let nameFor = (projectOpt, stackOpt, name) => {
|
|
7
|
+
let project = projectOpt !== undefined ? projectOpt : "online-shop-catalog-aws";
|
|
8
|
+
let stack = stackOpt !== undefined ? stackOpt : "alpha";
|
|
9
|
+
return Util_LambdaLogging$ReventlessAws.logGroupNameFor(project, stack, name);
|
|
10
|
+
};
|
|
11
|
+
globalThis.test("is the conventional Lambda group prefix", () => {
|
|
12
|
+
globalThis.expect(nameFor(undefined, undefined, "AllReadModels")).toBe("/aws/lambda/online-shop-catalog-aws-alpha-AllReadModels");
|
|
13
|
+
});
|
|
14
|
+
globalThis.test("carries no Pulumi physical-name suffix, so a replaced function keeps its group", () => {
|
|
15
|
+
let before = nameFor(undefined, undefined, "AllReadModels");
|
|
16
|
+
let afterReplacement = nameFor(undefined, undefined, "AllReadModels");
|
|
17
|
+
globalThis.expect(before).toBe(afterReplacement);
|
|
18
|
+
});
|
|
19
|
+
globalThis.test("is stack-scoped, so environments of one project cannot collide", () => {
|
|
20
|
+
globalThis.expect(nameFor(undefined, "pr-42", "AllReadModels")).not.toBe(nameFor(undefined, "alpha", "AllReadModels"));
|
|
21
|
+
});
|
|
22
|
+
globalThis.test("is project-scoped, so one platform's plugins cannot collide", () => {
|
|
23
|
+
globalThis.expect(nameFor("online-shop-catalog-aws", undefined, "AllStateViewSlices")).not.toBe(nameFor("online-shop-ordering-aws", undefined, "AllStateViewSlices"));
|
|
24
|
+
});
|
|
25
|
+
globalThis.test("differs from the group Lambda would auto-create for the same function", () => {
|
|
26
|
+
globalThis.expect(nameFor(undefined, undefined, "AllReadModels")).not.toBe("/aws/lambda/AllReadModels-0287438");
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/* Not a pure module */
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
open JestGlobals
|
|
2
|
+
|
|
3
|
+
// Build a plain JS object shaped like a JS Error / SDK exception, cast to
|
|
4
|
+
// JsExn.t so the classifiers can be called without a real throw/catch.
|
|
5
|
+
type testError = {name: string, message: string}
|
|
6
|
+
let mkErr = (~name, ~message): JsExn.t => Obj.magic(({name, message}: testError))
|
|
7
|
+
|
|
8
|
+
module Adopting = Util_LogGroup_Adopting
|
|
9
|
+
|
|
10
|
+
let tags = Dict.fromArray([("Name", "PlatformApiAppSyncLogGroup"), ("reventless:role", "Logs")])
|
|
11
|
+
|
|
12
|
+
let normalisedOuts: Adopting.outs = {
|
|
13
|
+
logGroupName: Nullable.make("/aws/appsync/apis/abc123"),
|
|
14
|
+
arn: Nullable.make("arn:aws:logs:eu-west-1:1:log-group:/aws/appsync/apis/abc123:*"),
|
|
15
|
+
retentionInDays: Nullable.make(7),
|
|
16
|
+
tags: Nullable.make(tags),
|
|
17
|
+
managedBy: Nullable.make(Adopting.marker),
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let inputs: Adopting.providerInputs = {
|
|
21
|
+
logGroupName: "/aws/appsync/apis/abc123",
|
|
22
|
+
retentionInDays: 7,
|
|
23
|
+
tags,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe("Util_LogGroup_Adopting.isAlreadyExistsError", () => {
|
|
27
|
+
testSync("true for the error this provider exists to absorb", () =>
|
|
28
|
+
expect(
|
|
29
|
+
Adopting.isAlreadyExistsError(
|
|
30
|
+
mkErr(~name="ResourceAlreadyExistsException", ~message="The specified log group already exists"),
|
|
31
|
+
),
|
|
32
|
+
)->toBe(true)
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
testSync("true when the code rides in the message", () =>
|
|
36
|
+
expect(
|
|
37
|
+
Adopting.isAlreadyExistsError(
|
|
38
|
+
mkErr(~name="InvalidParameterException", ~message="ResourceAlreadyExistsException: exists"),
|
|
39
|
+
),
|
|
40
|
+
)->toBe(true)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
testSync("false for a permission failure, which must still fail loudly", () =>
|
|
44
|
+
expect(
|
|
45
|
+
Adopting.isAlreadyExistsError(mkErr(~name="AccessDeniedException", ~message="not authorized")),
|
|
46
|
+
)->toBe(false)
|
|
47
|
+
)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe("Util_LogGroup_Adopting.isRetryableError", () => {
|
|
51
|
+
testSync("true for the concurrent-write rejection on one group", () =>
|
|
52
|
+
expect(Adopting.isRetryableError(mkErr(~name="OperationAbortedException", ~message="x")))->toBe(
|
|
53
|
+
true,
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
testSync("false for an already-exists error — that is absorbed, not retried", () =>
|
|
58
|
+
expect(
|
|
59
|
+
Adopting.isRetryableError(mkErr(~name="ResourceAlreadyExistsException", ~message="x")),
|
|
60
|
+
)->toBe(false)
|
|
61
|
+
)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe("Util_LogGroup_Adopting.taggableArn", () => {
|
|
65
|
+
testSync("strips the trailing `:*` TagResource rejects", () =>
|
|
66
|
+
expect(
|
|
67
|
+
Adopting.taggableArn("arn:aws:logs:eu-west-1:1:log-group:/aws/appsync/apis/abc123:*"),
|
|
68
|
+
)->toBe("arn:aws:logs:eu-west-1:1:log-group:/aws/appsync/apis/abc123")
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
testSync("leaves an already-taggable arn alone", () => {
|
|
72
|
+
let arn = "arn:aws:logs:eu-west-1:1:log-group:/aws/appsync/apis/abc123"
|
|
73
|
+
expect(Adopting.taggableArn(arn))->toBe(arn)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe("Util_LogGroup_Adopting.sameTags", () => {
|
|
78
|
+
testSync("is insensitive to key order", () =>
|
|
79
|
+
expect(
|
|
80
|
+
Adopting.sameTags(
|
|
81
|
+
Dict.fromArray([("a", "1"), ("b", "2")]),
|
|
82
|
+
Dict.fromArray([("b", "2"), ("a", "1")]),
|
|
83
|
+
),
|
|
84
|
+
)->toBe(true)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
testSync("sees a changed value", () =>
|
|
88
|
+
expect(
|
|
89
|
+
Adopting.sameTags(Dict.fromArray([("a", "1")]), Dict.fromArray([("a", "2")])),
|
|
90
|
+
)->toBe(false)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
testSync("sees an added key", () =>
|
|
94
|
+
expect(
|
|
95
|
+
Adopting.sameTags(Dict.fromArray([("a", "1")]), Dict.fromArray([("a", "1"), ("b", "2")])),
|
|
96
|
+
)->toBe(false)
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
describe("Util_LogGroup_Adopting.diff_", () => {
|
|
101
|
+
testSync("no change when the live state already matches", () => {
|
|
102
|
+
let result = Adopting.diff_("id", normalisedOuts, inputs)
|
|
103
|
+
expect(result.changes)->toBe(false)
|
|
104
|
+
expect(result.replaces)->toEqual([])
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
testSync("a retention change is an update, not a replace", () => {
|
|
108
|
+
let result = Adopting.diff_("id", normalisedOuts, {...inputs, retentionInDays: 30})
|
|
109
|
+
expect(result.changes)->toBe(true)
|
|
110
|
+
expect(result.replaces)->toEqual([])
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
testSync("a tag change is an update, not a replace", () => {
|
|
114
|
+
let result = Adopting.diff_(
|
|
115
|
+
"id",
|
|
116
|
+
normalisedOuts,
|
|
117
|
+
{...inputs, tags: Dict.fromArray([("Name", "renamed")])},
|
|
118
|
+
)
|
|
119
|
+
expect(result.changes)->toBe(true)
|
|
120
|
+
expect(result.replaces)->toEqual([])
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
testSync("a name change replaces — CloudWatch cannot rename a group in place", () => {
|
|
124
|
+
let result = Adopting.diff_(
|
|
125
|
+
"id",
|
|
126
|
+
normalisedOuts,
|
|
127
|
+
{...inputs, logGroupName: "/aws/appsync/apis/def456"},
|
|
128
|
+
)
|
|
129
|
+
expect(result.changes)->toBe(true)
|
|
130
|
+
expect(result.replaces)->toEqual(["logGroupName"])
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
testSync("replacing creates before deleting, so the old group stays readable", () => {
|
|
134
|
+
let result = Adopting.diff_(
|
|
135
|
+
"id",
|
|
136
|
+
normalisedOuts,
|
|
137
|
+
{...inputs, logGroupName: "/aws/appsync/apis/def456"},
|
|
138
|
+
)
|
|
139
|
+
expect(result.deleteBeforeReplace)->toBe(false)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// State written by the classic `aws:cloudwatch/logGroup:LogGroup` resource the
|
|
143
|
+
// alias adopts carries none of this provider's fields. It has to report a
|
|
144
|
+
// change even when nothing differs, because the following update is what
|
|
145
|
+
// writes this provider's output shape — and Pulumi's `__provider` — into state.
|
|
146
|
+
testSync("adopted classic state is always a change, so the update normalises it", () => {
|
|
147
|
+
let classicOuts: Adopting.outs = {
|
|
148
|
+
logGroupName: Nullable.undefined,
|
|
149
|
+
arn: Nullable.undefined,
|
|
150
|
+
retentionInDays: Nullable.undefined,
|
|
151
|
+
tags: Nullable.undefined,
|
|
152
|
+
managedBy: Nullable.undefined,
|
|
153
|
+
}
|
|
154
|
+
let result = Adopting.diff_("id", classicOuts, inputs)
|
|
155
|
+
expect(result.changes)->toBe(true)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
testSync("adopted classic state never replaces on a field it does not carry", () => {
|
|
159
|
+
let classicOuts: Adopting.outs = {
|
|
160
|
+
logGroupName: Nullable.undefined,
|
|
161
|
+
arn: Nullable.undefined,
|
|
162
|
+
retentionInDays: Nullable.undefined,
|
|
163
|
+
tags: Nullable.undefined,
|
|
164
|
+
managedBy: Nullable.undefined,
|
|
165
|
+
}
|
|
166
|
+
let result = Adopting.diff_("id", classicOuts, inputs)
|
|
167
|
+
expect(result.replaces)->toEqual([])
|
|
168
|
+
})
|
|
169
|
+
})
|