@friggframework/devtools 2.0.0--canary.428.4fa6f20.0 → 2.0.0--canary.428.08c3f6f.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.
@@ -229,6 +229,18 @@ const createVPCInfrastructure = (AppDefinition) => {
229
229
  Key: 'Name',
230
230
  Value: '${self:service}-${self:provider.stage}-vpc',
231
231
  },
232
+ {
233
+ Key: 'ManagedBy',
234
+ Value: 'Frigg',
235
+ },
236
+ {
237
+ Key: 'Service',
238
+ Value: '${self:service}',
239
+ },
240
+ {
241
+ Key: 'Stage',
242
+ Value: '${self:provider.stage}',
243
+ },
232
244
  ],
233
245
  },
234
246
  },
@@ -242,6 +254,18 @@ const createVPCInfrastructure = (AppDefinition) => {
242
254
  Key: 'Name',
243
255
  Value: '${self:service}-${self:provider.stage}-igw',
244
256
  },
257
+ {
258
+ Key: 'ManagedBy',
259
+ Value: 'Frigg',
260
+ },
261
+ {
262
+ Key: 'Service',
263
+ Value: '${self:service}',
264
+ },
265
+ {
266
+ Key: 'Stage',
267
+ Value: '${self:provider.stage}',
268
+ },
245
269
  ],
246
270
  },
247
271
  },
@@ -268,6 +292,22 @@ const createVPCInfrastructure = (AppDefinition) => {
268
292
  Key: 'Name',
269
293
  Value: '${self:service}-${self:provider.stage}-public-subnet',
270
294
  },
295
+ {
296
+ Key: 'ManagedBy',
297
+ Value: 'Frigg',
298
+ },
299
+ {
300
+ Key: 'Service',
301
+ Value: '${self:service}',
302
+ },
303
+ {
304
+ Key: 'Stage',
305
+ Value: '${self:provider.stage}',
306
+ },
307
+ {
308
+ Key: 'Type',
309
+ Value: 'Public',
310
+ },
271
311
  ],
272
312
  },
273
313
  },
@@ -284,6 +324,22 @@ const createVPCInfrastructure = (AppDefinition) => {
284
324
  Key: 'Name',
285
325
  Value: '${self:service}-${self:provider.stage}-private-subnet-1',
286
326
  },
327
+ {
328
+ Key: 'ManagedBy',
329
+ Value: 'Frigg',
330
+ },
331
+ {
332
+ Key: 'Service',
333
+ Value: '${self:service}',
334
+ },
335
+ {
336
+ Key: 'Stage',
337
+ Value: '${self:provider.stage}',
338
+ },
339
+ {
340
+ Key: 'Type',
341
+ Value: 'Private',
342
+ },
287
343
  ],
288
344
  },
289
345
  },
@@ -300,6 +356,22 @@ const createVPCInfrastructure = (AppDefinition) => {
300
356
  Key: 'Name',
301
357
  Value: '${self:service}-${self:provider.stage}-private-subnet-2',
302
358
  },
359
+ {
360
+ Key: 'ManagedBy',
361
+ Value: 'Frigg',
362
+ },
363
+ {
364
+ Key: 'Service',
365
+ Value: '${self:service}',
366
+ },
367
+ {
368
+ Key: 'Stage',
369
+ Value: '${self:provider.stage}',
370
+ },
371
+ {
372
+ Key: 'Type',
373
+ Value: 'Private',
374
+ },
303
375
  ],
304
376
  },
305
377
  },
@@ -314,6 +386,18 @@ const createVPCInfrastructure = (AppDefinition) => {
314
386
  Key: 'Name',
315
387
  Value: '${self:service}-${self:provider.stage}-nat-eip',
316
388
  },
389
+ {
390
+ Key: 'ManagedBy',
391
+ Value: 'Frigg',
392
+ },
393
+ {
394
+ Key: 'Service',
395
+ Value: '${self:service}',
396
+ },
397
+ {
398
+ Key: 'Stage',
399
+ Value: '${self:provider.stage}',
400
+ },
317
401
  ],
318
402
  },
319
403
  DependsOn: 'FriggVPCGatewayAttachment',
@@ -332,6 +416,18 @@ const createVPCInfrastructure = (AppDefinition) => {
332
416
  Key: 'Name',
333
417
  Value: '${self:service}-${self:provider.stage}-nat-gateway',
334
418
  },
419
+ {
420
+ Key: 'ManagedBy',
421
+ Value: 'Frigg',
422
+ },
423
+ {
424
+ Key: 'Service',
425
+ Value: '${self:service}',
426
+ },
427
+ {
428
+ Key: 'Stage',
429
+ Value: '${self:provider.stage}',
430
+ },
335
431
  ],
336
432
  },
337
433
  },
@@ -346,6 +442,22 @@ const createVPCInfrastructure = (AppDefinition) => {
346
442
  Key: 'Name',
347
443
  Value: '${self:service}-${self:provider.stage}-public-rt',
348
444
  },
445
+ {
446
+ Key: 'ManagedBy',
447
+ Value: 'Frigg',
448
+ },
449
+ {
450
+ Key: 'Service',
451
+ Value: '${self:service}',
452
+ },
453
+ {
454
+ Key: 'Stage',
455
+ Value: '${self:provider.stage}',
456
+ },
457
+ {
458
+ Key: 'Type',
459
+ Value: 'Public',
460
+ },
349
461
  ],
350
462
  },
351
463
  },
@@ -380,6 +492,22 @@ const createVPCInfrastructure = (AppDefinition) => {
380
492
  Key: 'Name',
381
493
  Value: '${self:service}-${self:provider.stage}-private-rt',
382
494
  },
495
+ {
496
+ Key: 'ManagedBy',
497
+ Value: 'Frigg',
498
+ },
499
+ {
500
+ Key: 'Service',
501
+ Value: '${self:service}',
502
+ },
503
+ {
504
+ Key: 'Stage',
505
+ Value: '${self:provider.stage}',
506
+ },
507
+ {
508
+ Key: 'Type',
509
+ Value: 'Private',
510
+ },
383
511
  ],
384
512
  },
385
513
  },
@@ -460,6 +588,18 @@ const createVPCInfrastructure = (AppDefinition) => {
460
588
  Key: 'Name',
461
589
  Value: '${self:service}-${self:provider.stage}-lambda-sg',
462
590
  },
591
+ {
592
+ Key: 'ManagedBy',
593
+ Value: 'Frigg',
594
+ },
595
+ {
596
+ Key: 'Service',
597
+ Value: '${self:service}',
598
+ },
599
+ {
600
+ Key: 'Stage',
601
+ Value: '${self:provider.stage}',
602
+ },
463
603
  ],
464
604
  },
465
605
  },
@@ -548,6 +688,22 @@ const createVPCInfrastructure = (AppDefinition) => {
548
688
  Key: 'Name',
549
689
  Value: '${self:service}-${self:provider.stage}-vpc-endpoint-sg',
550
690
  },
691
+ {
692
+ Key: 'ManagedBy',
693
+ Value: 'Frigg',
694
+ },
695
+ {
696
+ Key: 'Service',
697
+ Value: '${self:service}',
698
+ },
699
+ {
700
+ Key: 'Stage',
701
+ Value: '${self:provider.stage}',
702
+ },
703
+ {
704
+ Key: 'Type',
705
+ Value: 'VPCEndpoint',
706
+ },
551
707
  ],
552
708
  },
553
709
  };
@@ -1007,6 +1163,100 @@ const composeServerlessDefinition = async (AppDefinition) => {
1007
1163
  }
1008
1164
  }
1009
1165
 
1166
+ /**
1167
+ * Heals VPC configuration issues by fixing common misconfigurations
1168
+ * @param {Object} discoveredResources - Resources discovered from AWS
1169
+ * @param {Object} AppDefinition - Application definition with VPC settings
1170
+ * @returns {Object} Healing report with actions taken and recommendations
1171
+ */
1172
+ const healVPCConfiguration = (discoveredResources, AppDefinition) => {
1173
+ const healingReport = {
1174
+ healed: [],
1175
+ warnings: [],
1176
+ errors: [],
1177
+ recommendations: []
1178
+ };
1179
+
1180
+ // Only heal if selfHeal is explicitly enabled
1181
+ if (!AppDefinition.vpc?.selfHeal) {
1182
+ return healingReport;
1183
+ }
1184
+
1185
+ console.log('🔧 Self-healing mode enabled - checking for VPC misconfigurations...');
1186
+
1187
+ // Check NAT Gateway placement
1188
+ if (discoveredResources.natGatewayInPrivateSubnet) {
1189
+ healingReport.warnings.push(
1190
+ `NAT Gateway ${discoveredResources.natGatewayInPrivateSubnet} is in a private subnet`
1191
+ );
1192
+ healingReport.recommendations.push(
1193
+ 'NAT Gateway should be recreated in a public subnet for proper internet connectivity'
1194
+ );
1195
+
1196
+ // Mark that we need to create a new NAT Gateway
1197
+ discoveredResources.needsNewNatGateway = true;
1198
+ healingReport.healed.push('Marked NAT Gateway for recreation in public subnet');
1199
+ }
1200
+
1201
+ // Check if EIP is already associated
1202
+ if (discoveredResources.elasticIpAlreadyAssociated) {
1203
+ healingReport.warnings.push(
1204
+ `Elastic IP ${discoveredResources.existingElasticIp} is already associated`
1205
+ );
1206
+
1207
+ // In self-heal mode, we'll try to reuse or create a new one
1208
+ if (discoveredResources.existingNatGatewayId) {
1209
+ healingReport.healed.push(
1210
+ 'Will reuse existing NAT Gateway instead of creating a new one'
1211
+ );
1212
+ discoveredResources.reuseExistingNatGateway = true;
1213
+ } else {
1214
+ healingReport.healed.push(
1215
+ 'Will allocate a new Elastic IP for NAT Gateway'
1216
+ );
1217
+ discoveredResources.allocateNewElasticIp = true;
1218
+ }
1219
+ }
1220
+
1221
+ // Check route table associations
1222
+ if (discoveredResources.privateSubnetsWithWrongRoutes) {
1223
+ healingReport.warnings.push(
1224
+ `Found ${discoveredResources.privateSubnetsWithWrongRoutes.length} private subnets with incorrect routes`
1225
+ );
1226
+ healingReport.healed.push(
1227
+ 'Route tables will be corrected during deployment'
1228
+ );
1229
+ }
1230
+
1231
+ // Check for orphaned resources
1232
+ if (discoveredResources.orphanedElasticIps?.length > 0) {
1233
+ healingReport.warnings.push(
1234
+ `Found ${discoveredResources.orphanedElasticIps.length} orphaned Elastic IPs`
1235
+ );
1236
+ healingReport.recommendations.push(
1237
+ 'Consider releasing orphaned Elastic IPs to avoid charges'
1238
+ );
1239
+ }
1240
+
1241
+ // Log healing report
1242
+ if (healingReport.healed.length > 0) {
1243
+ console.log('✅ Self-healing actions:');
1244
+ healingReport.healed.forEach(action => console.log(` - ${action}`));
1245
+ }
1246
+
1247
+ if (healingReport.warnings.length > 0) {
1248
+ console.log('⚠️ Issues detected:');
1249
+ healingReport.warnings.forEach(warning => console.log(` - ${warning}`));
1250
+ }
1251
+
1252
+ if (healingReport.recommendations.length > 0) {
1253
+ console.log('💡 Recommendations:');
1254
+ healingReport.recommendations.forEach(rec => console.log(` - ${rec}`));
1255
+ }
1256
+
1257
+ return healingReport;
1258
+ };
1259
+
1010
1260
  // VPC Configuration based on App Definition
1011
1261
  if (AppDefinition.vpc?.enable === true) {
1012
1262
  // Add VPC-related IAM permissions
@@ -1022,107 +1272,383 @@ const composeServerlessDefinition = async (AppDefinition) => {
1022
1272
  Resource: '*',
1023
1273
  });
1024
1274
 
1025
- // Default approach: Use AWS Discovery to find existing VPC resources
1026
- if (AppDefinition.vpc.createNew === true) {
1027
- // Option 1: Create new VPC infrastructure (explicit opt-in)
1028
- const vpcConfig = {};
1275
+ // Run healing if enabled and we have discovered resources
1276
+ if (discoveredResources && Object.keys(discoveredResources).length > 0) {
1277
+ const healingReport = healVPCConfiguration(discoveredResources, AppDefinition);
1029
1278
 
1030
- if (AppDefinition.vpc.securityGroupIds) {
1031
- // User provided custom security groups
1032
- vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds;
1033
- } else {
1034
- // Use auto-created security group
1035
- vpcConfig.securityGroupIds = [
1036
- { Ref: 'FriggLambdaSecurityGroup' },
1037
- ];
1279
+ // If healing failed critically, throw an error unless selfHeal is true
1280
+ if (healingReport.errors.length > 0 && !AppDefinition.vpc?.selfHeal) {
1281
+ throw new Error(`VPC configuration errors detected: ${healingReport.errors.join(', ')}`);
1038
1282
  }
1283
+ }
1039
1284
 
1040
- if (AppDefinition.vpc.subnetIds) {
1041
- // User provided custom subnets
1042
- vpcConfig.subnetIds = AppDefinition.vpc.subnetIds;
1043
- } else {
1044
- // Use auto-created private subnets
1045
- vpcConfig.subnetIds = [
1046
- { Ref: 'FriggPrivateSubnet1' },
1047
- { Ref: 'FriggPrivateSubnet2' },
1048
- ];
1049
- }
1285
+ // STEP 1: Determine VPC (create, discover, or use existing)
1286
+ const vpcManagement = AppDefinition.vpc.management || 'discover';
1287
+ let vpcId = null;
1288
+ let vpcConfig = {
1289
+ securityGroupIds: [],
1290
+ subnetIds: []
1291
+ };
1050
1292
 
1051
- // Set VPC config for Lambda functions
1052
- definition.provider.vpc = vpcConfig;
1293
+ console.log(`VPC Management Mode: ${vpcManagement}`);
1053
1294
 
1054
- // Add VPC infrastructure resources to CloudFormation
1295
+ // First, establish VPC context
1296
+ if (vpcManagement === 'create-new') {
1297
+ // Create new VPC infrastructure
1055
1298
  const vpcResources = createVPCInfrastructure(AppDefinition);
1056
1299
  Object.assign(definition.resources.Resources, vpcResources);
1300
+ vpcId = { Ref: 'FriggVPC' }; // Reference to created VPC
1301
+
1302
+ // Default security group for new VPC
1303
+ vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds || [
1304
+ { Ref: 'FriggLambdaSecurityGroup' }
1305
+ ];
1306
+ } else if (vpcManagement === 'use-existing') {
1307
+ // Use explicitly provided VPC
1308
+ if (!AppDefinition.vpc.vpcId) {
1309
+ throw new Error('VPC management is set to "use-existing" but no vpcId was provided');
1310
+ }
1311
+ vpcId = AppDefinition.vpc.vpcId;
1312
+ // Use provided security groups or try to discover default security group for the VPC
1313
+ vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds ||
1314
+ (discoveredResources.defaultSecurityGroupId ? [discoveredResources.defaultSecurityGroupId] : []);
1315
+ } else {
1316
+ // Discover VPC
1317
+ if (!discoveredResources.defaultVpcId) {
1318
+ throw new Error(
1319
+ 'VPC discovery failed: No VPC found. ' +
1320
+ 'Either set vpc.management to "create-new" or provide vpc.vpcId with "use-existing".'
1321
+ );
1322
+ }
1323
+ vpcId = discoveredResources.defaultVpcId;
1324
+ vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds ||
1325
+ (discoveredResources.defaultSecurityGroupId ? [discoveredResources.defaultSecurityGroupId] : []);
1326
+ }
1327
+
1328
+ // STEP 2: Handle Subnet Management (independent of VPC management)
1329
+ // When creating a new VPC, default to creating subnets unless explicitly specified
1330
+ const defaultSubnetManagement = vpcManagement === 'create-new' ? 'create' : 'discover';
1331
+ const subnetManagement = AppDefinition.vpc.subnets?.management || defaultSubnetManagement;
1332
+ console.log(`Subnet Management Mode: ${subnetManagement}`);
1333
+
1334
+ // Ensure we have a valid VPC ID for subnet operations
1335
+ const effectiveVpcId = vpcId || discoveredResources.defaultVpcId;
1336
+ if (!effectiveVpcId) {
1337
+ throw new Error('Cannot manage subnets without a VPC ID');
1338
+ }
1339
+
1340
+ // Subnet decision tree
1341
+ if (subnetManagement === 'create') {
1342
+ // Create new subnets in the VPC (either new or existing)
1343
+ console.log('Creating new subnets...');
1344
+
1345
+ // Determine VpcId based on VPC management mode
1346
+ const subnetVpcId = vpcManagement === 'create-new' ? { Ref: 'FriggVPC' } : effectiveVpcId;
1347
+
1348
+ // Generate CIDR blocks based on VPC type
1349
+ // For new VPC: use Fn::Cidr to generate from 10.0.0.0/16
1350
+ // For existing VPC: use safer high-range /24 blocks less likely to conflict
1351
+ let subnet1Cidr, subnet2Cidr, publicSubnetCidr;
1352
+
1353
+ if (vpcManagement === 'create-new') {
1354
+ // Use Fn::Cidr to generate 3 /24 subnets from the VPC CIDR
1355
+ // This creates [10.0.0.0/24, 10.0.1.0/24, 10.0.2.0/24]
1356
+ const generatedCidrs = {
1357
+ 'Fn::Cidr': ['10.0.0.0/16', 3, 8] // 3 subnets with /24 (256-8=248 bits)
1358
+ };
1359
+ subnet1Cidr = { 'Fn::Select': [0, generatedCidrs] }; // 10.0.0.0/24
1360
+ subnet2Cidr = { 'Fn::Select': [1, generatedCidrs] }; // 10.0.1.0/24
1361
+ publicSubnetCidr = { 'Fn::Select': [2, generatedCidrs] }; // 10.0.2.0/24
1362
+ } else {
1363
+ // For existing VPCs, use high-range /24 blocks less likely to conflict
1364
+ // These are in the 172.31.x.x range for default VPC or high ranges for custom VPCs
1365
+ subnet1Cidr = '172.31.240.0/24';
1366
+ subnet2Cidr = '172.31.241.0/24';
1367
+ publicSubnetCidr = '172.31.250.0/24';
1368
+ }
1369
+
1370
+ // Create private subnets
1371
+ definition.resources.Resources.FriggPrivateSubnet1 = {
1372
+ Type: 'AWS::EC2::Subnet',
1373
+ Properties: {
1374
+ VpcId: subnetVpcId,
1375
+ CidrBlock: subnet1Cidr,
1376
+ AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
1377
+ Tags: [
1378
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-1' },
1379
+ { Key: 'Type', Value: 'Private' },
1380
+ { Key: 'ManagedBy', Value: 'Frigg' }
1381
+ ]
1382
+ }
1383
+ };
1384
+
1385
+ definition.resources.Resources.FriggPrivateSubnet2 = {
1386
+ Type: 'AWS::EC2::Subnet',
1387
+ Properties: {
1388
+ VpcId: subnetVpcId,
1389
+ CidrBlock: subnet2Cidr,
1390
+ AvailabilityZone: { 'Fn::Select': [1, { 'Fn::GetAZs': '' }] },
1391
+ Tags: [
1392
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-2' },
1393
+ { Key: 'Type', Value: 'Private' },
1394
+ { Key: 'ManagedBy', Value: 'Frigg' }
1395
+ ]
1396
+ }
1397
+ };
1398
+
1399
+ // Create public subnet for NAT
1400
+ definition.resources.Resources.FriggPublicSubnet = {
1401
+ Type: 'AWS::EC2::Subnet',
1402
+ Properties: {
1403
+ VpcId: subnetVpcId,
1404
+ CidrBlock: publicSubnetCidr,
1405
+ MapPublicIpOnLaunch: true,
1406
+ AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
1407
+ Tags: [
1408
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-public' },
1409
+ { Key: 'Type', Value: 'Public' },
1410
+ { Key: 'ManagedBy', Value: 'Frigg' }
1411
+ ]
1412
+ }
1413
+ };
1414
+
1415
+ vpcConfig.subnetIds = [
1416
+ { Ref: 'FriggPrivateSubnet1' },
1417
+ { Ref: 'FriggPrivateSubnet2' }
1418
+ ];
1419
+
1420
+ // IMPORTANT: Create route tables even without NAT Gateway management
1421
+ // Otherwise subnets won't have proper routing
1422
+ if (!AppDefinition.vpc.natGateway || AppDefinition.vpc.natGateway.management === 'discover') {
1423
+ // Need to ensure public subnet has IGW route
1424
+ if (vpcManagement === 'create-new' || !discoveredResources.internetGatewayId) {
1425
+ // Create or reference IGW for public subnet
1426
+ if (!definition.resources.Resources.FriggInternetGateway) {
1427
+ definition.resources.Resources.FriggInternetGateway = {
1428
+ Type: 'AWS::EC2::InternetGateway',
1429
+ Properties: {
1430
+ Tags: [
1431
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-igw' },
1432
+ { Key: 'ManagedBy', Value: 'Frigg' }
1433
+ ]
1434
+ }
1435
+ };
1436
+
1437
+ definition.resources.Resources.FriggIGWAttachment = {
1438
+ Type: 'AWS::EC2::VPCGatewayAttachment',
1439
+ Properties: {
1440
+ VpcId: subnetVpcId,
1441
+ InternetGatewayId: { Ref: 'FriggInternetGateway' }
1442
+ }
1443
+ };
1444
+ }
1445
+ }
1446
+
1447
+ // Create public route table with IGW route
1448
+ definition.resources.Resources.FriggPublicRouteTable = {
1449
+ Type: 'AWS::EC2::RouteTable',
1450
+ Properties: {
1451
+ VpcId: subnetVpcId,
1452
+ Tags: [
1453
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-rt' },
1454
+ { Key: 'ManagedBy', Value: 'Frigg' }
1455
+ ]
1456
+ }
1457
+ };
1458
+
1459
+ definition.resources.Resources.FriggPublicRoute = {
1460
+ Type: 'AWS::EC2::Route',
1461
+ DependsOn: vpcManagement === 'create-new' ? 'FriggIGWAttachment' : undefined,
1462
+ Properties: {
1463
+ RouteTableId: { Ref: 'FriggPublicRouteTable' },
1464
+ DestinationCidrBlock: '0.0.0.0/0',
1465
+ GatewayId: discoveredResources.internetGatewayId || { Ref: 'FriggInternetGateway' }
1466
+ }
1467
+ };
1468
+
1469
+ // Associate public subnet with public route table
1470
+ definition.resources.Resources.FriggPublicSubnetRouteTableAssociation = {
1471
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
1472
+ Properties: {
1473
+ SubnetId: { Ref: 'FriggPublicSubnet' },
1474
+ RouteTableId: { Ref: 'FriggPublicRouteTable' }
1475
+ }
1476
+ };
1477
+
1478
+ // Create private route table for Lambda subnets
1479
+ definition.resources.Resources.FriggLambdaRouteTable = {
1480
+ Type: 'AWS::EC2::RouteTable',
1481
+ Properties: {
1482
+ VpcId: subnetVpcId,
1483
+ Tags: [
1484
+ { Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-rt' },
1485
+ { Key: 'ManagedBy', Value: 'Frigg' }
1486
+ ]
1487
+ }
1488
+ };
1489
+
1490
+ // Associate private subnets with route table
1491
+ definition.resources.Resources.FriggPrivateSubnet1RouteTableAssociation = {
1492
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
1493
+ Properties: {
1494
+ SubnetId: { Ref: 'FriggPrivateSubnet1' },
1495
+ RouteTableId: { Ref: 'FriggLambdaRouteTable' }
1496
+ }
1497
+ };
1498
+
1499
+ definition.resources.Resources.FriggPrivateSubnet2RouteTableAssociation = {
1500
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
1501
+ Properties: {
1502
+ SubnetId: { Ref: 'FriggPrivateSubnet2' },
1503
+ RouteTableId: { Ref: 'FriggLambdaRouteTable' }
1504
+ }
1505
+ };
1506
+ }
1507
+ } else if (subnetManagement === 'use-existing') {
1508
+ // Use explicitly provided subnet IDs
1509
+ if (!AppDefinition.vpc.subnets?.ids || AppDefinition.vpc.subnets.ids.length < 2) {
1510
+ throw new Error(
1511
+ 'Subnet management is "use-existing" but less than 2 subnet IDs provided. ' +
1512
+ 'Provide at least 2 subnet IDs in vpc.subnets.ids.'
1513
+ );
1514
+ }
1515
+ vpcConfig.subnetIds = AppDefinition.vpc.subnets.ids;
1057
1516
  } else {
1058
- // Option 2: Use AWS Discovery (default behavior)
1059
- // VPC configuration using discovered or explicitly provided resources
1060
- const vpcConfig = {
1061
- securityGroupIds:
1062
- AppDefinition.vpc.securityGroupIds ||
1063
- (discoveredResources.defaultSecurityGroupId
1064
- ? [discoveredResources.defaultSecurityGroupId]
1065
- : []),
1066
- subnetIds:
1067
- AppDefinition.vpc.subnetIds ||
1068
- (discoveredResources.privateSubnetId1 &&
1517
+ // Discover mode (default)
1518
+ vpcConfig.subnetIds =
1519
+ AppDefinition.vpc.subnets?.ids?.length > 0
1520
+ ? AppDefinition.vpc.subnets.ids
1521
+ : (discoveredResources.privateSubnetId1 &&
1069
1522
  discoveredResources.privateSubnetId2
1070
1523
  ? [
1071
1524
  discoveredResources.privateSubnetId1,
1072
1525
  discoveredResources.privateSubnetId2,
1073
1526
  ]
1074
- : []),
1075
- };
1527
+ : []);
1528
+
1529
+ if (vpcConfig.subnetIds.length < 2) {
1530
+ if (AppDefinition.vpc.selfHeal) {
1531
+ console.log('No subnets found but self-heal enabled - creating minimal subnet setup');
1532
+ // Fall back to creating subnets
1533
+ subnetManagement = 'create';
1534
+ // Recursion would be complex here, so just set flag
1535
+ discoveredResources.createSubnets = true;
1536
+ } else {
1537
+ throw new Error(
1538
+ 'No subnets discovered and subnets.management is "discover". ' +
1539
+ 'Either enable vpc.selfHeal, set subnets.management to "create", or provide subnet IDs.'
1540
+ );
1541
+ }
1542
+ }
1543
+ }
1076
1544
 
1077
- // Set VPC config for Lambda functions only if we have valid subnet IDs
1078
- if (
1079
- vpcConfig.subnetIds.length >= 2 &&
1080
- vpcConfig.securityGroupIds.length > 0
1081
- ) {
1082
- definition.provider.vpc = vpcConfig;
1545
+ // Set VPC config for Lambda functions only if we have valid subnet IDs
1546
+ if (
1547
+ vpcConfig.subnetIds.length >= 2 &&
1548
+ vpcConfig.securityGroupIds.length > 0
1549
+ ) {
1550
+ definition.provider.vpc = vpcConfig;
1083
1551
 
1084
- // ALWAYS manage NAT Gateway through CloudFormation for self-healing
1085
- // This ensures NAT Gateway is always in the correct subnet with proper configuration
1552
+ // ALWAYS manage NAT Gateway through CloudFormation for self-healing
1553
+ // This ensures NAT Gateway is always in the correct subnet with proper configuration
1086
1554
 
1087
- console.log('AppDefinition.vpc.natGateway', AppDefinition.vpc.natGateway);
1088
- const natGatewayMethod =
1089
- AppDefinition.vpc.natGateway?.method || 'useExisting';
1090
- console.log('natGatewayMethod', natGatewayMethod);
1091
- const needsNewNatGateway =
1092
- natGatewayMethod === 'createAndManage';
1555
+ console.log('AppDefinition.vpc.natGateway', AppDefinition.vpc.natGateway);
1556
+ const natGatewayManagement =
1557
+ AppDefinition.vpc.natGateway?.management || 'discover';
1558
+ console.log('natGatewayManagement', natGatewayManagement);
1559
+ let needsNewNatGateway =
1560
+ natGatewayManagement === 'createAndManage' ||
1561
+ discoveredResources.needsNewNatGateway === true; // Use healing flag
1093
1562
 
1094
- console.log('needsNewNatGateway', needsNewNatGateway);
1563
+ console.log('needsNewNatGateway', needsNewNatGateway);
1095
1564
 
1096
- // Helper function to validate discovered public subnet
1097
- const isValidPublicSubnet = (subnetId, discoveredResources) => {
1098
- // Basic validation - in production, AWSDiscovery should check route tables for IGW routes
1099
- return (
1100
- discoveredResources.publicSubnetHasIgwRoute !== false
1101
- );
1102
- };
1565
+ // Remove unused helper function - validation is done in discovery
1103
1566
 
1104
- if (needsNewNatGateway) {
1105
- // Always create new dedicated resources in create mode to avoid confusion with existing ones
1106
- console.log(
1107
- 'Create mode: Creating dedicated EIP, public subnet, and NAT Gateway...'
1108
- );
1567
+ // Variables to track NAT Gateway and EIP reuse
1568
+ let reuseExistingNatGateway = false;
1569
+ let useExistingEip = false;
1109
1570
 
1110
- // Create EIP (ignore any discovered)
1111
- definition.resources.Resources.FriggNATGatewayEIP = {
1112
- Type: 'AWS::EC2::EIP',
1113
- Properties: {
1114
- Domain: 'vpc',
1115
- Tags: [
1116
- {
1117
- Key: 'Name',
1118
- Value: '${self:service}-${self:provider.stage}-nat-eip',
1119
- },
1120
- ],
1121
- },
1122
- };
1571
+ if (needsNewNatGateway) {
1572
+ // Always create new dedicated resources in create mode to avoid confusion with existing ones
1573
+ console.log(
1574
+ 'Create mode: Creating dedicated EIP, public subnet, and NAT Gateway...'
1575
+ );
1576
+
1577
+ // Check if we can reuse existing NAT Gateway and EIP to avoid conflicts
1578
+
1579
+ // Check if we have a Frigg-managed NAT Gateway that we can reuse
1580
+ if (discoveredResources.existingNatGatewayId &&
1581
+ discoveredResources.existingElasticIpAllocationId) {
1582
+ // We have both NAT Gateway and EIP
1583
+ console.log('Found existing Frigg-managed NAT Gateway and EIP');
1584
+
1585
+ // CRITICAL: Check if NAT Gateway is in correct (public) subnet
1586
+ if (!discoveredResources.natGatewayInPrivateSubnet) {
1587
+ // NAT Gateway is properly configured, reuse it
1588
+ console.log('✅ Existing NAT Gateway is in PUBLIC subnet, will reuse it');
1589
+ reuseExistingNatGateway = true;
1590
+ } else {
1591
+ // NAT Gateway is in PRIVATE subnet - NEVER reuse it
1592
+ console.log('❌ NAT Gateway is in PRIVATE subnet - MUST create new one in PUBLIC subnet');
1593
+
1594
+ if (AppDefinition.vpc.selfHeal) {
1595
+ console.log('Self-heal enabled: Creating new NAT Gateway in PUBLIC subnet');
1596
+ // Force creation of new NAT in public subnet
1597
+ reuseExistingNatGateway = false;
1598
+ // Cannot reuse the EIP since it's associated with wrong NAT
1599
+ useExistingEip = false;
1600
+ // Mark for cleanup recommendations
1601
+ discoveredResources.needsCleanup = true;
1602
+ } else {
1603
+ throw new Error(
1604
+ 'CRITICAL: NAT Gateway is in PRIVATE subnet (will not work!). ' +
1605
+ 'Enable vpc.selfHeal to auto-fix or set natGateway.management to "createAndManage".'
1606
+ );
1607
+ }
1608
+ }
1609
+ } else if (discoveredResources.existingElasticIpAllocationId &&
1610
+ !discoveredResources.existingNatGatewayId) {
1611
+ // We have an EIP but no NAT Gateway - can reuse the EIP
1612
+ console.log('Found orphaned EIP, will reuse it for new NAT Gateway in PUBLIC subnet');
1613
+ useExistingEip = true;
1614
+ }
1615
+
1616
+ // Skip all resource creation if reusing existing NAT Gateway
1617
+ if (reuseExistingNatGateway) {
1618
+ console.log('Reusing existing NAT Gateway - skipping resource creation');
1619
+ // The existing NAT Gateway will be used for routing
1620
+ // No new resources need to be created
1621
+ } else {
1622
+ // Only create EIP if we're not reusing an existing one
1623
+ if (!useExistingEip) {
1624
+ definition.resources.Resources.FriggNATGatewayEIP = {
1625
+ Type: 'AWS::EC2::EIP',
1626
+ Properties: {
1627
+ Domain: 'vpc',
1628
+ Tags: [
1629
+ {
1630
+ Key: 'Name',
1631
+ Value: '${self:service}-${self:provider.stage}-nat-eip',
1632
+ },
1633
+ {
1634
+ Key: 'ManagedBy',
1635
+ Value: 'Frigg',
1636
+ },
1637
+ {
1638
+ Key: 'Service',
1639
+ Value: '${self:service}',
1640
+ },
1641
+ {
1642
+ Key: 'Stage',
1643
+ Value: '${self:provider.stage}',
1644
+ },
1645
+ ],
1646
+ },
1647
+ };
1648
+ }
1123
1649
 
1124
- // Create public subnet (ignore any discovered; ensure it's in a matching AZ)
1125
- if (!discoveredResources.publicSubnetId) {
1650
+ // Create public subnet if needed (for NAT Gateway placement)
1651
+ if (!discoveredResources.publicSubnetId || discoveredResources.createPublicSubnet) {
1126
1652
  console.log(
1127
1653
  'No public subnet found, creating one for NAT Gateway placement...'
1128
1654
  );
@@ -1222,55 +1748,103 @@ const composeServerlessDefinition = async (AppDefinition) => {
1222
1748
  };
1223
1749
  }
1224
1750
 
1225
- // Create NAT Gateway using the new resources
1226
- definition.resources.Resources.FriggNATGateway = {
1227
- Type: 'AWS::EC2::NatGateway',
1228
- Properties: {
1229
- AllocationId: {
1230
- 'Fn::GetAtt': [
1231
- 'FriggNATGatewayEIP',
1232
- 'AllocationId',
1751
+ // Create NAT Gateway only if not reusing existing one
1752
+ definition.resources.Resources.FriggNATGateway = {
1753
+ Type: 'AWS::EC2::NatGateway',
1754
+ Properties: {
1755
+ AllocationId: useExistingEip ?
1756
+ discoveredResources.existingElasticIpAllocationId :
1757
+ {
1758
+ 'Fn::GetAtt': [
1759
+ 'FriggNATGatewayEIP',
1760
+ 'AllocationId',
1761
+ ],
1762
+ },
1763
+ SubnetId: discoveredResources.publicSubnetId || {
1764
+ Ref: 'FriggPublicSubnet',
1765
+ },
1766
+ Tags: [
1767
+ {
1768
+ Key: 'Name',
1769
+ Value: '${self:service}-${self:provider.stage}-nat-gateway',
1770
+ },
1771
+ {
1772
+ Key: 'ManagedBy',
1773
+ Value: 'Frigg',
1774
+ },
1775
+ {
1776
+ Key: 'Service',
1777
+ Value: '${self:service}',
1778
+ },
1779
+ {
1780
+ Key: 'Stage',
1781
+ Value: '${self:provider.stage}',
1782
+ },
1233
1783
  ],
1234
1784
  },
1235
- SubnetId: discoveredResources.publicSubnetId || {
1236
- Ref: 'FriggPublicSubnet',
1237
- },
1238
- Tags: [
1239
- {
1240
- Key: 'Name',
1241
- Value: '${self:service}-${self:provider.stage}-nat-gateway',
1242
- },
1243
- {
1244
- Key: 'ManagedBy',
1245
- Value: 'CloudFormation',
1246
- },
1247
- ],
1248
- },
1249
- };
1250
- } else if (discoveredResources.existingNatGatewayId) {
1251
- console.log('discoveredResources.existingNatGatewayId', discoveredResources.existingNatGatewayId);
1252
- // Reuse mode: Use existing NAT, but validate first
1253
- if (
1254
- discoveredResources.publicSubnetId &&
1255
- isValidPublicSubnet(
1256
- discoveredResources.publicSubnetId,
1257
- discoveredResources
1258
- )
1259
- ) {
1260
- console.log(
1261
- 'Reuse mode: Valid existing NAT found; adding routes...'
1262
- );
1263
- // No new NAT creation; just add routes referencing existingNatGatewayId
1785
+ };
1786
+ }
1787
+ } else if (natGatewayManagement === 'discover' || natGatewayManagement === 'useExisting') {
1788
+ // Discover or use existing NAT Gateway
1789
+ if (natGatewayManagement === 'useExisting' && AppDefinition.vpc.natGateway?.id) {
1790
+ // Use explicitly provided NAT Gateway ID
1791
+ console.log(`Using explicitly provided NAT Gateway: ${AppDefinition.vpc.natGateway.id}`);
1792
+ discoveredResources.existingNatGatewayId = AppDefinition.vpc.natGateway.id;
1793
+ }
1794
+
1795
+ if (discoveredResources.existingNatGatewayId) {
1796
+ console.log('discoveredResources.existingNatGatewayId', discoveredResources.existingNatGatewayId);
1797
+
1798
+ // CRITICAL: Verify NAT Gateway is in PUBLIC subnet
1799
+ if (discoveredResources.natGatewayInPrivateSubnet) {
1800
+ // NAT is in PRIVATE subnet - CANNOT use it
1801
+ console.log('❌ CRITICAL: NAT Gateway is in PRIVATE subnet - Internet connectivity will NOT work!');
1802
+
1803
+ if (AppDefinition.vpc.selfHeal === true) {
1804
+ console.log('Self-heal enabled: Will create new NAT Gateway in PUBLIC subnet');
1805
+ // Force creation of new NAT Gateway in public subnet
1806
+ needsNewNatGateway = true;
1807
+ discoveredResources.existingNatGatewayId = null; // Don't use the misconfigured NAT
1808
+ // Ensure we have a public subnet for the NAT
1809
+ if (!discoveredResources.publicSubnetId) {
1810
+ console.log('No public subnet found - will create one for NAT Gateway');
1811
+ discoveredResources.createPublicSubnet = true;
1812
+ }
1813
+ } else {
1814
+ throw new Error(
1815
+ 'CRITICAL: NAT Gateway is in PRIVATE subnet and will NOT provide internet connectivity! ' +
1816
+ 'Options: 1) Enable vpc.selfHeal to auto-create proper NAT, ' +
1817
+ '2) Set natGateway.management to "createAndManage", or ' +
1818
+ '3) Manually fix the NAT Gateway placement.'
1819
+ );
1820
+ }
1821
+ } else {
1822
+ // NAT is correctly in public subnet
1823
+ console.log('✅ NAT Gateway is correctly placed in PUBLIC subnet');
1824
+ }
1264
1825
  } else {
1265
- throw new Error(
1266
- 'Existing NAT discovered but public subnet is invalid or missing. Set method to "createAndManage" or fix subnet configuration.'
1267
- );
1826
+ // No existing NAT Gateway found
1827
+ if (natGatewayManagement === 'useExisting') {
1828
+ throw new Error(
1829
+ 'NAT Gateway management set to "useExisting" but no NAT Gateway found. ' +
1830
+ 'Either provide natGateway.id or change management to "discover" or "createAndManage".'
1831
+ );
1832
+ } else if (AppDefinition.vpc.selfHeal === true) {
1833
+ // Self-healing enabled, create a new NAT Gateway
1834
+ console.log('No NAT Gateway found but self-healing enabled - creating new NAT Gateway in PUBLIC subnet');
1835
+ needsNewNatGateway = true;
1836
+ // Ensure we have a public subnet for the NAT
1837
+ if (!discoveredResources.publicSubnetId) {
1838
+ console.log('No public subnet found - will create one for NAT Gateway');
1839
+ discoveredResources.createPublicSubnet = true;
1840
+ }
1841
+ } else {
1842
+ throw new Error(
1843
+ 'No existing NAT Gateway found in discovery mode. ' +
1844
+ 'Set natGateway.management to "createAndManage" to create a new NAT Gateway.'
1845
+ );
1846
+ }
1268
1847
  }
1269
- } else {
1270
- // No NAT and not in create mode: Error out to prevent isolated subnets
1271
- throw new Error(
1272
- 'No existing NAT Gateway found and createAndManage not enabled. Update appDefinition.vpc.natGateway.method or ensure discovery finds a valid NAT.'
1273
- );
1274
1848
  }
1275
1849
 
1276
1850
  // Always add route table and routes (referencing the NAT, whether new or existing)
@@ -1289,47 +1863,96 @@ const composeServerlessDefinition = async (AppDefinition) => {
1289
1863
  },
1290
1864
  };
1291
1865
 
1292
- if (needsNewNatGateway) {
1293
- definition.resources.Resources.FriggNATRoute = {
1866
+ // Determine which NAT Gateway ID to use for routing
1867
+ let natGatewayIdForRoute;
1868
+
1869
+ if (reuseExistingNatGateway) {
1870
+ // Use the existing NAT Gateway that we're reusing
1871
+ natGatewayIdForRoute = discoveredResources.existingNatGatewayId;
1872
+ } else if (needsNewNatGateway && !reuseExistingNatGateway) {
1873
+ // Reference the new NAT Gateway being created
1874
+ natGatewayIdForRoute = { Ref: 'FriggNATGateway' };
1875
+ } else if (discoveredResources.existingNatGatewayId) {
1876
+ // Use the existing NAT Gateway ID
1877
+ natGatewayIdForRoute = discoveredResources.existingNatGatewayId;
1878
+ } else if (AppDefinition.vpc.natGateway?.id) {
1879
+ // Use explicitly provided NAT Gateway ID
1880
+ natGatewayIdForRoute = AppDefinition.vpc.natGateway.id;
1881
+ } else if (AppDefinition.vpc.selfHeal === true) {
1882
+ // Self-healing enabled but no NAT Gateway - skip NAT route
1883
+ natGatewayIdForRoute = null;
1884
+ } else {
1885
+ throw new Error(
1886
+ 'Unable to determine NAT Gateway ID for routing. ' +
1887
+ 'Please check your configuration.'
1888
+ );
1889
+ }
1890
+
1891
+ // Only create NAT route if we have a NAT Gateway
1892
+ if (natGatewayIdForRoute) {
1893
+ definition.resources.Resources.FriggNATRoute = {
1294
1894
  Type: 'AWS::EC2::Route',
1295
1895
  Properties: {
1296
1896
  RouteTableId: { Ref: 'FriggLambdaRouteTable' },
1297
1897
  DestinationCidrBlock: '0.0.0.0/0',
1298
- NatGatewayId: { Ref: 'FriggNATGateway' },
1898
+ NatGatewayId: natGatewayIdForRoute,
1299
1899
  },
1300
1900
  };
1301
- } else {
1302
- definition.resources.Resources.FriggNATRoute = {
1303
- Type: 'AWS::EC2::Route',
1901
+ }
1902
+
1903
+ // Associate Lambda subnets with NAT Gateway route table
1904
+ // CRITICAL: This fixes the "NAT Gateway in private subnet" issue by ensuring correct routing
1905
+ if (AppDefinition.vpc.selfHeal === true) {
1906
+ console.log('✅ Self-healing: Ensuring subnets have correct route table associations');
1907
+ // In self-heal mode, we force the associations even if they might conflict
1908
+ // CloudFormation will automatically disassociate from old route table first
1909
+ }
1910
+
1911
+ // Only create associations for discovered subnets (not for Refs)
1912
+ if (typeof vpcConfig.subnetIds[0] === 'string') {
1913
+ definition.resources.Resources.FriggSubnet1RouteAssociation = {
1914
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
1304
1915
  Properties: {
1916
+ SubnetId: vpcConfig.subnetIds[0],
1305
1917
  RouteTableId: { Ref: 'FriggLambdaRouteTable' },
1306
- DestinationCidrBlock: '0.0.0.0/0',
1307
- NatGatewayId:
1308
- discoveredResources.existingNatGatewayId,
1309
1918
  },
1919
+ DependsOn: 'FriggLambdaRouteTable',
1310
1920
  };
1311
1921
  }
1312
1922
 
1313
- // Associate Lambda subnets with NAT Gateway route table
1314
- // Note: This will only work if the subnets aren't already associated with another route table
1315
- // If deployment fails, manually associate the subnets with the correct route table in AWS Console
1316
- definition.resources.Resources.FriggSubnet1RouteAssociation = {
1317
- Type: 'AWS::EC2::SubnetRouteTableAssociation',
1318
- Properties: {
1319
- SubnetId: vpcConfig.subnetIds[0],
1320
- RouteTableId: { Ref: 'FriggLambdaRouteTable' },
1321
- },
1322
- DependsOn: 'FriggLambdaRouteTable',
1323
- };
1923
+ if (typeof vpcConfig.subnetIds[1] === 'string') {
1924
+ definition.resources.Resources.FriggSubnet2RouteAssociation = {
1925
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
1926
+ Properties: {
1927
+ SubnetId: vpcConfig.subnetIds[1],
1928
+ RouteTableId: { Ref: 'FriggLambdaRouteTable' },
1929
+ },
1930
+ DependsOn: 'FriggLambdaRouteTable',
1931
+ };
1932
+ }
1324
1933
 
1325
- definition.resources.Resources.FriggSubnet2RouteAssociation = {
1326
- Type: 'AWS::EC2::SubnetRouteTableAssociation',
1327
- Properties: {
1328
- SubnetId: vpcConfig.subnetIds[1],
1329
- RouteTableId: { Ref: 'FriggLambdaRouteTable' },
1330
- },
1331
- DependsOn: 'FriggLambdaRouteTable',
1332
- };
1934
+ // If subnets are CloudFormation refs (newly created), associate them
1935
+ if (typeof vpcConfig.subnetIds[0] === 'object' && vpcConfig.subnetIds[0].Ref) {
1936
+ definition.resources.Resources.FriggNewSubnet1RouteAssociation = {
1937
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
1938
+ Properties: {
1939
+ SubnetId: vpcConfig.subnetIds[0],
1940
+ RouteTableId: { Ref: 'FriggLambdaRouteTable' },
1941
+ },
1942
+ DependsOn: ['FriggLambdaRouteTable', vpcConfig.subnetIds[0].Ref],
1943
+ };
1944
+ }
1945
+
1946
+ if (typeof vpcConfig.subnetIds[1] === 'object' && vpcConfig.subnetIds[1].Ref) {
1947
+ definition.resources.Resources.FriggNewSubnet2RouteAssociation = {
1948
+ Type: 'AWS::EC2::SubnetRouteTableAssociation',
1949
+ Properties: {
1950
+ SubnetId: vpcConfig.subnetIds[1],
1951
+ RouteTableId: { Ref: 'FriggLambdaRouteTable' },
1952
+ },
1953
+ DependsOn: ['FriggLambdaRouteTable', vpcConfig.subnetIds[1].Ref],
1954
+ };
1955
+ }
1333
1956
 
1334
1957
  // Add VPC endpoints for AWS service optimization (optional but recommended)
1335
1958
  if (AppDefinition.vpc.enableVPCEndpoints !== false) {
@@ -1430,109 +2053,109 @@ const composeServerlessDefinition = async (AppDefinition) => {
1430
2053
  }
1431
2054
  }
1432
2055
 
1433
- // SSM Parameter Store Configuration based on App Definition
1434
- if (AppDefinition.ssm?.enable === true) {
1435
- // Add AWS Parameters and Secrets Lambda Extension layer
1436
- definition.provider.layers = [
1437
- 'arn:aws:lambda:${self:provider.region}:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11',
1438
- ];
2056
+ // SSM Parameter Store Configuration based on App Definition
2057
+ if (AppDefinition.ssm?.enable === true) {
2058
+ // Add AWS Parameters and Secrets Lambda Extension layer
2059
+ definition.provider.layers = [
2060
+ 'arn:aws:lambda:${self:provider.region}:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11',
2061
+ ];
1439
2062
 
1440
- // Add SSM IAM permissions
1441
- definition.provider.iamRoleStatements.push({
1442
- Effect: 'Allow',
1443
- Action: [
1444
- 'ssm:GetParameter',
1445
- 'ssm:GetParameters',
1446
- 'ssm:GetParametersByPath',
1447
- ],
1448
- Resource: [
1449
- 'arn:aws:ssm:${self:provider.region}:*:parameter/${self:service}/${self:provider.stage}/*',
1450
- ],
1451
- });
2063
+ // Add SSM IAM permissions
2064
+ definition.provider.iamRoleStatements.push({
2065
+ Effect: 'Allow',
2066
+ Action: [
2067
+ 'ssm:GetParameter',
2068
+ 'ssm:GetParameters',
2069
+ 'ssm:GetParametersByPath',
2070
+ ],
2071
+ Resource: [
2072
+ 'arn:aws:ssm:${self:provider.region}:*:parameter/${self:service}/${self:provider.stage}/*',
2073
+ ],
2074
+ });
1452
2075
 
1453
- // Add environment variable for SSM parameter prefix
1454
- definition.provider.environment.SSM_PARAMETER_PREFIX =
1455
- '/${self:service}/${self:provider.stage}';
1456
- }
2076
+ // Add environment variable for SSM parameter prefix
2077
+ definition.provider.environment.SSM_PARAMETER_PREFIX =
2078
+ '/${self:service}/${self:provider.stage}';
2079
+ }
1457
2080
 
1458
- // Add integration-specific functions and resources
1459
- if (
1460
- AppDefinition.integrations &&
1461
- Array.isArray(AppDefinition.integrations)
1462
- ) {
1463
- for (const integration of AppDefinition.integrations) {
1464
- if (
1465
- !integration ||
1466
- !integration.Definition ||
1467
- !integration.Definition.name
1468
- ) {
1469
- throw new Error(
1470
- 'Invalid integration: missing Definition or name'
1471
- );
1472
- }
1473
- const integrationName = integration.Definition.name;
2081
+ // Add integration-specific functions and resources
2082
+ if (
2083
+ AppDefinition.integrations &&
2084
+ Array.isArray(AppDefinition.integrations)
2085
+ ) {
2086
+ console.log(`Processing ${AppDefinition.integrations.length} integrations...`);
2087
+ for (const integration of AppDefinition.integrations) {
2088
+ if (
2089
+ !integration ||
2090
+ !integration.Definition ||
2091
+ !integration.Definition.name
2092
+ ) {
2093
+ throw new Error(
2094
+ 'Invalid integration: missing Definition or name'
2095
+ );
2096
+ }
2097
+ const integrationName = integration.Definition.name;
1474
2098
 
1475
- // Add function for the integration
1476
- definition.functions[integrationName] = {
1477
- handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
1478
- events: [
1479
- {
1480
- httpApi: {
1481
- path: `/api/${integrationName}-integration/{proxy+}`,
1482
- method: 'ANY',
1483
- },
2099
+ // Add function for the integration
2100
+ definition.functions[integrationName] = {
2101
+ handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
2102
+ events: [
2103
+ {
2104
+ httpApi: {
2105
+ path: `/api/${integrationName}-integration/{proxy+}`,
2106
+ method: 'ANY',
1484
2107
  },
1485
- ],
1486
- };
2108
+ },
2109
+ ],
2110
+ };
1487
2111
 
1488
- // Add SQS Queue for the integration
1489
- const queueReference = `${integrationName.charAt(0).toUpperCase() +
1490
- integrationName.slice(1)
1491
- }Queue`;
1492
- const queueName = `\${self:service}--\${self:provider.stage}-${queueReference}`;
1493
- definition.resources.Resources[queueReference] = {
1494
- Type: 'AWS::SQS::Queue',
1495
- Properties: {
1496
- QueueName: `\${self:custom.${queueReference}}`,
1497
- MessageRetentionPeriod: 60,
1498
- VisibilityTimeout: 1800, // 30 minutes
1499
- RedrivePolicy: {
1500
- maxReceiveCount: 1,
1501
- deadLetterTargetArn: {
1502
- 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
1503
- },
2112
+ // Add SQS Queue for the integration
2113
+ const queueReference = `${integrationName.charAt(0).toUpperCase() +
2114
+ integrationName.slice(1)
2115
+ }Queue`;
2116
+ const queueName = `\${self:service}--\${self:provider.stage}-${queueReference}`;
2117
+ definition.resources.Resources[queueReference] = {
2118
+ Type: 'AWS::SQS::Queue',
2119
+ Properties: {
2120
+ QueueName: `\${self:custom.${queueReference}}`,
2121
+ MessageRetentionPeriod: 60,
2122
+ VisibilityTimeout: 1800, // 30 minutes
2123
+ RedrivePolicy: {
2124
+ maxReceiveCount: 1,
2125
+ deadLetterTargetArn: {
2126
+ 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
1504
2127
  },
1505
2128
  },
1506
- };
2129
+ },
2130
+ };
1507
2131
 
1508
- // Add Queue Worker for the integration
1509
- const queueWorkerName = `${integrationName}QueueWorker`;
1510
- definition.functions[queueWorkerName] = {
1511
- handler: `node_modules/@friggframework/core/handlers/workers/integration-defined-workers.handlers.${integrationName}.queueWorker`,
1512
- reservedConcurrency: 5,
1513
- events: [
1514
- {
1515
- sqs: {
1516
- arn: {
1517
- 'Fn::GetAtt': [queueReference, 'Arn'],
1518
- },
1519
- batchSize: 1,
2132
+ // Add Queue Worker for the integration
2133
+ const queueWorkerName = `${integrationName}QueueWorker`;
2134
+ definition.functions[queueWorkerName] = {
2135
+ handler: `node_modules/@friggframework/core/handlers/workers/integration-defined-workers.handlers.${integrationName}.queueWorker`,
2136
+ reservedConcurrency: 5,
2137
+ events: [
2138
+ {
2139
+ sqs: {
2140
+ arn: {
2141
+ 'Fn::GetAtt': [queueReference, 'Arn'],
1520
2142
  },
2143
+ batchSize: 1,
1521
2144
  },
1522
- ],
1523
- timeout: 600,
1524
- };
1525
-
1526
- // Add Queue URL for the integration to the ENVironment variables
1527
- definition.provider.environment = {
1528
- ...definition.provider.environment,
1529
- [`${integrationName.toUpperCase()}_QUEUE_URL`]: {
1530
- Ref: queueReference,
1531
2145
  },
1532
- };
2146
+ ],
2147
+ timeout: 600,
2148
+ };
1533
2149
 
1534
- definition.custom[queueReference] = queueName;
1535
- }
2150
+ // Add Queue URL for the integration to the ENVironment variables
2151
+ definition.provider.environment = {
2152
+ ...definition.provider.environment,
2153
+ [`${integrationName.toUpperCase()}_QUEUE_URL`]: {
2154
+ Ref: queueReference,
2155
+ },
2156
+ };
2157
+
2158
+ definition.custom[queueReference] = queueName;
1536
2159
  }
1537
2160
  }
1538
2161