@friggframework/devtools 2.0.0--canary.397.155fecd.0 → 2.0.0--canary.398.c9e5d61.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/build-command/index.js +25 -2
- package/frigg-cli/deploy-command/index.js +26 -2
- package/frigg-cli/generate-iam-command.js +115 -0
- package/frigg-cli/index.js +11 -1
- package/infrastructure/AWS-DISCOVERY-TROUBLESHOOTING.md +245 -0
- package/infrastructure/AWS-IAM-CREDENTIAL-NEEDS.md +561 -0
- package/infrastructure/DEPLOYMENT-INSTRUCTIONS.md +268 -0
- package/infrastructure/GENERATE-IAM-DOCS.md +253 -0
- package/infrastructure/README-TESTING.md +332 -0
- package/infrastructure/WEBSOCKET-CONFIGURATION.md +105 -0
- package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
- package/infrastructure/__tests__/helpers/test-utils.js +277 -0
- package/infrastructure/aws-discovery.js +416 -0
- package/infrastructure/aws-discovery.test.js +373 -0
- package/infrastructure/build-time-discovery.js +206 -0
- package/infrastructure/build-time-discovery.test.js +375 -0
- package/infrastructure/create-frigg-infrastructure.js +9 -1
- package/infrastructure/frigg-deployment-iam-stack.yaml +370 -0
- package/infrastructure/iam-generator.js +644 -0
- package/infrastructure/iam-generator.test.js +169 -0
- package/infrastructure/integration.test.js +383 -0
- package/infrastructure/run-discovery.js +108 -0
- package/infrastructure/serverless-template.js +557 -26
- package/infrastructure/serverless-template.test.js +498 -0
- package/package.json +6 -5
- package/test/mock-integration.js +14 -4
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Find the actual path to node_modules directory
|
|
6
|
+
* Tries multiple methods to locate node_modules:
|
|
7
|
+
* 1. Traversing up from current directory
|
|
8
|
+
* 2. Using npm root command
|
|
9
|
+
* 3. Looking for package.json and adjacent node_modules
|
|
10
|
+
* @returns {string} Path to node_modules directory
|
|
11
|
+
*/
|
|
5
12
|
const findNodeModulesPath = () => {
|
|
6
13
|
try {
|
|
7
14
|
// Method 1: Try to find node_modules by traversing up from current directory
|
|
@@ -75,7 +82,12 @@ const findNodeModulesPath = () => {
|
|
|
75
82
|
}
|
|
76
83
|
};
|
|
77
84
|
|
|
78
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Modify handler paths to point to the correct node_modules location
|
|
87
|
+
* Only modifies paths when running in offline mode
|
|
88
|
+
* @param {Object} functions - Serverless functions configuration object
|
|
89
|
+
* @returns {Object} Modified functions object with updated handler paths
|
|
90
|
+
*/
|
|
79
91
|
const modifyHandlerPaths = (functions) => {
|
|
80
92
|
// Check if we're running in offline mode
|
|
81
93
|
const isOffline = process.argv.includes('offline');
|
|
@@ -94,7 +106,8 @@ const modifyHandlerPaths = (functions) => {
|
|
|
94
106
|
const functionDef = modifiedFunctions[functionName];
|
|
95
107
|
if (functionDef?.handler?.includes('node_modules/')) {
|
|
96
108
|
// Replace node_modules/ with the actual path to node_modules/
|
|
97
|
-
|
|
109
|
+
const relativePath = path.relative(process.cwd(), nodeModulesPath);
|
|
110
|
+
functionDef.handler = functionDef.handler.replace('node_modules/', `${relativePath}/`);
|
|
98
111
|
console.log(`Updated handler for ${functionName}: ${functionDef.handler}`);
|
|
99
112
|
}
|
|
100
113
|
}
|
|
@@ -102,6 +115,329 @@ const modifyHandlerPaths = (functions) => {
|
|
|
102
115
|
return modifiedFunctions;
|
|
103
116
|
};
|
|
104
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Create VPC infrastructure resources for CloudFormation
|
|
120
|
+
* Creates VPC, subnets, NAT gateway, route tables, and security groups
|
|
121
|
+
* @param {Object} AppDefinition - Application definition object
|
|
122
|
+
* @param {Object} AppDefinition.vpc - VPC configuration
|
|
123
|
+
* @param {string} [AppDefinition.vpc.cidrBlock='10.0.0.0/16'] - CIDR block for VPC
|
|
124
|
+
* @returns {Object} CloudFormation resources for VPC infrastructure
|
|
125
|
+
*/
|
|
126
|
+
const createVPCInfrastructure = (AppDefinition) => {
|
|
127
|
+
const vpcResources = {
|
|
128
|
+
// VPC
|
|
129
|
+
FriggVPC: {
|
|
130
|
+
Type: 'AWS::EC2::VPC',
|
|
131
|
+
Properties: {
|
|
132
|
+
CidrBlock: AppDefinition.vpc.cidrBlock || '10.0.0.0/16',
|
|
133
|
+
EnableDnsHostnames: true,
|
|
134
|
+
EnableDnsSupport: true,
|
|
135
|
+
Tags: [
|
|
136
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc' }
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
// Internet Gateway
|
|
142
|
+
FriggInternetGateway: {
|
|
143
|
+
Type: 'AWS::EC2::InternetGateway',
|
|
144
|
+
Properties: {
|
|
145
|
+
Tags: [
|
|
146
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-igw' }
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
// Attach Internet Gateway to VPC
|
|
152
|
+
FriggVPCGatewayAttachment: {
|
|
153
|
+
Type: 'AWS::EC2::VPCGatewayAttachment',
|
|
154
|
+
Properties: {
|
|
155
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
156
|
+
InternetGatewayId: { Ref: 'FriggInternetGateway' }
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
// Public Subnet for NAT Gateway
|
|
161
|
+
FriggPublicSubnet: {
|
|
162
|
+
Type: 'AWS::EC2::Subnet',
|
|
163
|
+
Properties: {
|
|
164
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
165
|
+
CidrBlock: '10.0.1.0/24',
|
|
166
|
+
AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
|
|
167
|
+
MapPublicIpOnLaunch: true,
|
|
168
|
+
Tags: [
|
|
169
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-subnet' }
|
|
170
|
+
]
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
// Private Subnet 1 for Lambda
|
|
175
|
+
FriggPrivateSubnet1: {
|
|
176
|
+
Type: 'AWS::EC2::Subnet',
|
|
177
|
+
Properties: {
|
|
178
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
179
|
+
CidrBlock: '10.0.2.0/24',
|
|
180
|
+
AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
|
|
181
|
+
Tags: [
|
|
182
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-subnet-1' }
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
// Private Subnet 2 for Lambda (different AZ for redundancy)
|
|
188
|
+
FriggPrivateSubnet2: {
|
|
189
|
+
Type: 'AWS::EC2::Subnet',
|
|
190
|
+
Properties: {
|
|
191
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
192
|
+
CidrBlock: '10.0.3.0/24',
|
|
193
|
+
AvailabilityZone: { 'Fn::Select': [1, { 'Fn::GetAZs': '' }] },
|
|
194
|
+
Tags: [
|
|
195
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-subnet-2' }
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
// Elastic IP for NAT Gateway
|
|
201
|
+
FriggNATGatewayEIP: {
|
|
202
|
+
Type: 'AWS::EC2::EIP',
|
|
203
|
+
Properties: {
|
|
204
|
+
Domain: 'vpc',
|
|
205
|
+
Tags: [
|
|
206
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-eip' }
|
|
207
|
+
]
|
|
208
|
+
},
|
|
209
|
+
DependsOn: 'FriggVPCGatewayAttachment'
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
// NAT Gateway for private subnet internet access
|
|
213
|
+
FriggNATGateway: {
|
|
214
|
+
Type: 'AWS::EC2::NatGateway',
|
|
215
|
+
Properties: {
|
|
216
|
+
AllocationId: { 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'] },
|
|
217
|
+
SubnetId: { Ref: 'FriggPublicSubnet' },
|
|
218
|
+
Tags: [
|
|
219
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-gateway' }
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
// Public Route Table
|
|
225
|
+
FriggPublicRouteTable: {
|
|
226
|
+
Type: 'AWS::EC2::RouteTable',
|
|
227
|
+
Properties: {
|
|
228
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
229
|
+
Tags: [
|
|
230
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-rt' }
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
// Public Route to Internet Gateway
|
|
236
|
+
FriggPublicRoute: {
|
|
237
|
+
Type: 'AWS::EC2::Route',
|
|
238
|
+
Properties: {
|
|
239
|
+
RouteTableId: { Ref: 'FriggPublicRouteTable' },
|
|
240
|
+
DestinationCidrBlock: '0.0.0.0/0',
|
|
241
|
+
GatewayId: { Ref: 'FriggInternetGateway' }
|
|
242
|
+
},
|
|
243
|
+
DependsOn: 'FriggVPCGatewayAttachment'
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
// Associate Public Subnet with Public Route Table
|
|
247
|
+
FriggPublicSubnetRouteTableAssociation: {
|
|
248
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
249
|
+
Properties: {
|
|
250
|
+
SubnetId: { Ref: 'FriggPublicSubnet' },
|
|
251
|
+
RouteTableId: { Ref: 'FriggPublicRouteTable' }
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
// Private Route Table
|
|
256
|
+
FriggPrivateRouteTable: {
|
|
257
|
+
Type: 'AWS::EC2::RouteTable',
|
|
258
|
+
Properties: {
|
|
259
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
260
|
+
Tags: [
|
|
261
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-rt' }
|
|
262
|
+
]
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
// Private Route to NAT Gateway
|
|
267
|
+
FriggPrivateRoute: {
|
|
268
|
+
Type: 'AWS::EC2::Route',
|
|
269
|
+
Properties: {
|
|
270
|
+
RouteTableId: { Ref: 'FriggPrivateRouteTable' },
|
|
271
|
+
DestinationCidrBlock: '0.0.0.0/0',
|
|
272
|
+
NatGatewayId: { Ref: 'FriggNATGateway' }
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
// Associate Private Subnet 1 with Private Route Table
|
|
277
|
+
FriggPrivateSubnet1RouteTableAssociation: {
|
|
278
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
279
|
+
Properties: {
|
|
280
|
+
SubnetId: { Ref: 'FriggPrivateSubnet1' },
|
|
281
|
+
RouteTableId: { Ref: 'FriggPrivateRouteTable' }
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
// Associate Private Subnet 2 with Private Route Table
|
|
286
|
+
FriggPrivateSubnet2RouteTableAssociation: {
|
|
287
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
288
|
+
Properties: {
|
|
289
|
+
SubnetId: { Ref: 'FriggPrivateSubnet2' },
|
|
290
|
+
RouteTableId: { Ref: 'FriggPrivateRouteTable' }
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
// Security Group for Lambda functions
|
|
295
|
+
FriggLambdaSecurityGroup: {
|
|
296
|
+
Type: 'AWS::EC2::SecurityGroup',
|
|
297
|
+
Properties: {
|
|
298
|
+
GroupDescription: 'Security group for Frigg Lambda functions',
|
|
299
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
300
|
+
SecurityGroupEgress: [
|
|
301
|
+
{
|
|
302
|
+
IpProtocol: 'tcp',
|
|
303
|
+
FromPort: 443,
|
|
304
|
+
ToPort: 443,
|
|
305
|
+
CidrIp: '0.0.0.0/0',
|
|
306
|
+
Description: 'HTTPS outbound'
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
IpProtocol: 'tcp',
|
|
310
|
+
FromPort: 80,
|
|
311
|
+
ToPort: 80,
|
|
312
|
+
CidrIp: '0.0.0.0/0',
|
|
313
|
+
Description: 'HTTP outbound'
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
IpProtocol: 'tcp',
|
|
317
|
+
FromPort: 53,
|
|
318
|
+
ToPort: 53,
|
|
319
|
+
CidrIp: '0.0.0.0/0',
|
|
320
|
+
Description: 'DNS TCP'
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
IpProtocol: 'udp',
|
|
324
|
+
FromPort: 53,
|
|
325
|
+
ToPort: 53,
|
|
326
|
+
CidrIp: '0.0.0.0/0',
|
|
327
|
+
Description: 'DNS UDP'
|
|
328
|
+
}
|
|
329
|
+
],
|
|
330
|
+
Tags: [
|
|
331
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-sg' }
|
|
332
|
+
]
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// Add VPC Endpoints for cost optimization
|
|
338
|
+
if (AppDefinition.vpc.enableVPCEndpoints !== false) {
|
|
339
|
+
// S3 Gateway Endpoint (free)
|
|
340
|
+
vpcResources.FriggS3VPCEndpoint = {
|
|
341
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
342
|
+
Properties: {
|
|
343
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
344
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.s3',
|
|
345
|
+
VpcEndpointType: 'Gateway',
|
|
346
|
+
RouteTableIds: [
|
|
347
|
+
{ Ref: 'FriggPrivateRouteTable' }
|
|
348
|
+
]
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
// DynamoDB Gateway Endpoint (free)
|
|
353
|
+
vpcResources.FriggDynamoDBVPCEndpoint = {
|
|
354
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
355
|
+
Properties: {
|
|
356
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
357
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.dynamodb',
|
|
358
|
+
VpcEndpointType: 'Gateway',
|
|
359
|
+
RouteTableIds: [
|
|
360
|
+
{ Ref: 'FriggPrivateRouteTable' }
|
|
361
|
+
]
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// KMS Interface Endpoint (paid, but useful if using KMS)
|
|
366
|
+
if (AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true) {
|
|
367
|
+
vpcResources.FriggKMSVPCEndpoint = {
|
|
368
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
369
|
+
Properties: {
|
|
370
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
371
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.kms',
|
|
372
|
+
VpcEndpointType: 'Interface',
|
|
373
|
+
SubnetIds: [
|
|
374
|
+
{ Ref: 'FriggPrivateSubnet1' },
|
|
375
|
+
{ Ref: 'FriggPrivateSubnet2' }
|
|
376
|
+
],
|
|
377
|
+
SecurityGroupIds: [
|
|
378
|
+
{ Ref: 'FriggVPCEndpointSecurityGroup' }
|
|
379
|
+
],
|
|
380
|
+
PrivateDnsEnabled: true
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Secrets Manager Interface Endpoint (paid, but useful for secrets)
|
|
386
|
+
vpcResources.FriggSecretsManagerVPCEndpoint = {
|
|
387
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
388
|
+
Properties: {
|
|
389
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
390
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.secretsmanager',
|
|
391
|
+
VpcEndpointType: 'Interface',
|
|
392
|
+
SubnetIds: [
|
|
393
|
+
{ Ref: 'FriggPrivateSubnet1' },
|
|
394
|
+
{ Ref: 'FriggPrivateSubnet2' }
|
|
395
|
+
],
|
|
396
|
+
SecurityGroupIds: [
|
|
397
|
+
{ Ref: 'FriggVPCEndpointSecurityGroup' }
|
|
398
|
+
],
|
|
399
|
+
PrivateDnsEnabled: true
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
// Security Group for VPC Endpoints
|
|
404
|
+
vpcResources.FriggVPCEndpointSecurityGroup = {
|
|
405
|
+
Type: 'AWS::EC2::SecurityGroup',
|
|
406
|
+
Properties: {
|
|
407
|
+
GroupDescription: 'Security group for Frigg VPC Endpoints',
|
|
408
|
+
VpcId: { Ref: 'FriggVPC' },
|
|
409
|
+
SecurityGroupIngress: [
|
|
410
|
+
{
|
|
411
|
+
IpProtocol: 'tcp',
|
|
412
|
+
FromPort: 443,
|
|
413
|
+
ToPort: 443,
|
|
414
|
+
SourceSecurityGroupId: { Ref: 'FriggLambdaSecurityGroup' },
|
|
415
|
+
Description: 'HTTPS from Lambda'
|
|
416
|
+
}
|
|
417
|
+
],
|
|
418
|
+
Tags: [
|
|
419
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc-endpoint-sg' }
|
|
420
|
+
]
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return vpcResources;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Compose a complete serverless framework configuration from app definition
|
|
430
|
+
* @param {Object} AppDefinition - Application definition object
|
|
431
|
+
* @param {string} [AppDefinition.name] - Application name
|
|
432
|
+
* @param {string} [AppDefinition.provider='aws'] - Cloud provider
|
|
433
|
+
* @param {Array} AppDefinition.integrations - Array of integration definitions
|
|
434
|
+
* @param {Object} [AppDefinition.vpc] - VPC configuration
|
|
435
|
+
* @param {Object} [AppDefinition.encryption] - KMS encryption configuration
|
|
436
|
+
* @param {Object} [AppDefinition.ssm] - SSM parameter store configuration
|
|
437
|
+
* @param {Object} [AppDefinition.websockets] - WebSocket configuration
|
|
438
|
+
* @param {boolean} [AppDefinition.websockets.enable=false] - Enable WebSocket support for live update streaming
|
|
439
|
+
* @returns {Object} Complete serverless framework configuration
|
|
440
|
+
*/
|
|
105
441
|
const composeServerlessDefinition = (AppDefinition) => {
|
|
106
442
|
const definition = {
|
|
107
443
|
frameworkVersion: '>=3.17.0',
|
|
@@ -180,26 +516,6 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
180
516
|
},
|
|
181
517
|
},
|
|
182
518
|
functions: {
|
|
183
|
-
defaultWebsocket: {
|
|
184
|
-
handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
185
|
-
events: [
|
|
186
|
-
{
|
|
187
|
-
websocket: {
|
|
188
|
-
route: '$connect',
|
|
189
|
-
},
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
websocket: {
|
|
193
|
-
route: '$default',
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
websocket: {
|
|
198
|
-
route: '$disconnect',
|
|
199
|
-
},
|
|
200
|
-
},
|
|
201
|
-
],
|
|
202
|
-
},
|
|
203
519
|
auth: {
|
|
204
520
|
handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
|
|
205
521
|
events: [
|
|
@@ -245,7 +561,7 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
245
561
|
Type: 'AWS::SQS::Queue',
|
|
246
562
|
Properties: {
|
|
247
563
|
QueueName:
|
|
248
|
-
'internal-error-queue-${self:provider.stage}',
|
|
564
|
+
'${self:service}-internal-error-queue-${self:provider.stage}',
|
|
249
565
|
MessageRetentionPeriod: 300,
|
|
250
566
|
},
|
|
251
567
|
},
|
|
@@ -329,6 +645,7 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
329
645
|
},
|
|
330
646
|
};
|
|
331
647
|
|
|
648
|
+
|
|
332
649
|
// KMS Configuration based on App Definition
|
|
333
650
|
if (AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true) {
|
|
334
651
|
// Add KMS IAM permissions
|
|
@@ -347,12 +664,181 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
347
664
|
// Add serverless-kms-grants plugin
|
|
348
665
|
definition.plugins.push('serverless-kms-grants');
|
|
349
666
|
|
|
350
|
-
// Configure KMS grants with default key
|
|
667
|
+
// Configure KMS grants with discovered default key
|
|
351
668
|
definition.custom.kmsGrants = {
|
|
352
|
-
kmsKeyId: '
|
|
669
|
+
kmsKeyId: '${env:AWS_DISCOVERY_KMS_KEY_ID}'
|
|
353
670
|
};
|
|
354
671
|
}
|
|
355
672
|
|
|
673
|
+
// VPC Configuration based on App Definition
|
|
674
|
+
if (AppDefinition.vpc?.enable === true) {
|
|
675
|
+
// Add VPC-related IAM permissions
|
|
676
|
+
definition.provider.iamRoleStatements.push({
|
|
677
|
+
Effect: 'Allow',
|
|
678
|
+
Action: [
|
|
679
|
+
'ec2:CreateNetworkInterface',
|
|
680
|
+
'ec2:DescribeNetworkInterfaces',
|
|
681
|
+
'ec2:DeleteNetworkInterface',
|
|
682
|
+
'ec2:AttachNetworkInterface',
|
|
683
|
+
'ec2:DetachNetworkInterface'
|
|
684
|
+
],
|
|
685
|
+
Resource: '*'
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
// Default approach: Use AWS Discovery to find existing VPC resources
|
|
689
|
+
if (AppDefinition.vpc.createNew === true) {
|
|
690
|
+
// Option 1: Create new VPC infrastructure (explicit opt-in)
|
|
691
|
+
const vpcConfig = {};
|
|
692
|
+
|
|
693
|
+
if (AppDefinition.vpc.securityGroupIds) {
|
|
694
|
+
// User provided custom security groups
|
|
695
|
+
vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds;
|
|
696
|
+
} else {
|
|
697
|
+
// Use auto-created security group
|
|
698
|
+
vpcConfig.securityGroupIds = [{ Ref: 'FriggLambdaSecurityGroup' }];
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (AppDefinition.vpc.subnetIds) {
|
|
702
|
+
// User provided custom subnets
|
|
703
|
+
vpcConfig.subnetIds = AppDefinition.vpc.subnetIds;
|
|
704
|
+
} else {
|
|
705
|
+
// Use auto-created private subnets
|
|
706
|
+
vpcConfig.subnetIds = [
|
|
707
|
+
{ Ref: 'FriggPrivateSubnet1' },
|
|
708
|
+
{ Ref: 'FriggPrivateSubnet2' }
|
|
709
|
+
];
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Set VPC config for Lambda functions
|
|
713
|
+
definition.provider.vpc = vpcConfig;
|
|
714
|
+
|
|
715
|
+
// Add VPC infrastructure resources to CloudFormation
|
|
716
|
+
const vpcResources = createVPCInfrastructure(AppDefinition);
|
|
717
|
+
Object.assign(definition.resources.Resources, vpcResources);
|
|
718
|
+
} else {
|
|
719
|
+
// Option 2: Use AWS Discovery (default behavior)
|
|
720
|
+
// VPC configuration using discovered or explicitly provided resources
|
|
721
|
+
const vpcConfig = {
|
|
722
|
+
securityGroupIds: AppDefinition.vpc.securityGroupIds ||
|
|
723
|
+
['${env:AWS_DISCOVERY_SECURITY_GROUP_ID}'],
|
|
724
|
+
subnetIds: AppDefinition.vpc.subnetIds || [
|
|
725
|
+
'${env:AWS_DISCOVERY_SUBNET_ID_1}',
|
|
726
|
+
'${env:AWS_DISCOVERY_SUBNET_ID_2}'
|
|
727
|
+
]
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
// Set VPC config for Lambda functions
|
|
731
|
+
definition.provider.vpc = vpcConfig;
|
|
732
|
+
|
|
733
|
+
// Add NAT Gateway for Lambda internet access (required for external API calls)
|
|
734
|
+
// Even with existing VPC, Lambdas need guaranteed internet access for integrations
|
|
735
|
+
definition.resources.Resources.FriggNATGatewayEIP = {
|
|
736
|
+
Type: 'AWS::EC2::EIP',
|
|
737
|
+
Properties: {
|
|
738
|
+
Domain: 'vpc',
|
|
739
|
+
Tags: [
|
|
740
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-eip' }
|
|
741
|
+
]
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
definition.resources.Resources.FriggNATGateway = {
|
|
746
|
+
Type: 'AWS::EC2::NatGateway',
|
|
747
|
+
Properties: {
|
|
748
|
+
AllocationId: { 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'] },
|
|
749
|
+
SubnetId: '${env:AWS_DISCOVERY_PUBLIC_SUBNET_ID}', // Discovery finds public subnet
|
|
750
|
+
Tags: [
|
|
751
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-gateway' }
|
|
752
|
+
]
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
// Create route table for Lambda subnets to use NAT Gateway
|
|
757
|
+
definition.resources.Resources.FriggLambdaRouteTable = {
|
|
758
|
+
Type: 'AWS::EC2::RouteTable',
|
|
759
|
+
Properties: {
|
|
760
|
+
VpcId: '${env:AWS_DISCOVERY_VPC_ID}',
|
|
761
|
+
Tags: [
|
|
762
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-rt' }
|
|
763
|
+
]
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
definition.resources.Resources.FriggNATRoute = {
|
|
768
|
+
Type: 'AWS::EC2::Route',
|
|
769
|
+
Properties: {
|
|
770
|
+
RouteTableId: { Ref: 'FriggLambdaRouteTable' },
|
|
771
|
+
DestinationCidrBlock: '0.0.0.0/0',
|
|
772
|
+
NatGatewayId: { Ref: 'FriggNATGateway' }
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
// Associate Lambda subnets with NAT Gateway route table
|
|
777
|
+
definition.resources.Resources.FriggSubnet1RouteAssociation = {
|
|
778
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
779
|
+
Properties: {
|
|
780
|
+
SubnetId: '${env:AWS_DISCOVERY_SUBNET_ID_1}',
|
|
781
|
+
RouteTableId: { Ref: 'FriggLambdaRouteTable' }
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
definition.resources.Resources.FriggSubnet2RouteAssociation = {
|
|
786
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
787
|
+
Properties: {
|
|
788
|
+
SubnetId: '${env:AWS_DISCOVERY_SUBNET_ID_2}',
|
|
789
|
+
RouteTableId: { Ref: 'FriggLambdaRouteTable' }
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
// Add VPC endpoints for AWS service optimization (optional but recommended)
|
|
794
|
+
if (AppDefinition.vpc.enableVPCEndpoints !== false) {
|
|
795
|
+
definition.resources.Resources.VPCEndpointS3 = {
|
|
796
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
797
|
+
Properties: {
|
|
798
|
+
VpcId: '${env:AWS_DISCOVERY_VPC_ID}',
|
|
799
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.s3',
|
|
800
|
+
VpcEndpointType: 'Gateway',
|
|
801
|
+
RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }]
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
definition.resources.Resources.VPCEndpointDynamoDB = {
|
|
806
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
807
|
+
Properties: {
|
|
808
|
+
VpcId: '${env:AWS_DISCOVERY_VPC_ID}',
|
|
809
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.dynamodb',
|
|
810
|
+
VpcEndpointType: 'Gateway',
|
|
811
|
+
RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }]
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// SSM Parameter Store Configuration based on App Definition
|
|
819
|
+
if (AppDefinition.ssm?.enable === true) {
|
|
820
|
+
// Add AWS Parameters and Secrets Lambda Extension layer
|
|
821
|
+
definition.provider.layers = [
|
|
822
|
+
'arn:aws:lambda:${self:provider.region}:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11'
|
|
823
|
+
];
|
|
824
|
+
|
|
825
|
+
// Add SSM IAM permissions
|
|
826
|
+
definition.provider.iamRoleStatements.push({
|
|
827
|
+
Effect: 'Allow',
|
|
828
|
+
Action: [
|
|
829
|
+
'ssm:GetParameter',
|
|
830
|
+
'ssm:GetParameters',
|
|
831
|
+
'ssm:GetParametersByPath'
|
|
832
|
+
],
|
|
833
|
+
Resource: [
|
|
834
|
+
'arn:aws:ssm:${self:provider.region}:*:parameter/${self:service}/${self:provider.stage}/*'
|
|
835
|
+
]
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
// Add environment variable for SSM parameter prefix
|
|
839
|
+
definition.provider.environment.SSM_PARAMETER_PREFIX = '/${self:service}/${self:provider.stage}';
|
|
840
|
+
}
|
|
841
|
+
|
|
356
842
|
// Add integration-specific functions and resources
|
|
357
843
|
for (const integration of AppDefinition.integrations) {
|
|
358
844
|
const integrationName = integration.Definition.name;
|
|
@@ -419,6 +905,51 @@ const composeServerlessDefinition = (AppDefinition) => {
|
|
|
419
905
|
definition.custom[queueReference] = queueName;
|
|
420
906
|
}
|
|
421
907
|
|
|
908
|
+
// Check if AWS discovery environment variables are missing and error
|
|
909
|
+
const needsDiscovery = AppDefinition.vpc?.enable ||
|
|
910
|
+
AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ||
|
|
911
|
+
AppDefinition.ssm?.enable;
|
|
912
|
+
|
|
913
|
+
if (needsDiscovery && !process.env.AWS_DISCOVERY_VPC_ID) {
|
|
914
|
+
console.error('❌ AWS discovery environment variables not found');
|
|
915
|
+
console.error('');
|
|
916
|
+
console.error('🚨 Your AppDefinition requires AWS discovery but variables are missing:');
|
|
917
|
+
if (AppDefinition.vpc?.enable) console.error(' ❌ VPC support (vpc.enable: true)');
|
|
918
|
+
if (AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption) console.error(' ❌ KMS encryption (encryption.useDefaultKMSForFieldLevelEncryption: true)');
|
|
919
|
+
if (AppDefinition.ssm?.enable) console.error(' ❌ SSM parameters (ssm.enable: true)');
|
|
920
|
+
console.error('');
|
|
921
|
+
console.error('💡 Run AWS discovery before building:');
|
|
922
|
+
console.error(' node node_modules/@friggframework/devtools/infrastructure/run-discovery.js');
|
|
923
|
+
console.error('');
|
|
924
|
+
console.error('🔧 Or use the build command which runs discovery automatically:');
|
|
925
|
+
console.error(' npx frigg build');
|
|
926
|
+
throw new Error('AWS discovery required but environment variables not found');
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// Add websocket function if enabled
|
|
930
|
+
if (AppDefinition.websockets?.enable === true) {
|
|
931
|
+
definition.functions.defaultWebsocket = {
|
|
932
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
933
|
+
events: [
|
|
934
|
+
{
|
|
935
|
+
websocket: {
|
|
936
|
+
route: '$connect',
|
|
937
|
+
},
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
websocket: {
|
|
941
|
+
route: '$default',
|
|
942
|
+
},
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
websocket: {
|
|
946
|
+
route: '$disconnect',
|
|
947
|
+
},
|
|
948
|
+
},
|
|
949
|
+
],
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
422
953
|
// Modify handler paths to point to the correct node_modules location
|
|
423
954
|
definition.functions = modifyHandlerPaths(definition.functions);
|
|
424
955
|
|