@friggframework/devtools 2.0.0--canary.653.8213092.0 → 2.0.0--canary.654.be44e66.0
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/frigg-cli/init-command/backend-first-handler.js +1 -1
- package/frigg-cli/package.json +1 -1
- package/infrastructure/domains/integration/integration-builder.js +1 -13
- package/infrastructure/domains/integration/integration-builder.test.js +0 -52
- package/infrastructure/domains/parameters/offload-utils.js +7 -0
- package/infrastructure/domains/parameters/offload-utils.test.js +13 -0
- package/infrastructure/domains/security/iam-generator.js +1 -0
- package/infrastructure/domains/security/iam-generator.test.js +30 -1
- package/infrastructure/domains/security/templates/frigg-deployment-iam-stack.yaml +1 -0
- package/infrastructure/domains/security/templates/iam-policy-basic.json +2 -1
- package/infrastructure/domains/security/templates/iam-policy-full.json +2 -1
- package/infrastructure/domains/shared/environment-builder.js +17 -0
- package/infrastructure/domains/shared/environment-builder.test.js +61 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +4 -1
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +70 -0
- package/infrastructure/domains/shared/utilities/logging-config.js +77 -0
- package/infrastructure/infrastructure-composer.test.js +49 -1
- package/package.json +8 -8
- package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +0 -338
package/frigg-cli/package.json
CHANGED
|
@@ -32,10 +32,6 @@ const {
|
|
|
32
32
|
const {
|
|
33
33
|
nestedNodeModulesExcludes,
|
|
34
34
|
} = require('../shared/utilities/nested-node-modules');
|
|
35
|
-
const {
|
|
36
|
-
INTEGRATION_QUEUE_MAX_RECEIVE_COUNT,
|
|
37
|
-
QUEUE_MAX_RECEIVE_COUNT_ENV,
|
|
38
|
-
} = require('@friggframework/core/queues/queue-delivery');
|
|
39
35
|
|
|
40
36
|
class IntegrationBuilder extends InfrastructureBuilder {
|
|
41
37
|
constructor() {
|
|
@@ -573,7 +569,7 @@ class IntegrationBuilder extends InfrastructureBuilder {
|
|
|
573
569
|
MessageRetentionPeriod: 345600, // 4 days (SQS default)
|
|
574
570
|
VisibilityTimeout: 1800,
|
|
575
571
|
RedrivePolicy: {
|
|
576
|
-
maxReceiveCount:
|
|
572
|
+
maxReceiveCount: 3,
|
|
577
573
|
deadLetterTargetArn: {
|
|
578
574
|
'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
|
|
579
575
|
},
|
|
@@ -581,14 +577,6 @@ class IntegrationBuilder extends InfrastructureBuilder {
|
|
|
581
577
|
},
|
|
582
578
|
};
|
|
583
579
|
|
|
584
|
-
const queueWorker = result.functions[`${integrationName}QueueWorker`];
|
|
585
|
-
queueWorker.environment = {
|
|
586
|
-
...queueWorker.environment,
|
|
587
|
-
[QUEUE_MAX_RECEIVE_COUNT_ENV]: String(
|
|
588
|
-
INTEGRATION_QUEUE_MAX_RECEIVE_COUNT
|
|
589
|
-
),
|
|
590
|
-
};
|
|
591
|
-
|
|
592
580
|
// Add queue URL to environment
|
|
593
581
|
this.setQueueUrlEnvironment(
|
|
594
582
|
integrationName,
|
|
@@ -4,10 +4,6 @@
|
|
|
4
4
|
* Tests integration-specific Lambda functions and SQS queues
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const {
|
|
8
|
-
INTEGRATION_QUEUE_MAX_RECEIVE_COUNT,
|
|
9
|
-
QUEUE_MAX_RECEIVE_COUNT_ENV,
|
|
10
|
-
} = require('@friggframework/core/queues/queue-delivery');
|
|
11
7
|
const { IntegrationBuilder } = require('./integration-builder');
|
|
12
8
|
const { ValidationResult } = require('../shared/base-builder');
|
|
13
9
|
|
|
@@ -211,54 +207,6 @@ describe('IntegrationBuilder', () => {
|
|
|
211
207
|
});
|
|
212
208
|
});
|
|
213
209
|
|
|
214
|
-
it('takes the redrive max receive count from the core runtime contract', async () => {
|
|
215
|
-
const result = await integrationBuilder.build(
|
|
216
|
-
{ integrations: [{ Definition: { name: 'test' } }] },
|
|
217
|
-
{}
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
expect(
|
|
221
|
-
result.resources.TestQueue.Properties.RedrivePolicy
|
|
222
|
-
.maxReceiveCount
|
|
223
|
-
).toBe(INTEGRATION_QUEUE_MAX_RECEIVE_COUNT);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it('tells the queue worker the max receive count of the queue it owns', async () => {
|
|
227
|
-
const result = await integrationBuilder.build(
|
|
228
|
-
{ integrations: [{ Definition: { name: 'test' } }] },
|
|
229
|
-
{}
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
expect(
|
|
233
|
-
result.functions.testQueueWorker.environment[
|
|
234
|
-
QUEUE_MAX_RECEIVE_COUNT_ENV
|
|
235
|
-
]
|
|
236
|
-
).toBe('3');
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it('leaves the max receive count unset for an external queue', async () => {
|
|
240
|
-
const result = await integrationBuilder.build(
|
|
241
|
-
{
|
|
242
|
-
integrations: [
|
|
243
|
-
{
|
|
244
|
-
Definition: { name: 'test' },
|
|
245
|
-
ownership: { queue: 'external' },
|
|
246
|
-
queue: {
|
|
247
|
-
url: 'https://sqs.us-east-1.amazonaws.com/123456789012/test-queue',
|
|
248
|
-
},
|
|
249
|
-
},
|
|
250
|
-
],
|
|
251
|
-
},
|
|
252
|
-
{}
|
|
253
|
-
);
|
|
254
|
-
|
|
255
|
-
expect(
|
|
256
|
-
result.functions.testQueueWorker.environment?.[
|
|
257
|
-
QUEUE_MAX_RECEIVE_COUNT_ENV
|
|
258
|
-
]
|
|
259
|
-
).toBeUndefined();
|
|
260
|
-
});
|
|
261
|
-
|
|
262
210
|
it('should create queue worker function', async () => {
|
|
263
211
|
const appDefinition = {
|
|
264
212
|
integrations: [
|
|
@@ -53,6 +53,13 @@ const FRAMEWORK_ENV_BLOCKLIST = new Set([
|
|
|
53
53
|
'FRIGG_SSM_OFFLOADED_KEYS',
|
|
54
54
|
'FRIGG_SSM_CACHE_TTL',
|
|
55
55
|
'WORKER_FUNCTION_NAME',
|
|
56
|
+
// Read by the logger, telemetry flush and Prisma at INIT, before SSM loads
|
|
57
|
+
'FRIGG_LOG_LEVEL',
|
|
58
|
+
'AWS_LAMBDA_LOG_LEVEL',
|
|
59
|
+
'AWS_LAMBDA_LOG_FORMAT',
|
|
60
|
+
'OTEL_FLUSH_TIMEOUT_MS',
|
|
61
|
+
'DEBUG_VERBOSE',
|
|
62
|
+
'PRISMA_LOG_LEVEL',
|
|
56
63
|
// Reserved AWS Lambda runtime variables
|
|
57
64
|
'_HANDLER',
|
|
58
65
|
'_X_AMZN_TRACE_ID',
|
|
@@ -190,4 +190,17 @@ describe('offload-utils', () => {
|
|
|
190
190
|
expect(FRAMEWORK_ENV_BLOCKLIST.has(key)).toBe(true);
|
|
191
191
|
}
|
|
192
192
|
});
|
|
193
|
+
|
|
194
|
+
it('blocklist covers the logger keys read at INIT (ADR-048)', () => {
|
|
195
|
+
for (const key of [
|
|
196
|
+
'FRIGG_LOG_LEVEL',
|
|
197
|
+
'AWS_LAMBDA_LOG_LEVEL',
|
|
198
|
+
'AWS_LAMBDA_LOG_FORMAT',
|
|
199
|
+
'OTEL_FLUSH_TIMEOUT_MS',
|
|
200
|
+
'DEBUG_VERBOSE',
|
|
201
|
+
'PRISMA_LOG_LEVEL',
|
|
202
|
+
]) {
|
|
203
|
+
expect(FRAMEWORK_ENV_BLOCKLIST.has(key)).toBe(true);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
193
206
|
});
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
const
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const {
|
|
4
|
+
generateIAMCloudFormation,
|
|
5
|
+
getFeatureSummary,
|
|
6
|
+
generateBasicIAMPolicy,
|
|
7
|
+
generateFullIAMPolicy,
|
|
8
|
+
} = require('./iam-generator');
|
|
2
9
|
|
|
3
10
|
describe('IAM Generator', () => {
|
|
4
11
|
describe('getFeatureSummary', () => {
|
|
@@ -250,6 +257,28 @@ describe('IAM Generator', () => {
|
|
|
250
257
|
expect(yaml).toContain('iam:ListPolicyVersions');
|
|
251
258
|
});
|
|
252
259
|
|
|
260
|
+
it('should grant logs:DeleteRetentionPolicy so logging.retentionInDays can be removed', () => {
|
|
261
|
+
const yaml = generateIAMCloudFormation({
|
|
262
|
+
appName: 'test-app',
|
|
263
|
+
features: getFeatureSummary({ name: 'test-app' }).features,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect(yaml).toContain('logs:PutRetentionPolicy');
|
|
267
|
+
expect(yaml).toContain('logs:DeleteRetentionPolicy');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should grant logs:DeleteRetentionPolicy in the static policy templates', () => {
|
|
271
|
+
for (const policy of [generateBasicIAMPolicy(), generateFullIAMPolicy()]) {
|
|
272
|
+
const actions = policy.Statement.flatMap((statement) => statement.Action);
|
|
273
|
+
expect(actions).toContain('logs:DeleteRetentionPolicy');
|
|
274
|
+
}
|
|
275
|
+
const stackYaml = fs.readFileSync(
|
|
276
|
+
path.join(__dirname, 'templates/frigg-deployment-iam-stack.yaml'),
|
|
277
|
+
'utf8'
|
|
278
|
+
);
|
|
279
|
+
expect(stackYaml).toContain("'logs:DeleteRetentionPolicy'");
|
|
280
|
+
});
|
|
281
|
+
|
|
253
282
|
it('should include internal-error-queue pattern in SQS resources', () => {
|
|
254
283
|
const appDefinition = {
|
|
255
284
|
name: 'test-app',
|
|
@@ -238,6 +238,7 @@ Resources:
|
|
|
238
238
|
- 'logs:FilterLogEvents'
|
|
239
239
|
- 'logs:PutLogEvents'
|
|
240
240
|
- 'logs:PutRetentionPolicy'
|
|
241
|
+
- 'logs:DeleteRetentionPolicy'
|
|
241
242
|
Resource:
|
|
242
243
|
- !Sub 'arn:aws:logs:*:${AWS::AccountId}:log-group:/aws/lambda/*frigg*'
|
|
243
244
|
- !Sub 'arn:aws:logs:*:${AWS::AccountId}:log-group:/aws/lambda/*frigg*:*'
|
|
@@ -183,7 +183,8 @@
|
|
|
183
183
|
"logs:DescribeLogStreams",
|
|
184
184
|
"logs:FilterLogEvents",
|
|
185
185
|
"logs:PutLogEvents",
|
|
186
|
-
"logs:PutRetentionPolicy"
|
|
186
|
+
"logs:PutRetentionPolicy",
|
|
187
|
+
"logs:DeleteRetentionPolicy"
|
|
187
188
|
],
|
|
188
189
|
"Resource": [
|
|
189
190
|
"arn:aws:logs:*:*:log-group:/aws/lambda/*frigg*",
|
|
@@ -183,7 +183,8 @@
|
|
|
183
183
|
"logs:DescribeLogStreams",
|
|
184
184
|
"logs:FilterLogEvents",
|
|
185
185
|
"logs:PutLogEvents",
|
|
186
|
-
"logs:PutRetentionPolicy"
|
|
186
|
+
"logs:PutRetentionPolicy",
|
|
187
|
+
"logs:DeleteRetentionPolicy"
|
|
187
188
|
],
|
|
188
189
|
"Resource": [
|
|
189
190
|
"arn:aws:logs:*:*:log-group:/aws/lambda/*frigg*",
|
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
isSsmOffloadActive,
|
|
14
14
|
getOffloadedKeys,
|
|
15
15
|
} = require('../parameters/offload-utils');
|
|
16
|
+
const { normalizeLogLevel } = require('./utilities/logging-config');
|
|
16
17
|
|
|
17
18
|
// OTLP-family exporters read their endpoint/headers from these standard env
|
|
18
19
|
// vars (ADR-011). When such an exporter is configured we auto-register them as
|
|
@@ -44,6 +45,20 @@ function addTelemetryEnvPassthrough(appDefinition, envVars) {
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
// ADR-048 §12: logging.level wins over an `environment` passthrough. INFO is
|
|
49
|
+
// the core default, so it emits no env var.
|
|
50
|
+
function addLoggingEnv(appDefinition, envVars) {
|
|
51
|
+
const level = appDefinition?.logging?.level;
|
|
52
|
+
if (level === undefined) return;
|
|
53
|
+
|
|
54
|
+
const normalized = normalizeLogLevel(level);
|
|
55
|
+
if (normalized === 'INFO') {
|
|
56
|
+
delete envVars.FRIGG_LOG_LEVEL;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
envVars.FRIGG_LOG_LEVEL = normalized;
|
|
60
|
+
}
|
|
61
|
+
|
|
47
62
|
/**
|
|
48
63
|
* Get environment variables from AppDefinition
|
|
49
64
|
*
|
|
@@ -120,6 +135,8 @@ function getAppEnvironmentVars(appDefinition) {
|
|
|
120
135
|
envKeys.push(key);
|
|
121
136
|
}
|
|
122
137
|
|
|
138
|
+
addLoggingEnv(appDefinition, envVars);
|
|
139
|
+
|
|
123
140
|
if (envKeys.length > 0) {
|
|
124
141
|
console.log(
|
|
125
142
|
` Found ${envKeys.length} environment variables: ${envKeys.join(
|
|
@@ -147,6 +147,67 @@ describe('Environment Builder', () => {
|
|
|
147
147
|
});
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
+
describe('logging env (ADR-048)', () => {
|
|
151
|
+
it.each(['debug', 'WARN', 'Error'])(
|
|
152
|
+
'emits literal upper-case FRIGG_LOG_LEVEL for level "%s"',
|
|
153
|
+
(level) => {
|
|
154
|
+
const result = getAppEnvironmentVars({ logging: { level } });
|
|
155
|
+
|
|
156
|
+
expect(result.FRIGG_LOG_LEVEL).toBe(level.toUpperCase());
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
it.each(['info', 'INFO'])(
|
|
161
|
+
'emits nothing for the default level "%s"',
|
|
162
|
+
(level) => {
|
|
163
|
+
const result = getAppEnvironmentVars({ logging: { level } });
|
|
164
|
+
|
|
165
|
+
expect(result).not.toHaveProperty('FRIGG_LOG_LEVEL');
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
it('emits nothing when logging or logging.level is absent', () => {
|
|
170
|
+
expect(getAppEnvironmentVars({})).not.toHaveProperty(
|
|
171
|
+
'FRIGG_LOG_LEVEL'
|
|
172
|
+
);
|
|
173
|
+
expect(
|
|
174
|
+
getAppEnvironmentVars({ logging: { retentionInDays: 30 } })
|
|
175
|
+
).not.toHaveProperty('FRIGG_LOG_LEVEL');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('lets logging.level win over an environment passthrough', () => {
|
|
179
|
+
const result = getAppEnvironmentVars({
|
|
180
|
+
environment: { FRIGG_LOG_LEVEL: true },
|
|
181
|
+
logging: { level: 'error' },
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect(result.FRIGG_LOG_LEVEL).toBe('ERROR');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('drops an environment passthrough when logging.level is INFO', () => {
|
|
188
|
+
const result = getAppEnvironmentVars({
|
|
189
|
+
environment: { FRIGG_LOG_LEVEL: true },
|
|
190
|
+
logging: { level: 'info' },
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(result).not.toHaveProperty('FRIGG_LOG_LEVEL');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('keeps an environment passthrough when logging.level is absent', () => {
|
|
197
|
+
const result = getAppEnvironmentVars({
|
|
198
|
+
environment: { FRIGG_LOG_LEVEL: true },
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
expect(result.FRIGG_LOG_LEVEL).toBe("${env:FRIGG_LOG_LEVEL, ''}");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('throws on an unknown level', () => {
|
|
205
|
+
expect(() =>
|
|
206
|
+
getAppEnvironmentVars({ logging: { level: 'verbose' } })
|
|
207
|
+
).toThrow(/logging\.level/);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
150
211
|
describe("getAppEnvironmentVars() - 'ssm' offload", () => {
|
|
151
212
|
const originalSkipDiscovery = process.env.FRIGG_SKIP_AWS_DISCOVERY;
|
|
152
213
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const { buildEnvironment } = require('../environment-builder');
|
|
11
11
|
const { nestedNodeModulesExcludes } = require('./nested-node-modules');
|
|
12
|
+
const { buildLoggingProviderConfig } = require('./logging-config');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Create base serverless definition with core functions and resources
|
|
@@ -33,6 +34,7 @@ function createBaseDefinition(
|
|
|
33
34
|
usePrismaLayer = true
|
|
34
35
|
) {
|
|
35
36
|
const region = process.env.AWS_REGION || 'us-east-1';
|
|
37
|
+
const loggingConfig = buildLoggingProviderConfig(AppDefinition.logging);
|
|
36
38
|
|
|
37
39
|
// Package config for handlers that skip esbuild (need node_modules dependencies)
|
|
38
40
|
// Include backend src/ and index.js since handlers load the app definition
|
|
@@ -164,7 +166,7 @@ function createBaseDefinition(
|
|
|
164
166
|
};
|
|
165
167
|
|
|
166
168
|
return {
|
|
167
|
-
frameworkVersion: '>=3.17.0',
|
|
169
|
+
frameworkVersion: loggingConfig.frameworkVersion || '>=3.17.0',
|
|
168
170
|
service: AppDefinition.name || 'create-frigg-app',
|
|
169
171
|
package: {
|
|
170
172
|
individually: true,
|
|
@@ -218,6 +220,7 @@ function createBaseDefinition(
|
|
|
218
220
|
name: '${opt:stage, "dev"}-${self:service}',
|
|
219
221
|
disableDefaultEndpoint: false,
|
|
220
222
|
},
|
|
223
|
+
...loggingConfig.provider,
|
|
221
224
|
},
|
|
222
225
|
plugins: [
|
|
223
226
|
'serverless-esbuild',
|
|
@@ -317,3 +317,73 @@ describe('nested node_modules (lambda.keepNestedNodeModules)', () => {
|
|
|
317
317
|
}
|
|
318
318
|
});
|
|
319
319
|
});
|
|
320
|
+
|
|
321
|
+
describe('logging (ADR-048)', () => {
|
|
322
|
+
const build = (logging) =>
|
|
323
|
+
createBaseDefinition({ name: 'test-app', ...(logging && { logging }) }, {}, {});
|
|
324
|
+
|
|
325
|
+
it('emits no logs, logRetentionInDays or frameworkVersion change when logging is absent', () => {
|
|
326
|
+
const result = build();
|
|
327
|
+
|
|
328
|
+
expect(result.provider.logs).toBeUndefined();
|
|
329
|
+
expect(result.provider.logRetentionInDays).toBeUndefined();
|
|
330
|
+
expect(result.frameworkVersion).toBe('>=3.17.0');
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('maps logging.level to provider.logs.lambda and sets frameworkVersion >=3.58.0', () => {
|
|
334
|
+
const result = build({ level: 'warn' });
|
|
335
|
+
|
|
336
|
+
expect(result.provider.logs).toEqual({
|
|
337
|
+
lambda: {
|
|
338
|
+
logFormat: 'JSON',
|
|
339
|
+
applicationLogLevel: 'WARN',
|
|
340
|
+
systemLogLevel: 'INFO',
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
expect(result.provider.logRetentionInDays).toBeUndefined();
|
|
344
|
+
expect(result.frameworkVersion).toBe('>=3.58.0');
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it.each(['info', 'INFO', 'Info', 'fatal', 'TRACE'])(
|
|
348
|
+
'accepts level "%s" in any case',
|
|
349
|
+
(level) => {
|
|
350
|
+
const result = build({ level });
|
|
351
|
+
|
|
352
|
+
expect(result.provider.logs.lambda.applicationLogLevel).toBe(
|
|
353
|
+
level.toUpperCase()
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
it.each(['verbose', 'warning', '', 3])('throws on unknown level %p', (level) => {
|
|
359
|
+
expect(() => build({ level })).toThrow(/logging\.level/);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('maps retentionInDays to provider.logRetentionInDays without a logs block', () => {
|
|
363
|
+
const result = build({ retentionInDays: 30 });
|
|
364
|
+
|
|
365
|
+
expect(result.provider.logRetentionInDays).toBe(30);
|
|
366
|
+
expect(result.provider.logs).toBeUndefined();
|
|
367
|
+
expect(result.frameworkVersion).toBe('>=3.17.0');
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('accepts 1096, which CloudWatch allows but the osls schema omits', () => {
|
|
371
|
+
expect(build({ retentionInDays: 1096 }).provider.logRetentionInDays).toBe(1096);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it.each([10, 0, -1, 30.5, '30'])(
|
|
375
|
+
'throws on retentionInDays %p, which CloudWatch rejects',
|
|
376
|
+
(retentionInDays) => {
|
|
377
|
+
expect(() => build({ retentionInDays })).toThrow(
|
|
378
|
+
/logging\.retentionInDays/
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
it('sets both level and retention together', () => {
|
|
384
|
+
const result = build({ level: 'debug', retentionInDays: 14 });
|
|
385
|
+
|
|
386
|
+
expect(result.provider.logs.lambda.applicationLogLevel).toBe('DEBUG');
|
|
387
|
+
expect(result.provider.logRetentionInDays).toBe(14);
|
|
388
|
+
});
|
|
389
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const LOG_LEVELS = ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'];
|
|
2
|
+
|
|
3
|
+
// CloudWatch Logs PutRetentionPolicy values. The osls schema copy lacks 1096.
|
|
4
|
+
const RETENTION_DAYS = [
|
|
5
|
+
1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1096, 1827,
|
|
6
|
+
2192, 2557, 2922, 3288, 3653,
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
// First osls version that compiles provider.logs.lambda into LoggingConfig.
|
|
10
|
+
const LOGGING_CONFIG_FRAMEWORK_VERSION = '>=3.58.0';
|
|
11
|
+
|
|
12
|
+
// ADR-048 Open question 3 (Phase 0 spike). Set to false to leave Lambda on
|
|
13
|
+
// its default Text format; FRIGG_LOG_LEVEL then filters in process alone.
|
|
14
|
+
const EMIT_LAMBDA_JSON_LOGGING_CONFIG = true;
|
|
15
|
+
|
|
16
|
+
function normalizeLogLevel(level) {
|
|
17
|
+
const normalized = typeof level === 'string' ? level.toUpperCase() : level;
|
|
18
|
+
if (!LOG_LEVELS.includes(normalized)) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Invalid logging.level ${JSON.stringify(level)}. Use one of: ${LOG_LEVELS.join(', ')} (any case).`
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
return normalized;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function validateRetentionInDays(retentionInDays) {
|
|
27
|
+
if (!RETENTION_DAYS.includes(retentionInDays)) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`Invalid logging.retentionInDays ${JSON.stringify(retentionInDays)}. CloudWatch accepts: ${RETENTION_DAYS.join(', ')}.`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return retentionInDays;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Map appDefinition.logging to provider-level serverless settings.
|
|
37
|
+
* Returns an empty object when logging is absent (ADR-048 §12: no change).
|
|
38
|
+
*
|
|
39
|
+
* @param {Object} [logging] - appDefinition.logging
|
|
40
|
+
* @returns {{ frameworkVersion?: string, provider?: Object }}
|
|
41
|
+
*/
|
|
42
|
+
function buildLoggingProviderConfig(logging) {
|
|
43
|
+
if (!logging) return {};
|
|
44
|
+
|
|
45
|
+
const provider = {};
|
|
46
|
+
const result = {};
|
|
47
|
+
|
|
48
|
+
if (logging.level !== undefined) {
|
|
49
|
+
const level = normalizeLogLevel(logging.level);
|
|
50
|
+
result.frameworkVersion = LOGGING_CONFIG_FRAMEWORK_VERSION;
|
|
51
|
+
if (EMIT_LAMBDA_JSON_LOGGING_CONFIG) {
|
|
52
|
+
provider.logs = {
|
|
53
|
+
lambda: {
|
|
54
|
+
logFormat: 'JSON',
|
|
55
|
+
applicationLogLevel: level,
|
|
56
|
+
systemLogLevel: 'INFO',
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (logging.retentionInDays !== undefined) {
|
|
63
|
+
provider.logRetentionInDays = validateRetentionInDays(
|
|
64
|
+
logging.retentionInDays
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (Object.keys(provider).length > 0) result.provider = provider;
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
LOG_LEVELS,
|
|
74
|
+
RETENTION_DAYS,
|
|
75
|
+
normalizeLogLevel,
|
|
76
|
+
buildLoggingProviderConfig,
|
|
77
|
+
};
|
|
@@ -150,7 +150,7 @@ describe('composeServerlessDefinition', () => {
|
|
|
150
150
|
|
|
151
151
|
expect(result.service).toBe('test-app');
|
|
152
152
|
expect(result.provider.name).toBe('aws');
|
|
153
|
-
expect(result.provider.runtime).toBe('
|
|
153
|
+
expect(result.provider.runtime).toBe('nodejs22.x');
|
|
154
154
|
expect(result.provider.region).toBe('us-east-1');
|
|
155
155
|
expect(result.provider.stage).toBe('${opt:stage}');
|
|
156
156
|
expect(result.frameworkVersion).toBe('>=3.17.0');
|
|
@@ -238,6 +238,54 @@ describe('composeServerlessDefinition', () => {
|
|
|
238
238
|
});
|
|
239
239
|
});
|
|
240
240
|
|
|
241
|
+
describe('Logging (ADR-048)', () => {
|
|
242
|
+
it('carries logging into provider.logs, FRIGG_LOG_LEVEL and logRetentionInDays', async () => {
|
|
243
|
+
const result = await composeServerlessDefinition({
|
|
244
|
+
integrations: [mockIntegration],
|
|
245
|
+
logging: { level: 'debug', retentionInDays: 30 },
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
expect(result.frameworkVersion).toBe('>=3.58.0');
|
|
249
|
+
expect(result.provider.logs.lambda).toEqual({
|
|
250
|
+
logFormat: 'JSON',
|
|
251
|
+
applicationLogLevel: 'DEBUG',
|
|
252
|
+
systemLogLevel: 'INFO',
|
|
253
|
+
});
|
|
254
|
+
expect(result.provider.logRetentionInDays).toBe(30);
|
|
255
|
+
expect(result.provider.environment.FRIGG_LOG_LEVEL).toBe('DEBUG');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('sets no function-level logs, which would replace the provider block', async () => {
|
|
259
|
+
const result = await composeServerlessDefinition({
|
|
260
|
+
integrations: [mockIntegration],
|
|
261
|
+
logging: { level: 'warn' },
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const functions = Object.values(result.functions);
|
|
265
|
+
expect(functions.length).toBeGreaterThan(0);
|
|
266
|
+
expect(functions.every((fn) => fn.logs === undefined)).toBe(true);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('lets logging.level win over environment: { FRIGG_LOG_LEVEL: true }', async () => {
|
|
270
|
+
const result = await composeServerlessDefinition({
|
|
271
|
+
integrations: [],
|
|
272
|
+
environment: { FRIGG_LOG_LEVEL: true },
|
|
273
|
+
logging: { level: 'error' },
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
expect(result.provider.environment.FRIGG_LOG_LEVEL).toBe('ERROR');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('changes nothing when logging is absent', async () => {
|
|
280
|
+
const result = await composeServerlessDefinition({ integrations: [] });
|
|
281
|
+
|
|
282
|
+
expect(result.frameworkVersion).toBe('>=3.17.0');
|
|
283
|
+
expect(result.provider.logs).toBeUndefined();
|
|
284
|
+
expect(result.provider.logRetentionInDays).toBeUndefined();
|
|
285
|
+
expect(result.provider.environment).not.toHaveProperty('FRIGG_LOG_LEVEL');
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
241
289
|
describe('VPC Configuration', () => {
|
|
242
290
|
it('should add VPC configuration when vpc.enable is true with discover mode', async () => {
|
|
243
291
|
const appDefinition = {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/devtools",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.
|
|
4
|
+
"version": "2.0.0--canary.654.be44e66.0",
|
|
5
5
|
"bin": {
|
|
6
6
|
"frigg": "./frigg-cli/index.js"
|
|
7
7
|
},
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
"@babel/eslint-parser": "^7.18.9",
|
|
27
27
|
"@babel/parser": "^7.25.3",
|
|
28
28
|
"@babel/traverse": "^7.25.3",
|
|
29
|
-
"@friggframework/core": "2.0.0--canary.
|
|
30
|
-
"@friggframework/schemas": "2.0.0--canary.
|
|
31
|
-
"@friggframework/test": "2.0.0--canary.
|
|
29
|
+
"@friggframework/core": "2.0.0--canary.654.be44e66.0",
|
|
30
|
+
"@friggframework/schemas": "2.0.0--canary.654.be44e66.0",
|
|
31
|
+
"@friggframework/test": "2.0.0--canary.654.be44e66.0",
|
|
32
32
|
"@hapi/boom": "^10.0.1",
|
|
33
33
|
"@inquirer/prompts": "^5.3.8",
|
|
34
34
|
"axios": "^1.18.0",
|
|
@@ -56,12 +56,12 @@
|
|
|
56
56
|
"validate-npm-package-name": "^5.0.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@friggframework/eslint-config": "2.0.0--canary.
|
|
60
|
-
"@friggframework/prettier-config": "2.0.0--canary.
|
|
59
|
+
"@friggframework/eslint-config": "2.0.0--canary.654.be44e66.0",
|
|
60
|
+
"@friggframework/prettier-config": "2.0.0--canary.654.be44e66.0",
|
|
61
61
|
"aws-sdk-client-mock": "^4.1.0",
|
|
62
62
|
"aws-sdk-client-mock-jest": "^4.1.0",
|
|
63
63
|
"jest": "^30.1.3",
|
|
64
|
-
"osls": "^3.
|
|
64
|
+
"osls": "^3.58.0",
|
|
65
65
|
"prettier": "^2.7.1",
|
|
66
66
|
"serverless-dotenv-plugin": "^6.0.0",
|
|
67
67
|
"serverless-esbuild": "^1.54.3",
|
|
@@ -89,5 +89,5 @@
|
|
|
89
89
|
"publishConfig": {
|
|
90
90
|
"access": "public"
|
|
91
91
|
},
|
|
92
|
-
"gitHead": "
|
|
92
|
+
"gitHead": "be44e66226ec1db051c264a5f8cf02998f3aa14c"
|
|
93
93
|
}
|
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base Serverless Definition Factory
|
|
3
|
-
*
|
|
4
|
-
* Utility Layer - Hexagonal Architecture
|
|
5
|
-
*
|
|
6
|
-
* Creates the base serverless.yml configuration with core functions,
|
|
7
|
-
* resources, plugins, and provider settings.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const { buildEnvironment } = require('../environment-builder');
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Create base serverless definition with core functions and resources
|
|
14
|
-
*
|
|
15
|
-
* This creates the foundation serverless configuration that all
|
|
16
|
-
* Frigg applications need, including:
|
|
17
|
-
* - Core Lambda functions (auth, user, health, dbMigrate)
|
|
18
|
-
* - Error handling infrastructure (SQS, SNS, CloudWatch)
|
|
19
|
-
* - Prisma Lambda Layer
|
|
20
|
-
* - Base plugins and esbuild configuration
|
|
21
|
-
*
|
|
22
|
-
* @param {Object} AppDefinition - Application definition
|
|
23
|
-
* @param {Object} appEnvironmentVars - Environment variables from app definition
|
|
24
|
-
* @param {Object} discoveredResources - AWS resources discovered during build
|
|
25
|
-
* @returns {Object} Base serverless definition
|
|
26
|
-
*/
|
|
27
|
-
function createBaseDefinition(
|
|
28
|
-
AppDefinition,
|
|
29
|
-
appEnvironmentVars,
|
|
30
|
-
discoveredResources
|
|
31
|
-
) {
|
|
32
|
-
const region = process.env.AWS_REGION || 'us-east-1';
|
|
33
|
-
|
|
34
|
-
// Function-level package config to exclude Prisma and AWS SDK
|
|
35
|
-
const functionPackageConfig = {
|
|
36
|
-
exclude: [
|
|
37
|
-
// Exclude AWS SDK (already in Lambda runtime or externalized by esbuild)
|
|
38
|
-
'node_modules/aws-sdk/**',
|
|
39
|
-
'node_modules/@aws-sdk/**',
|
|
40
|
-
|
|
41
|
-
// Exclude Prisma (provided via Lambda Layer)
|
|
42
|
-
'node_modules/@prisma/**',
|
|
43
|
-
'node_modules/.prisma/**',
|
|
44
|
-
'node_modules/prisma/**',
|
|
45
|
-
'node_modules/@friggframework/core/generated/**',
|
|
46
|
-
|
|
47
|
-
// Exclude nested node_modules from symlinked frigg packages (for npm link development)
|
|
48
|
-
'node_modules/@friggframework/core/node_modules/**',
|
|
49
|
-
'node_modules/@friggframework/devtools/node_modules/**',
|
|
50
|
-
|
|
51
|
-
// Exclude development/test files from backend project
|
|
52
|
-
'coverage/**',
|
|
53
|
-
'test/**',
|
|
54
|
-
'src/**',
|
|
55
|
-
'layers/**',
|
|
56
|
-
'**/*.test.js',
|
|
57
|
-
'**/*.spec.js',
|
|
58
|
-
'.git/**',
|
|
59
|
-
'.github/**',
|
|
60
|
-
],
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
frameworkVersion: '>=3.17.0',
|
|
65
|
-
service: AppDefinition.name || 'create-frigg-app',
|
|
66
|
-
package: {
|
|
67
|
-
individually: true,
|
|
68
|
-
},
|
|
69
|
-
useDotenv: true,
|
|
70
|
-
provider: {
|
|
71
|
-
name: AppDefinition.provider || 'aws',
|
|
72
|
-
...(process.env.AWS_PROFILE && { profile: process.env.AWS_PROFILE }),
|
|
73
|
-
runtime: 'nodejs22.x', // Node.js 22.x (latest Lambda runtime with AWS SDK v3)
|
|
74
|
-
timeout: 29, // Set to 29s to give buffer before API Gateway's 30s timeout
|
|
75
|
-
region,
|
|
76
|
-
stage: '${opt:stage}',
|
|
77
|
-
environment: buildEnvironment(appEnvironmentVars, discoveredResources),
|
|
78
|
-
iamRoleStatements: [
|
|
79
|
-
{
|
|
80
|
-
Effect: 'Allow',
|
|
81
|
-
Action: ['sns:Publish'],
|
|
82
|
-
Resource: { Ref: 'InternalErrorBridgeTopic' },
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
Effect: 'Allow',
|
|
86
|
-
Action: [
|
|
87
|
-
'sqs:SendMessage',
|
|
88
|
-
'sqs:SendMessageBatch',
|
|
89
|
-
'sqs:GetQueueUrl',
|
|
90
|
-
'sqs:GetQueueAttributes',
|
|
91
|
-
],
|
|
92
|
-
Resource: [
|
|
93
|
-
{ 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'] },
|
|
94
|
-
{
|
|
95
|
-
'Fn::Join': [
|
|
96
|
-
':',
|
|
97
|
-
[
|
|
98
|
-
'arn:aws:sqs:${self:provider.region}:*:${self:service}--${self:provider.stage}-*Queue',
|
|
99
|
-
],
|
|
100
|
-
],
|
|
101
|
-
},
|
|
102
|
-
],
|
|
103
|
-
},
|
|
104
|
-
],
|
|
105
|
-
httpApi: {
|
|
106
|
-
payload: '2.0',
|
|
107
|
-
cors: {
|
|
108
|
-
allowedOrigins: ['*'],
|
|
109
|
-
allowedHeaders: ['*'],
|
|
110
|
-
allowedMethods: ['*'],
|
|
111
|
-
allowCredentials: false,
|
|
112
|
-
},
|
|
113
|
-
name: '${opt:stage, "dev"}-${self:service}',
|
|
114
|
-
disableDefaultEndpoint: false,
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
plugins: [
|
|
118
|
-
'serverless-esbuild',
|
|
119
|
-
'serverless-dotenv-plugin',
|
|
120
|
-
'serverless-offline-sqs',
|
|
121
|
-
'serverless-offline',
|
|
122
|
-
'@friggframework/serverless-plugin',
|
|
123
|
-
],
|
|
124
|
-
custom: {
|
|
125
|
-
esbuild: {
|
|
126
|
-
bundle: true,
|
|
127
|
-
minify: true,
|
|
128
|
-
sourcemap: true,
|
|
129
|
-
target: 'node22',
|
|
130
|
-
platform: 'node',
|
|
131
|
-
format: 'cjs',
|
|
132
|
-
external: [
|
|
133
|
-
'@aws-sdk/*',
|
|
134
|
-
'aws-sdk',
|
|
135
|
-
'@prisma/client',
|
|
136
|
-
'prisma',
|
|
137
|
-
'.prisma/*',
|
|
138
|
-
],
|
|
139
|
-
packager: 'npm',
|
|
140
|
-
keepNames: true,
|
|
141
|
-
keepOutputDirectory: false, // Clean up .esbuild directory after packaging
|
|
142
|
-
exclude: [
|
|
143
|
-
'aws-sdk',
|
|
144
|
-
'@aws-sdk/*',
|
|
145
|
-
'@prisma/client',
|
|
146
|
-
'prisma',
|
|
147
|
-
],
|
|
148
|
-
},
|
|
149
|
-
'serverless-offline': {
|
|
150
|
-
httpPort: 3001,
|
|
151
|
-
lambdaPort: 4001,
|
|
152
|
-
websocketPort: 3002,
|
|
153
|
-
location: '.', // Set base directory for handler resolution to current directory
|
|
154
|
-
skipCacheInvalidation: false,
|
|
155
|
-
},
|
|
156
|
-
'serverless-offline-sqs': {
|
|
157
|
-
autoCreate: false,
|
|
158
|
-
apiVersion: '2012-11-05',
|
|
159
|
-
endpoint: 'http://localhost:4566',
|
|
160
|
-
region,
|
|
161
|
-
accessKeyId: 'root',
|
|
162
|
-
secretAccessKey: 'root',
|
|
163
|
-
skipCacheInvalidation: false,
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
functions: {
|
|
167
|
-
auth: {
|
|
168
|
-
handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
|
|
169
|
-
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
170
|
-
skipEsbuild: true, // Handlers in node_modules don't need bundling
|
|
171
|
-
package: functionPackageConfig,
|
|
172
|
-
events: [
|
|
173
|
-
{ httpApi: { path: '/api/integrations', method: 'ANY' } },
|
|
174
|
-
{
|
|
175
|
-
httpApi: {
|
|
176
|
-
path: '/api/integrations/{proxy+}',
|
|
177
|
-
method: 'ANY',
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
{ httpApi: { path: '/api/authorize', method: 'ANY' } },
|
|
181
|
-
],
|
|
182
|
-
},
|
|
183
|
-
user: {
|
|
184
|
-
handler: 'node_modules/@friggframework/core/handlers/routers/user.handler',
|
|
185
|
-
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
186
|
-
skipEsbuild: true, // Handlers in node_modules don't need bundling
|
|
187
|
-
package: functionPackageConfig,
|
|
188
|
-
events: [{ httpApi: { path: '/user/{proxy+}', method: 'ANY' } }],
|
|
189
|
-
},
|
|
190
|
-
health: {
|
|
191
|
-
handler: 'node_modules/@friggframework/core/handlers/routers/health.handler',
|
|
192
|
-
layers: [{ Ref: 'PrismaLambdaLayer' }],
|
|
193
|
-
skipEsbuild: true, // Handlers in node_modules don't need bundling
|
|
194
|
-
package: functionPackageConfig,
|
|
195
|
-
events: [
|
|
196
|
-
{ httpApi: { path: '/health', method: 'GET' } },
|
|
197
|
-
{ httpApi: { path: '/health/{proxy+}', method: 'GET' } },
|
|
198
|
-
],
|
|
199
|
-
},
|
|
200
|
-
dbMigrate: {
|
|
201
|
-
handler: 'node_modules/@friggframework/core/handlers/database-migration-handler.handler',
|
|
202
|
-
// DO NOT use Prisma layer - this function includes Prisma CLI separately
|
|
203
|
-
skipEsbuild: true, // Handlers in node_modules don't need bundling
|
|
204
|
-
timeout: 300, // 5 minutes for long-running migrations
|
|
205
|
-
memorySize: 1024, // Extra memory for Prisma CLI and migration operations
|
|
206
|
-
reservedConcurrency: 1, // Prevent concurrent migrations (CRITICAL for data safety)
|
|
207
|
-
description: 'Runs database migrations via Prisma CLI (invoke manually from CI/CD or triggers). Prisma CLI bundled separately.',
|
|
208
|
-
package: {
|
|
209
|
-
individually: true,
|
|
210
|
-
patterns: [
|
|
211
|
-
// Include handler
|
|
212
|
-
'node_modules/@friggframework/core/handlers/database-migration-handler.js',
|
|
213
|
-
|
|
214
|
-
// Include ONLY PostgreSQL Prisma client (exclude MongoDB)
|
|
215
|
-
'node_modules/@friggframework/core/generated/prisma-postgresql/**',
|
|
216
|
-
'!node_modules/@friggframework/core/generated/prisma-mongodb/**', // Exclude MongoDB client entirely
|
|
217
|
-
|
|
218
|
-
// Include Prisma runtime
|
|
219
|
-
'node_modules/@prisma/client/**',
|
|
220
|
-
'node_modules/.prisma/**',
|
|
221
|
-
'node_modules/prisma/**', // Prisma CLI
|
|
222
|
-
|
|
223
|
-
// Exclude unnecessary engines and files
|
|
224
|
-
'!node_modules/prisma/node_modules/**',
|
|
225
|
-
'!**/query-engine-darwin*', // Exclude macOS binaries (keep rhel for Lambda)
|
|
226
|
-
'!**/runtime/*.wasm', // WASM engines
|
|
227
|
-
'!**/*.md',
|
|
228
|
-
'!**/*.map',
|
|
229
|
-
'!**/LICENSE*',
|
|
230
|
-
'!**/*.d.ts',
|
|
231
|
-
'!**/*.d.mts',
|
|
232
|
-
],
|
|
233
|
-
},
|
|
234
|
-
maximumEventAge: 60,
|
|
235
|
-
maximumRetryAttempts: 0,
|
|
236
|
-
tags: {
|
|
237
|
-
Purpose: 'DatabaseMigration',
|
|
238
|
-
ManagedBy: 'Frigg',
|
|
239
|
-
},
|
|
240
|
-
environment: {
|
|
241
|
-
CI: '1',
|
|
242
|
-
PRISMA_HIDE_UPDATE_MESSAGE: '1',
|
|
243
|
-
PRISMA_MIGRATE_SKIP_SEED: '1',
|
|
244
|
-
},
|
|
245
|
-
},
|
|
246
|
-
},
|
|
247
|
-
layers: {
|
|
248
|
-
prisma: {
|
|
249
|
-
path: 'layers/prisma',
|
|
250
|
-
name: '${self:service}-prisma-${sls:stage}',
|
|
251
|
-
description: 'Prisma runtime client only (NO CLI) with rhel-openssl-3.0.x binaries (~10-15MB). CLI packaged separately in dbMigrate function.',
|
|
252
|
-
compatibleRuntimes: ['nodejs20.x', 'nodejs22.x'],
|
|
253
|
-
retain: false,
|
|
254
|
-
},
|
|
255
|
-
},
|
|
256
|
-
resources: {
|
|
257
|
-
Resources: {
|
|
258
|
-
InternalErrorQueue: {
|
|
259
|
-
Type: 'AWS::SQS::Queue',
|
|
260
|
-
Properties: {
|
|
261
|
-
QueueName:
|
|
262
|
-
'${self:service}-internal-error-queue-${self:provider.stage}',
|
|
263
|
-
MessageRetentionPeriod: 300,
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
InternalErrorBridgeTopic: {
|
|
267
|
-
Type: 'AWS::SNS::Topic',
|
|
268
|
-
Properties: {
|
|
269
|
-
Subscription: [
|
|
270
|
-
{
|
|
271
|
-
Protocol: 'sqs',
|
|
272
|
-
Endpoint: {
|
|
273
|
-
'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
|
-
],
|
|
277
|
-
},
|
|
278
|
-
},
|
|
279
|
-
InternalErrorBridgePolicy: {
|
|
280
|
-
Type: 'AWS::SQS::QueuePolicy',
|
|
281
|
-
Properties: {
|
|
282
|
-
Queues: [{ Ref: 'InternalErrorQueue' }],
|
|
283
|
-
PolicyDocument: {
|
|
284
|
-
Version: '2012-10-17',
|
|
285
|
-
Statement: [
|
|
286
|
-
{
|
|
287
|
-
Sid: 'Allow Dead Letter SNS to publish to SQS',
|
|
288
|
-
Effect: 'Allow',
|
|
289
|
-
Principal: { Service: 'sns.amazonaws.com' },
|
|
290
|
-
Resource: {
|
|
291
|
-
'Fn::GetAtt': [
|
|
292
|
-
'InternalErrorQueue',
|
|
293
|
-
'Arn',
|
|
294
|
-
],
|
|
295
|
-
},
|
|
296
|
-
Action: [
|
|
297
|
-
'SQS:SendMessage',
|
|
298
|
-
'SQS:SendMessageBatch',
|
|
299
|
-
],
|
|
300
|
-
Condition: {
|
|
301
|
-
ArnEquals: {
|
|
302
|
-
'aws:SourceArn': {
|
|
303
|
-
Ref: 'InternalErrorBridgeTopic',
|
|
304
|
-
},
|
|
305
|
-
},
|
|
306
|
-
},
|
|
307
|
-
},
|
|
308
|
-
],
|
|
309
|
-
},
|
|
310
|
-
},
|
|
311
|
-
},
|
|
312
|
-
ApiGatewayAlarm5xx: {
|
|
313
|
-
Type: 'AWS::CloudWatch::Alarm',
|
|
314
|
-
Properties: {
|
|
315
|
-
AlarmDescription: 'API Gateway 5xx Errors',
|
|
316
|
-
Namespace: 'AWS/ApiGateway',
|
|
317
|
-
MetricName: '5XXError',
|
|
318
|
-
Statistic: 'Sum',
|
|
319
|
-
Threshold: 0,
|
|
320
|
-
ComparisonOperator: 'GreaterThanThreshold',
|
|
321
|
-
EvaluationPeriods: 1,
|
|
322
|
-
Period: 60,
|
|
323
|
-
AlarmActions: [{ Ref: 'InternalErrorBridgeTopic' }],
|
|
324
|
-
Dimensions: [
|
|
325
|
-
{ Name: 'ApiId', Value: { Ref: 'HttpApi' } },
|
|
326
|
-
{ Name: 'Stage', Value: '${self:provider.stage}' },
|
|
327
|
-
],
|
|
328
|
-
},
|
|
329
|
-
},
|
|
330
|
-
},
|
|
331
|
-
},
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
module.exports = {
|
|
336
|
-
createBaseDefinition,
|
|
337
|
-
};
|
|
338
|
-
|