@friggframework/devtools 2.0.0--canary.428.6b04c24.0 → 2.0.0--canary.428.5c4220d.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.
@@ -1,22 +1,45 @@
1
- let EC2Client, DescribeVpcsCommand, DescribeSubnetsCommand, DescribeSecurityGroupsCommand, DescribeRouteTablesCommand, DescribeNatGatewaysCommand, DescribeAddressesCommand, DescribeInternetGatewaysCommand;
1
+ let EC2Client,
2
+ DescribeVpcsCommand,
3
+ DescribeSubnetsCommand,
4
+ DescribeSecurityGroupsCommand,
5
+ DescribeRouteTablesCommand,
6
+ DescribeNatGatewaysCommand,
7
+ DescribeAddressesCommand,
8
+ DescribeInternetGatewaysCommand;
2
9
  let KMSClient, ListKeysCommand, DescribeKeyCommand;
3
10
  let STSClient, GetCallerIdentityCommand;
4
11
 
5
12
  function loadEC2() {
6
13
  if (!EC2Client) {
7
- ({ EC2Client, DescribeVpcsCommand, DescribeSubnetsCommand, DescribeSecurityGroupsCommand, DescribeRouteTablesCommand, DescribeNatGatewaysCommand, DescribeAddressesCommand, DescribeInternetGatewaysCommand } = require('@aws-sdk/client-ec2'));
14
+ ({
15
+ EC2Client,
16
+ DescribeVpcsCommand,
17
+ DescribeSubnetsCommand,
18
+ DescribeSecurityGroupsCommand,
19
+ DescribeRouteTablesCommand,
20
+ DescribeNatGatewaysCommand,
21
+ DescribeAddressesCommand,
22
+ DescribeInternetGatewaysCommand,
23
+ } = require('@aws-sdk/client-ec2'));
8
24
  }
9
25
  }
10
26
 
11
27
  function loadKMS() {
12
28
  if (!KMSClient) {
13
- ({ KMSClient, ListKeysCommand, DescribeKeyCommand } = require('@aws-sdk/client-kms'));
29
+ ({
30
+ KMSClient,
31
+ ListKeysCommand,
32
+ DescribeKeyCommand,
33
+ } = require('@aws-sdk/client-kms'));
14
34
  }
15
35
  }
16
36
 
17
37
  function loadSTS() {
18
38
  if (!STSClient) {
19
- ({ STSClient, GetCallerIdentityCommand } = require('@aws-sdk/client-sts'));
39
+ ({
40
+ STSClient,
41
+ GetCallerIdentityCommand,
42
+ } = require('@aws-sdk/client-sts'));
20
43
  }
21
44
  }
22
45
 
@@ -66,26 +89,26 @@ class AWSDiscovery {
66
89
  Filters: [
67
90
  {
68
91
  Name: 'is-default',
69
- Values: ['true']
70
- }
71
- ]
92
+ Values: ['true'],
93
+ },
94
+ ],
72
95
  });
73
-
96
+
74
97
  const response = await this.ec2Client.send(command);
75
-
98
+
76
99
  if (response.Vpcs && response.Vpcs.length > 0) {
77
100
  return response.Vpcs[0];
78
101
  }
79
-
102
+
80
103
  // If no default VPC, get the first available VPC
81
104
  const allVpcsCommand = new DescribeVpcsCommand({});
82
105
  const allVpcsResponse = await this.ec2Client.send(allVpcsCommand);
83
-
106
+
84
107
  if (allVpcsResponse.Vpcs && allVpcsResponse.Vpcs.length > 0) {
85
108
  console.log('No default VPC found, using first available VPC');
86
109
  return allVpcsResponse.Vpcs[0];
87
110
  }
88
-
111
+
89
112
  throw new Error('No VPC found in the account');
90
113
  } catch (error) {
91
114
  console.error('Error finding default VPC:', error.message);
@@ -106,9 +129,9 @@ class AWSDiscovery {
106
129
  Filters: [
107
130
  {
108
131
  Name: 'vpc-id',
109
- Values: [vpcId]
110
- }
111
- ]
132
+ Values: [vpcId],
133
+ },
134
+ ],
112
135
  });
113
136
 
114
137
  const response = await this.ec2Client.send(command);
@@ -117,7 +140,9 @@ class AWSDiscovery {
117
140
  throw new Error(`No subnets found in VPC ${vpcId}`);
118
141
  }
119
142
 
120
- console.log(`\nšŸ” Analyzing ${response.Subnets.length} subnets in VPC ${vpcId}...`);
143
+ console.log(
144
+ `\nšŸ” Analyzing ${response.Subnets.length} subnets in VPC ${vpcId}...`
145
+ );
121
146
 
122
147
  // Categorize subnets by their actual routing
123
148
  const privateSubnets = [];
@@ -128,10 +153,14 @@ class AWSDiscovery {
128
153
  const isPrivate = await this.isSubnetPrivate(subnet.SubnetId);
129
154
  if (isPrivate) {
130
155
  privateSubnets.push(subnet);
131
- console.log(` šŸ”’ Private subnet: ${subnet.SubnetId} (AZ: ${subnet.AvailabilityZone})`);
156
+ console.log(
157
+ ` šŸ”’ Private subnet: ${subnet.SubnetId} (AZ: ${subnet.AvailabilityZone})`
158
+ );
132
159
  } else {
133
160
  publicSubnets.push(subnet);
134
- console.log(` 🌐 Public subnet: ${subnet.SubnetId} (AZ: ${subnet.AvailabilityZone})`);
161
+ console.log(
162
+ ` 🌐 Public subnet: ${subnet.SubnetId} (AZ: ${subnet.AvailabilityZone})`
163
+ );
135
164
  }
136
165
  }
137
166
 
@@ -141,15 +170,21 @@ class AWSDiscovery {
141
170
 
142
171
  // If we have at least 2 private subnets, use them
143
172
  if (privateSubnets.length >= 2) {
144
- console.log(`āœ… Found ${privateSubnets.length} private subnets for Lambda deployment`);
173
+ console.log(
174
+ `āœ… Found ${privateSubnets.length} private subnets for Lambda deployment`
175
+ );
145
176
  return privateSubnets.slice(0, 2);
146
177
  }
147
178
 
148
179
  // If we have 1 private subnet, we need at least one more
149
180
  if (privateSubnets.length === 1) {
150
- console.warn(`āš ļø Only 1 private subnet found. Need at least 2 for high availability.`);
181
+ console.warn(
182
+ `āš ļø Only 1 private subnet found. Need at least 2 for high availability.`
183
+ );
151
184
  if (publicSubnets.length > 0 && autoConvert) {
152
- console.log(`šŸ”„ Will convert 1 public subnet to private for high availability...`);
185
+ console.log(
186
+ `šŸ”„ Will convert 1 public subnet to private for high availability...`
187
+ );
153
188
  // Note: The actual conversion happens in the serverless template
154
189
  }
155
190
  // Return what we have - mix of private and public if needed
@@ -158,38 +193,62 @@ class AWSDiscovery {
158
193
 
159
194
  // No private subnets found at all - this is a problem!
160
195
  if (privateSubnets.length === 0 && publicSubnets.length > 0) {
161
- console.error(`āŒ CRITICAL: No private subnets found, but ${publicSubnets.length} public subnets exist`);
162
- console.error(`āŒ Lambda functions should NOT be deployed in public subnets!`);
196
+ console.error(
197
+ `āŒ CRITICAL: No private subnets found, but ${publicSubnets.length} public subnets exist`
198
+ );
199
+ console.error(
200
+ `āŒ Lambda functions should NOT be deployed in public subnets!`
201
+ );
163
202
 
164
203
  if (autoConvert && publicSubnets.length >= 3) {
165
- console.log(`\nšŸ”§ AUTO-CONVERSION: Will configure subnets for proper isolation...`);
166
- console.log(` - Keeping ${publicSubnets[0].SubnetId} as public (for NAT Gateway)`);
167
- console.log(` - Converting ${publicSubnets[1].SubnetId} to private (for Lambda)`);
204
+ console.log(
205
+ `\nšŸ”§ AUTO-CONVERSION: Will configure subnets for proper isolation...`
206
+ );
207
+ console.log(
208
+ ` - Keeping ${publicSubnets[0].SubnetId} as public (for NAT Gateway)`
209
+ );
210
+ console.log(
211
+ ` - Converting ${publicSubnets[1].SubnetId} to private (for Lambda)`
212
+ );
168
213
  if (publicSubnets[2]) {
169
- console.log(` - Converting ${publicSubnets[2].SubnetId} to private (for Lambda)`);
214
+ console.log(
215
+ ` - Converting ${publicSubnets[2].SubnetId} to private (for Lambda)`
216
+ );
170
217
  }
171
218
 
172
219
  // Return subnets that SHOULD be private (indexes 1 and 2)
173
220
  // The actual conversion happens in the serverless template
174
221
  return publicSubnets.slice(1, 3);
175
222
  } else if (autoConvert && publicSubnets.length >= 2) {
176
- console.log(`\nšŸ”§ AUTO-CONVERSION: Only ${publicSubnets.length} subnets available`);
177
- console.log(` - Will need to create new subnets or reconfigure existing ones`);
223
+ console.log(
224
+ `\nšŸ”§ AUTO-CONVERSION: Only ${publicSubnets.length} subnets available`
225
+ );
226
+ console.log(
227
+ ` - Will need to create new subnets or reconfigure existing ones`
228
+ );
178
229
  // Return what we have but flag for conversion
179
230
  return publicSubnets.slice(0, 2);
180
231
  } else {
181
232
  console.error(`\nāš ļø CONFIGURATION ERROR:`);
182
- console.error(` Found ${publicSubnets.length} public subnets but no private subnets.`);
183
- console.error(` Lambda functions require private subnets for security.`);
233
+ console.error(
234
+ ` Found ${publicSubnets.length} public subnets but no private subnets.`
235
+ );
236
+ console.error(
237
+ ` Lambda functions require private subnets for security.`
238
+ );
184
239
  console.error(`\n Options:`);
185
- console.error(` 1. Enable selfHeal: true in vpc configuration`);
240
+ console.error(
241
+ ` 1. Enable selfHeal: true in vpc configuration`
242
+ );
186
243
  console.error(` 2. Create private subnets manually`);
187
- console.error(` 3. Set subnets.management: 'create' to create new private subnets`);
244
+ console.error(
245
+ ` 3. Set subnets.management: 'create' to create new private subnets`
246
+ );
188
247
 
189
248
  throw new Error(
190
249
  `No private subnets found in VPC ${vpcId}. ` +
191
- `Found ${publicSubnets.length} public subnets. ` +
192
- `Lambda requires private subnets. Enable selfHeal or create private subnets.`
250
+ `Found ${publicSubnets.length} public subnets. ` +
251
+ `Lambda requires private subnets. Enable selfHeal or create private subnets.`
193
252
  );
194
253
  }
195
254
  }
@@ -221,32 +280,37 @@ class AWSDiscovery {
221
280
  try {
222
281
  // First, get the subnet details to find its VPC
223
282
  const subnetCommand = new DescribeSubnetsCommand({
224
- SubnetIds: [subnetId]
283
+ SubnetIds: [subnetId],
225
284
  });
226
285
  const subnetResponse = await this.ec2Client.send(subnetCommand);
227
-
228
- if (!subnetResponse.Subnets || subnetResponse.Subnets.length === 0) {
286
+
287
+ if (
288
+ !subnetResponse.Subnets ||
289
+ subnetResponse.Subnets.length === 0
290
+ ) {
229
291
  throw new Error(`Subnet ${subnetId} not found`);
230
292
  }
231
-
293
+
232
294
  const subnet = subnetResponse.Subnets[0];
233
295
  const vpcId = subnet.VpcId;
234
-
296
+
235
297
  // Get all route tables for this VPC
236
298
  const routeTablesCommand = new DescribeRouteTablesCommand({
237
299
  Filters: [
238
300
  {
239
301
  Name: 'vpc-id',
240
- Values: [vpcId]
241
- }
242
- ]
302
+ Values: [vpcId],
303
+ },
304
+ ],
243
305
  });
244
-
245
- const routeTablesResponse = await this.ec2Client.send(routeTablesCommand);
246
-
306
+
307
+ const routeTablesResponse = await this.ec2Client.send(
308
+ routeTablesCommand
309
+ );
310
+
247
311
  // Find the route table for this subnet
248
312
  let routeTable = null;
249
-
313
+
250
314
  // First check for explicit association
251
315
  for (const rt of routeTablesResponse.RouteTables || []) {
252
316
  for (const assoc of rt.Associations || []) {
@@ -257,7 +321,7 @@ class AWSDiscovery {
257
321
  }
258
322
  if (routeTable) break;
259
323
  }
260
-
324
+
261
325
  // If no explicit association, use the main route table
262
326
  if (!routeTable) {
263
327
  for (const rt of routeTablesResponse.RouteTables || []) {
@@ -270,12 +334,12 @@ class AWSDiscovery {
270
334
  if (routeTable) break;
271
335
  }
272
336
  }
273
-
337
+
274
338
  if (!routeTable) {
275
339
  console.warn(`No route table found for subnet ${subnetId}`);
276
340
  return true; // Default to private for safety
277
341
  }
278
-
342
+
279
343
  // Check if route table has a route to an Internet Gateway
280
344
  let hasIgwRoute = false;
281
345
  let gatewayId = null;
@@ -290,14 +354,21 @@ class AWSDiscovery {
290
354
 
291
355
  // Enhanced logging for validation
292
356
  if (hasIgwRoute) {
293
- console.log(`āœ… Subnet ${subnetId} is PUBLIC (has route to IGW ${gatewayId})`);
357
+ console.log(
358
+ `āœ… Subnet ${subnetId} is PUBLIC (has route to IGW ${gatewayId})`
359
+ );
294
360
  return false; // It's a public subnet
295
361
  } else {
296
- console.log(`šŸ”’ Subnet ${subnetId} is PRIVATE (no IGW route found)`);
362
+ console.log(
363
+ `šŸ”’ Subnet ${subnetId} is PRIVATE (no IGW route found)`
364
+ );
297
365
  return true; // No IGW route found, it's private
298
366
  }
299
367
  } catch (error) {
300
- console.warn(`Could not determine if subnet ${subnetId} is private:`, error);
368
+ console.warn(
369
+ `Could not determine if subnet ${subnetId} is private:`,
370
+ error
371
+ );
301
372
  return true; // Default to private for safety
302
373
  }
303
374
  }
@@ -315,17 +386,20 @@ class AWSDiscovery {
315
386
  Filters: [
316
387
  {
317
388
  Name: 'vpc-id',
318
- Values: [vpcId]
389
+ Values: [vpcId],
319
390
  },
320
391
  {
321
392
  Name: 'group-name',
322
- Values: ['frigg-lambda-sg']
323
- }
324
- ]
393
+ Values: ['frigg-lambda-sg'],
394
+ },
395
+ ],
325
396
  });
326
-
397
+
327
398
  const friggResponse = await this.ec2Client.send(friggSgCommand);
328
- if (friggResponse.SecurityGroups && friggResponse.SecurityGroups.length > 0) {
399
+ if (
400
+ friggResponse.SecurityGroups &&
401
+ friggResponse.SecurityGroups.length > 0
402
+ ) {
329
403
  return friggResponse.SecurityGroups[0];
330
404
  }
331
405
 
@@ -334,20 +408,23 @@ class AWSDiscovery {
334
408
  Filters: [
335
409
  {
336
410
  Name: 'vpc-id',
337
- Values: [vpcId]
411
+ Values: [vpcId],
338
412
  },
339
413
  {
340
414
  Name: 'group-name',
341
- Values: ['default']
342
- }
343
- ]
415
+ Values: ['default'],
416
+ },
417
+ ],
344
418
  });
345
-
419
+
346
420
  const defaultResponse = await this.ec2Client.send(defaultSgCommand);
347
- if (defaultResponse.SecurityGroups && defaultResponse.SecurityGroups.length > 0) {
421
+ if (
422
+ defaultResponse.SecurityGroups &&
423
+ defaultResponse.SecurityGroups.length > 0
424
+ ) {
348
425
  return defaultResponse.SecurityGroups[0];
349
426
  }
350
-
427
+
351
428
  throw new Error(`No security group found for VPC ${vpcId}`);
352
429
  } catch (error) {
353
430
  console.error('Error finding default security group:', error);
@@ -367,9 +444,9 @@ class AWSDiscovery {
367
444
  Filters: [
368
445
  {
369
446
  Name: 'vpc-id',
370
- Values: [vpcId]
371
- }
372
- ]
447
+ Values: [vpcId],
448
+ },
449
+ ],
373
450
  });
374
451
 
375
452
  const response = await this.ec2Client.send(command);
@@ -391,14 +468,22 @@ class AWSDiscovery {
391
468
 
392
469
  if (publicSubnets.length === 0) {
393
470
  // If no public subnets found, we need to create one or inform the user
394
- console.warn(`WARNING: No public subnets found in VPC ${vpcId}`);
395
- console.warn('A public subnet with Internet Gateway route is required for NAT Gateway placement');
396
- console.warn('Please create a public subnet or use VPC endpoints instead');
471
+ console.warn(
472
+ `WARNING: No public subnets found in VPC ${vpcId}`
473
+ );
474
+ console.warn(
475
+ 'A public subnet with Internet Gateway route is required for NAT Gateway placement'
476
+ );
477
+ console.warn(
478
+ 'Please create a public subnet or use VPC endpoints instead'
479
+ );
397
480
  return null; // Return null instead of throwing to allow graceful handling
398
481
  }
399
482
 
400
483
  // Return first public subnet for NAT Gateway
401
- console.log(`Found ${publicSubnets.length} public subnets, using ${publicSubnets[0].SubnetId} for NAT Gateway`);
484
+ console.log(
485
+ `Found ${publicSubnets.length} public subnets, using ${publicSubnets[0].SubnetId} for NAT Gateway`
486
+ );
402
487
  return publicSubnets[0];
403
488
  } catch (error) {
404
489
  console.error('Error finding public subnets:', error);
@@ -418,13 +503,13 @@ class AWSDiscovery {
418
503
  Filters: [
419
504
  {
420
505
  Name: 'vpc-id',
421
- Values: [vpcId]
422
- }
423
- ]
506
+ Values: [vpcId],
507
+ },
508
+ ],
424
509
  });
425
-
510
+
426
511
  const response = await this.ec2Client.send(command);
427
-
512
+
428
513
  if (!response.RouteTables || response.RouteTables.length === 0) {
429
514
  throw new Error(`No route tables found for VPC ${vpcId}`);
430
515
  }
@@ -462,13 +547,13 @@ class AWSDiscovery {
462
547
  Filter: [
463
548
  {
464
549
  Name: 'vpc-id',
465
- Values: [vpcId]
550
+ Values: [vpcId],
466
551
  },
467
552
  {
468
553
  Name: 'state',
469
- Values: ['available']
470
- }
471
- ]
554
+ Values: ['available'],
555
+ },
556
+ ],
472
557
  });
473
558
 
474
559
  const response = await this.ec2Client.send(command);
@@ -476,14 +561,24 @@ class AWSDiscovery {
476
561
  if (response.NatGateways && response.NatGateways.length > 0) {
477
562
  // Sort NAT Gateways to prioritize Frigg-managed ones
478
563
  const sortedNatGateways = response.NatGateways.sort((a, b) => {
479
- const aIsFrigg = a.Tags && a.Tags.some(tag =>
480
- (tag.Key === 'ManagedBy' && tag.Value === 'Frigg') ||
481
- (tag.Key === 'Name' && tag.Value.includes('frigg'))
482
- );
483
- const bIsFrigg = b.Tags && b.Tags.some(tag =>
484
- (tag.Key === 'ManagedBy' && tag.Value === 'Frigg') ||
485
- (tag.Key === 'Name' && tag.Value.includes('frigg'))
486
- );
564
+ const aIsFrigg =
565
+ a.Tags &&
566
+ a.Tags.some(
567
+ (tag) =>
568
+ (tag.Key === 'ManagedBy' &&
569
+ tag.Value === 'Frigg') ||
570
+ (tag.Key === 'Name' &&
571
+ tag.Value.includes('frigg'))
572
+ );
573
+ const bIsFrigg =
574
+ b.Tags &&
575
+ b.Tags.some(
576
+ (tag) =>
577
+ (tag.Key === 'ManagedBy' &&
578
+ tag.Value === 'Frigg') ||
579
+ (tag.Key === 'Name' &&
580
+ tag.Value.includes('frigg'))
581
+ );
487
582
 
488
583
  if (aIsFrigg && !bIsFrigg) return -1;
489
584
  if (!aIsFrigg && bIsFrigg) return 1;
@@ -496,46 +591,71 @@ class AWSDiscovery {
496
591
  const isPrivate = await this.isSubnetPrivate(subnetId);
497
592
 
498
593
  // Check if it's a Frigg-managed NAT Gateway
499
- const isFriggNat = natGateway.Tags && natGateway.Tags.some(tag =>
500
- (tag.Key === 'ManagedBy' && tag.Value === 'Frigg') ||
501
- (tag.Key === 'Name' && tag.Value.includes('frigg'))
502
- );
594
+ const isFriggNat =
595
+ natGateway.Tags &&
596
+ natGateway.Tags.some(
597
+ (tag) =>
598
+ (tag.Key === 'ManagedBy' &&
599
+ tag.Value === 'Frigg') ||
600
+ (tag.Key === 'Name' &&
601
+ tag.Value.includes('frigg'))
602
+ );
503
603
 
504
604
  if (isPrivate) {
505
605
  // NAT Gateway appears to be in a private subnet
506
606
  // This could be due to route table misconfiguration
507
- console.warn(`WARNING: NAT Gateway ${natGateway.NatGatewayId} is in subnet ${subnetId} which appears to be private`);
607
+ console.warn(
608
+ `WARNING: NAT Gateway ${natGateway.NatGatewayId} is in subnet ${subnetId} which appears to be private`
609
+ );
508
610
 
509
611
  if (isFriggNat) {
510
- console.warn('This is a Frigg-managed NAT Gateway that may have been misconfigured by route table changes');
511
- console.warn('Consider enabling selfHeal: true to fix this automatically');
612
+ console.warn(
613
+ 'This is a Frigg-managed NAT Gateway that may have been misconfigured by route table changes'
614
+ );
615
+ console.warn(
616
+ 'Consider enabling selfHeal: true to fix this automatically'
617
+ );
512
618
  // Return it anyway if it's Frigg-managed - we can fix the routes
513
619
  // Mark that it's in a private subnet
514
620
  natGateway._isInPrivateSubnet = true;
515
621
  return natGateway;
516
622
  } else {
517
- console.warn('NAT Gateways MUST be placed in public subnets with Internet Gateway routes');
518
- console.warn('Skipping this misconfigured NAT Gateway...');
623
+ console.warn(
624
+ 'NAT Gateways MUST be placed in public subnets with Internet Gateway routes'
625
+ );
626
+ console.warn(
627
+ 'Skipping this misconfigured NAT Gateway...'
628
+ );
519
629
  continue; // Skip non-Frigg NAT Gateways in private subnets
520
630
  }
521
631
  }
522
632
 
523
633
  if (isFriggNat) {
524
- console.log(`Found existing Frigg-managed NAT Gateway: ${natGateway.NatGatewayId}`);
634
+ console.log(
635
+ `Found existing Frigg-managed NAT Gateway: ${natGateway.NatGatewayId}`
636
+ );
525
637
  natGateway._isInPrivateSubnet = false;
526
638
  return natGateway;
527
639
  }
528
640
 
529
641
  // Return first valid NAT Gateway that's in a public subnet
530
- console.log(`Found existing NAT Gateway in public subnet: ${natGateway.NatGatewayId}`);
642
+ console.log(
643
+ `Found existing NAT Gateway in public subnet: ${natGateway.NatGatewayId}`
644
+ );
531
645
  natGateway._isInPrivateSubnet = false;
532
646
  return natGateway;
533
647
  }
534
648
 
535
649
  // All non-Frigg NAT Gateways are in private subnets
536
- console.error(`ERROR: Found ${response.NatGateways.length} NAT Gateway(s) but all non-Frigg ones are in private subnets!`);
537
- console.error('These NAT Gateways will not provide internet connectivity without route table fixes');
538
- console.error('Enable selfHeal: true to fix automatically or create a new NAT Gateway');
650
+ console.error(
651
+ `ERROR: Found ${response.NatGateways.length} NAT Gateway(s) but all non-Frigg ones are in private subnets!`
652
+ );
653
+ console.error(
654
+ 'These NAT Gateways will not provide internet connectivity without route table fixes'
655
+ );
656
+ console.error(
657
+ 'Enable selfHeal: true to fix automatically or create a new NAT Gateway'
658
+ );
539
659
  return null; // Return null to trigger creation of new NAT Gateway
540
660
  }
541
661
 
@@ -554,31 +674,42 @@ class AWSDiscovery {
554
674
  try {
555
675
  const command = new DescribeAddressesCommand({});
556
676
  const response = await this.ec2Client.send(command);
557
-
677
+
558
678
  if (response.Addresses && response.Addresses.length > 0) {
559
679
  // Find an unassociated EIP first
560
- const availableEIP = response.Addresses.find(eip =>
561
- !eip.AssociationId && !eip.InstanceId && !eip.NetworkInterfaceId
680
+ const availableEIP = response.Addresses.find(
681
+ (eip) =>
682
+ !eip.AssociationId &&
683
+ !eip.InstanceId &&
684
+ !eip.NetworkInterfaceId
562
685
  );
563
-
686
+
564
687
  if (availableEIP) {
565
- console.log(`Found available Elastic IP: ${availableEIP.AllocationId}`);
688
+ console.log(
689
+ `Found available Elastic IP: ${availableEIP.AllocationId}`
690
+ );
566
691
  return availableEIP;
567
692
  }
568
-
693
+
569
694
  // Check for EIPs tagged for Frigg
570
- const friggEIP = response.Addresses.find(eip =>
571
- eip.Tags && eip.Tags.some(tag =>
572
- tag.Key === 'Name' && tag.Value.includes('frigg')
573
- )
695
+ const friggEIP = response.Addresses.find(
696
+ (eip) =>
697
+ eip.Tags &&
698
+ eip.Tags.some(
699
+ (tag) =>
700
+ tag.Key === 'Name' &&
701
+ tag.Value.includes('frigg')
702
+ )
574
703
  );
575
-
704
+
576
705
  if (friggEIP) {
577
- console.log(`Found Frigg-tagged Elastic IP: ${friggEIP.AllocationId}`);
706
+ console.log(
707
+ `Found Frigg-tagged Elastic IP: ${friggEIP.AllocationId}`
708
+ );
578
709
  return friggEIP;
579
710
  }
580
711
  }
581
-
712
+
582
713
  return null;
583
714
  } catch (error) {
584
715
  console.warn('Error finding available Elastic IP:', error.message);
@@ -591,38 +722,136 @@ class AWSDiscovery {
591
722
  * @returns {Promise<string|null>} KMS key ARN or null if no key found
592
723
  */
593
724
  async findDefaultKmsKey() {
725
+ console.log('KMS Discovery Starting...');
594
726
  try {
727
+ // Log AWS account and region info for verification
728
+ console.log(`[KMS Discovery] Running in region: ${this.region}`);
729
+ try {
730
+ const accountId = await this.getAccountId();
731
+ console.log(`[KMS Discovery] AWS Account ID: ${accountId}`);
732
+ } catch (error) {
733
+ console.warn(
734
+ '[KMS Discovery] Could not retrieve account ID:',
735
+ error.message
736
+ );
737
+ }
738
+
595
739
  const command = new ListKeysCommand({});
596
740
  const response = await this.kmsClient.send(command);
597
-
741
+
598
742
  if (!response.Keys || response.Keys.length === 0) {
599
- console.log('No KMS keys found in account');
743
+ console.log('[KMS Discovery] No KMS keys found in account');
600
744
  return null;
601
745
  }
602
746
 
747
+ console.log(
748
+ `[KMS Discovery] Found ${response.Keys.length} total keys in account`
749
+ );
750
+ let keysExamined = 0;
751
+ let customerManagedKeys = 0;
752
+ let enabledKeys = 0;
753
+ let pendingDeletionKeys = 0;
754
+
603
755
  // Look for customer managed keys first
604
756
  for (const key of response.Keys) {
605
757
  try {
606
- const describeCommand = new DescribeKeyCommand({ KeyId: key.KeyId });
607
- const keyDetails = await this.kmsClient.send(describeCommand);
608
-
609
- if (keyDetails.KeyMetadata &&
610
- keyDetails.KeyMetadata.KeyManager === 'CUSTOMER' &&
611
- keyDetails.KeyMetadata.KeyState === 'Enabled') {
612
- console.log(`Found customer managed KMS key: ${keyDetails.KeyMetadata.Arn}`);
613
- return keyDetails.KeyMetadata.Arn;
758
+ const describeCommand = new DescribeKeyCommand({
759
+ KeyId: key.KeyId,
760
+ });
761
+ const keyDetails = await this.kmsClient.send(
762
+ describeCommand
763
+ );
764
+ keysExamined++;
765
+
766
+ if (keyDetails.KeyMetadata) {
767
+ const metadata = keyDetails.KeyMetadata;
768
+
769
+ // Log detailed key information
770
+ console.log(`[KMS Discovery] Key ${key.KeyId}:`, {
771
+ KeyManager: metadata.KeyManager,
772
+ KeyState: metadata.KeyState,
773
+ Enabled: metadata.Enabled,
774
+ DeletionDate:
775
+ metadata.DeletionDate ||
776
+ 'Not scheduled for deletion',
777
+ Arn: metadata.Arn,
778
+ });
779
+
780
+ if (metadata.KeyManager === 'CUSTOMER') {
781
+ customerManagedKeys++;
782
+
783
+ if (metadata.KeyState === 'Enabled') {
784
+ enabledKeys++;
785
+ } else if (
786
+ metadata.KeyState === 'PendingDeletion'
787
+ ) {
788
+ pendingDeletionKeys++;
789
+ console.warn(
790
+ `[KMS Discovery] Skipping key ${key.KeyId} - State: PendingDeletion, DeletionDate: ${metadata.DeletionDate}`
791
+ );
792
+ }
793
+
794
+ // Explicitly check for enabled state AND absence of deletion
795
+ if (
796
+ metadata.KeyManager === 'CUSTOMER' &&
797
+ metadata.KeyState === 'Enabled' &&
798
+ !metadata.DeletionDate
799
+ ) {
800
+ console.log(
801
+ `[KMS Discovery] Found eligible customer managed KMS key: ${metadata.Arn}`
802
+ );
803
+ return metadata.Arn;
804
+ } else if (
805
+ metadata.KeyManager === 'CUSTOMER' &&
806
+ metadata.KeyState === 'Enabled' &&
807
+ metadata.DeletionDate
808
+ ) {
809
+ // This shouldn't happen according to AWS docs, but log it if it does
810
+ console.error(
811
+ `[KMS Discovery] WARNING: Key ${key.KeyId} has KeyState='Enabled' but DeletionDate is set: ${metadata.DeletionDate}`
812
+ );
813
+ }
814
+ }
614
815
  }
615
816
  } catch (error) {
616
817
  // Continue to next key if we can't describe this one
617
- console.warn(`Could not describe key ${key.KeyId}:`, error.message);
818
+ console.warn(
819
+ `[KMS Discovery] Could not describe key ${key.KeyId}:`,
820
+ error.message
821
+ );
618
822
  continue;
619
823
  }
620
824
  }
621
825
 
622
- console.log('No customer managed KMS keys found');
826
+ // Summary logging
827
+ console.log('[KMS Discovery] Summary:', {
828
+ totalKeys: response.Keys.length,
829
+ keysExamined: keysExamined,
830
+ customerManagedKeys: customerManagedKeys,
831
+ enabledKeys: enabledKeys,
832
+ pendingDeletionKeys: pendingDeletionKeys,
833
+ });
834
+
835
+ if (customerManagedKeys === 0) {
836
+ console.log(
837
+ '[KMS Discovery] No customer managed KMS keys found in account'
838
+ );
839
+ } else if (enabledKeys === 0) {
840
+ console.warn(
841
+ '[KMS Discovery] Found customer managed keys but none are in Enabled state'
842
+ );
843
+ } else {
844
+ console.warn(
845
+ '[KMS Discovery] Found enabled customer managed keys but none met all criteria'
846
+ );
847
+ }
848
+
623
849
  return null;
624
850
  } catch (error) {
625
- console.error('Error finding default KMS key:', error);
851
+ console.error(
852
+ '[KMS Discovery] Error finding default KMS key:',
853
+ error
854
+ );
626
855
  return null;
627
856
  }
628
857
  }
@@ -638,23 +867,26 @@ class AWSDiscovery {
638
867
  natGateways: [],
639
868
  elasticIps: [],
640
869
  subnets: [],
641
- routeTables: []
870
+ routeTables: [],
642
871
  };
643
872
 
644
873
  // Find NAT Gateways with Frigg tags
645
874
  const natCommand = new DescribeNatGatewaysCommand({
646
875
  Filter: [
647
876
  { Name: 'vpc-id', Values: [vpcId] },
648
- { Name: 'state', Values: ['available'] }
649
- ]
877
+ { Name: 'state', Values: ['available'] },
878
+ ],
650
879
  });
651
880
  const natResponse = await this.ec2Client.send(natCommand);
652
881
 
653
882
  if (natResponse.NatGateways) {
654
- resources.natGateways = natResponse.NatGateways.filter(nat =>
655
- nat.Tags && nat.Tags.some(tag =>
656
- tag.Key === 'ManagedBy' && tag.Value === 'Frigg'
657
- )
883
+ resources.natGateways = natResponse.NatGateways.filter(
884
+ (nat) =>
885
+ nat.Tags &&
886
+ nat.Tags.some(
887
+ (tag) =>
888
+ tag.Key === 'ManagedBy' && tag.Value === 'Frigg'
889
+ )
658
890
  );
659
891
  }
660
892
 
@@ -663,26 +895,30 @@ class AWSDiscovery {
663
895
  const eipResponse = await this.ec2Client.send(eipCommand);
664
896
 
665
897
  if (eipResponse.Addresses) {
666
- resources.elasticIps = eipResponse.Addresses.filter(eip =>
667
- eip.Tags && eip.Tags.some(tag =>
668
- tag.Key === 'ManagedBy' && tag.Value === 'Frigg'
669
- )
898
+ resources.elasticIps = eipResponse.Addresses.filter(
899
+ (eip) =>
900
+ eip.Tags &&
901
+ eip.Tags.some(
902
+ (tag) =>
903
+ tag.Key === 'ManagedBy' && tag.Value === 'Frigg'
904
+ )
670
905
  );
671
906
  }
672
907
 
673
908
  // Find Route Tables with Frigg tags
674
909
  const rtCommand = new DescribeRouteTablesCommand({
675
- Filters: [
676
- { Name: 'vpc-id', Values: [vpcId] }
677
- ]
910
+ Filters: [{ Name: 'vpc-id', Values: [vpcId] }],
678
911
  });
679
912
  const rtResponse = await this.ec2Client.send(rtCommand);
680
913
 
681
914
  if (rtResponse.RouteTables) {
682
- resources.routeTables = rtResponse.RouteTables.filter(rt =>
683
- rt.Tags && rt.Tags.some(tag =>
684
- tag.Key === 'ManagedBy' && tag.Value === 'Frigg'
685
- )
915
+ resources.routeTables = rtResponse.RouteTables.filter(
916
+ (rt) =>
917
+ rt.Tags &&
918
+ rt.Tags.some(
919
+ (tag) =>
920
+ tag.Key === 'ManagedBy' && tag.Value === 'Frigg'
921
+ )
686
922
  );
687
923
  }
688
924
 
@@ -693,7 +929,7 @@ class AWSDiscovery {
693
929
  natGateways: [],
694
930
  elasticIps: [],
695
931
  subnets: [],
696
- routeTables: []
932
+ routeTables: [],
697
933
  };
698
934
  }
699
935
  }
@@ -709,15 +945,15 @@ class AWSDiscovery {
709
945
  natGatewaysInPrivateSubnets: [],
710
946
  orphanedElasticIps: [],
711
947
  misconfiguredRouteTables: [],
712
- privateSubnetsWithoutNatRoute: []
948
+ privateSubnetsWithoutNatRoute: [],
713
949
  };
714
950
 
715
951
  // Find NAT Gateways in private subnets
716
952
  const natCommand = new DescribeNatGatewaysCommand({
717
953
  Filter: [
718
954
  { Name: 'vpc-id', Values: [vpcId] },
719
- { Name: 'state', Values: ['available'] }
720
- ]
955
+ { Name: 'state', Values: ['available'] },
956
+ ],
721
957
  });
722
958
  const natResponse = await this.ec2Client.send(natCommand);
723
959
 
@@ -728,7 +964,7 @@ class AWSDiscovery {
728
964
  misconfigurations.natGatewaysInPrivateSubnets.push({
729
965
  natGatewayId: nat.NatGatewayId,
730
966
  subnetId: nat.SubnetId,
731
- tags: nat.Tags
967
+ tags: nat.Tags,
732
968
  });
733
969
  }
734
970
  }
@@ -740,16 +976,24 @@ class AWSDiscovery {
740
976
 
741
977
  if (eipResponse.Addresses) {
742
978
  for (const eip of eipResponse.Addresses) {
743
- if (!eip.InstanceId && !eip.NetworkInterfaceId && !eip.AssociationId) {
979
+ if (
980
+ !eip.InstanceId &&
981
+ !eip.NetworkInterfaceId &&
982
+ !eip.AssociationId
983
+ ) {
744
984
  // Check if it's Frigg-managed
745
- const isFriggManaged = eip.Tags && eip.Tags.some(tag =>
746
- tag.Key === 'ManagedBy' && tag.Value === 'Frigg'
747
- );
985
+ const isFriggManaged =
986
+ eip.Tags &&
987
+ eip.Tags.some(
988
+ (tag) =>
989
+ tag.Key === 'ManagedBy' &&
990
+ tag.Value === 'Frigg'
991
+ );
748
992
  if (isFriggManaged) {
749
993
  misconfigurations.orphanedElasticIps.push({
750
994
  allocationId: eip.AllocationId,
751
995
  publicIp: eip.PublicIp,
752
- tags: eip.Tags
996
+ tags: eip.Tags,
753
997
  });
754
998
  }
755
999
  }
@@ -765,15 +1009,20 @@ class AWSDiscovery {
765
1009
 
766
1010
  // Find route table for this subnet
767
1011
  for (const rt of routeTables) {
768
- const isAssociated = rt.Associations && rt.Associations.some(
769
- assoc => assoc.SubnetId === subnet.SubnetId
770
- );
1012
+ const isAssociated =
1013
+ rt.Associations &&
1014
+ rt.Associations.some(
1015
+ (assoc) => assoc.SubnetId === subnet.SubnetId
1016
+ );
771
1017
 
772
1018
  if (isAssociated) {
773
- hasNatRoute = rt.Routes && rt.Routes.some(
774
- route => route.NatGatewayId &&
1019
+ hasNatRoute =
1020
+ rt.Routes &&
1021
+ rt.Routes.some(
1022
+ (route) =>
1023
+ route.NatGatewayId &&
775
1024
  route.DestinationCidrBlock === '0.0.0.0/0'
776
- );
1025
+ );
777
1026
  break;
778
1027
  }
779
1028
  }
@@ -781,7 +1030,7 @@ class AWSDiscovery {
781
1030
  if (!hasNatRoute) {
782
1031
  misconfigurations.privateSubnetsWithoutNatRoute.push({
783
1032
  subnetId: subnet.SubnetId,
784
- availabilityZone: subnet.AvailabilityZone
1033
+ availabilityZone: subnet.AvailabilityZone,
785
1034
  });
786
1035
  }
787
1036
  }
@@ -793,7 +1042,7 @@ class AWSDiscovery {
793
1042
  natGatewaysInPrivateSubnets: [],
794
1043
  orphanedElasticIps: [],
795
1044
  misconfiguredRouteTables: [],
796
- privateSubnetsWithoutNatRoute: []
1045
+ privateSubnetsWithoutNatRoute: [],
797
1046
  };
798
1047
  }
799
1048
  }
@@ -810,8 +1059,12 @@ class AWSDiscovery {
810
1059
  recommendations.push({
811
1060
  severity: 'critical',
812
1061
  issue: 'NAT Gateway in private subnet',
813
- recommendation: 'Recreate NAT Gateway in public subnet or fix route tables',
814
- affectedResources: misconfigurations.natGatewaysInPrivateSubnets.map(n => n.natGatewayId)
1062
+ recommendation:
1063
+ 'Recreate NAT Gateway in public subnet or fix route tables',
1064
+ affectedResources:
1065
+ misconfigurations.natGatewaysInPrivateSubnets.map(
1066
+ (n) => n.natGatewayId
1067
+ ),
815
1068
  });
816
1069
  }
817
1070
 
@@ -820,7 +1073,9 @@ class AWSDiscovery {
820
1073
  severity: 'warning',
821
1074
  issue: 'Orphaned Elastic IPs',
822
1075
  recommendation: 'Release unused Elastic IPs to avoid charges',
823
- affectedResources: misconfigurations.orphanedElasticIps.map(e => e.allocationId)
1076
+ affectedResources: misconfigurations.orphanedElasticIps.map(
1077
+ (e) => e.allocationId
1078
+ ),
824
1079
  });
825
1080
  }
826
1081
 
@@ -828,8 +1083,12 @@ class AWSDiscovery {
828
1083
  recommendations.push({
829
1084
  severity: 'critical',
830
1085
  issue: 'Private subnets without NAT route',
831
- recommendation: 'Add NAT Gateway route to private subnet route tables',
832
- affectedResources: misconfigurations.privateSubnetsWithoutNatRoute.map(s => s.subnetId)
1086
+ recommendation:
1087
+ 'Add NAT Gateway route to private subnet route tables',
1088
+ affectedResources:
1089
+ misconfigurations.privateSubnetsWithoutNatRoute.map(
1090
+ (s) => s.subnetId
1091
+ ),
833
1092
  });
834
1093
  }
835
1094
 
@@ -856,7 +1115,9 @@ class AWSDiscovery {
856
1115
  */
857
1116
  async discoverResources(options = {}) {
858
1117
  try {
859
- console.log('\nšŸš€ Discovering AWS resources for Frigg deployment...');
1118
+ console.log(
1119
+ '\nšŸš€ Discovering AWS resources for Frigg deployment...'
1120
+ );
860
1121
  console.log('═'.repeat(60));
861
1122
 
862
1123
  const vpc = await this.findDefaultVpc();
@@ -865,17 +1126,30 @@ class AWSDiscovery {
865
1126
  // Enable auto-convert if selfHeal is enabled
866
1127
  const autoConvert = options.selfHeal || false;
867
1128
 
868
- const privateSubnets = await this.findPrivateSubnets(vpc.VpcId, autoConvert);
869
- console.log(`\nāœ… Selected subnets for Lambda: ${privateSubnets.map(s => s.SubnetId).join(', ')}`);
1129
+ const privateSubnets = await this.findPrivateSubnets(
1130
+ vpc.VpcId,
1131
+ autoConvert
1132
+ );
1133
+ console.log(
1134
+ `\nāœ… Selected subnets for Lambda: ${privateSubnets
1135
+ .map((s) => s.SubnetId)
1136
+ .join(', ')}`
1137
+ );
870
1138
 
871
1139
  const publicSubnet = await this.findPublicSubnets(vpc.VpcId);
872
1140
  if (publicSubnet) {
873
- console.log(`\nāœ… Found public subnet for NAT Gateway: ${publicSubnet.SubnetId}`);
1141
+ console.log(
1142
+ `\nāœ… Found public subnet for NAT Gateway: ${publicSubnet.SubnetId}`
1143
+ );
874
1144
  } else {
875
- console.log(`\nāš ļø No public subnet found - NAT Gateway creation may fail`);
1145
+ console.log(
1146
+ `\nāš ļø No public subnet found - NAT Gateway creation may fail`
1147
+ );
876
1148
  }
877
1149
 
878
- const securityGroup = await this.findDefaultSecurityGroup(vpc.VpcId);
1150
+ const securityGroup = await this.findDefaultSecurityGroup(
1151
+ vpc.VpcId
1152
+ );
879
1153
  console.log(`\nāœ… Found security group: ${securityGroup.GroupId}`);
880
1154
 
881
1155
  const routeTable = await this.findPrivateRouteTable(vpc.VpcId);
@@ -887,9 +1161,11 @@ class AWSDiscovery {
887
1161
  } else {
888
1162
  console.log('ā„¹ļø No KMS key found');
889
1163
  }
890
-
1164
+
891
1165
  // Try to find existing NAT Gateway
892
- const existingNatGateway = await this.findExistingNatGateway(vpc.VpcId);
1166
+ const existingNatGateway = await this.findExistingNatGateway(
1167
+ vpc.VpcId
1168
+ );
893
1169
  let natGatewayId = null;
894
1170
  let elasticIpAllocationId = null;
895
1171
  let natGatewayInPrivateSubnet = false;
@@ -897,11 +1173,16 @@ class AWSDiscovery {
897
1173
  if (existingNatGateway) {
898
1174
  natGatewayId = existingNatGateway.NatGatewayId;
899
1175
  // Check if NAT Gateway is in a private subnet (from our detection)
900
- natGatewayInPrivateSubnet = existingNatGateway._isInPrivateSubnet || false;
1176
+ natGatewayInPrivateSubnet =
1177
+ existingNatGateway._isInPrivateSubnet || false;
901
1178
 
902
1179
  // Get the EIP allocation ID from the NAT Gateway
903
- if (existingNatGateway.NatGatewayAddresses && existingNatGateway.NatGatewayAddresses.length > 0) {
904
- elasticIpAllocationId = existingNatGateway.NatGatewayAddresses[0].AllocationId;
1180
+ if (
1181
+ existingNatGateway.NatGatewayAddresses &&
1182
+ existingNatGateway.NatGatewayAddresses.length > 0
1183
+ ) {
1184
+ elasticIpAllocationId =
1185
+ existingNatGateway.NatGatewayAddresses[0].AllocationId;
905
1186
  }
906
1187
  } else {
907
1188
  // If no NAT Gateway exists, check for available EIP
@@ -912,36 +1193,58 @@ class AWSDiscovery {
912
1193
  }
913
1194
 
914
1195
  // Check if the "private" subnets are actually public
915
- const subnet1IsActuallyPrivate = privateSubnets[0] ?
916
- await this.isSubnetPrivate(privateSubnets[0].SubnetId) : false;
917
- const subnet2IsActuallyPrivate = privateSubnets[1] ?
918
- await this.isSubnetPrivate(privateSubnets[1].SubnetId) :
919
- subnet1IsActuallyPrivate;
1196
+ const subnet1IsActuallyPrivate = privateSubnets[0]
1197
+ ? await this.isSubnetPrivate(privateSubnets[0].SubnetId)
1198
+ : false;
1199
+ const subnet2IsActuallyPrivate = privateSubnets[1]
1200
+ ? await this.isSubnetPrivate(privateSubnets[1].SubnetId)
1201
+ : subnet1IsActuallyPrivate;
920
1202
 
921
1203
  const subnetStatus = {
922
- requiresConversion: !subnet1IsActuallyPrivate || !subnet2IsActuallyPrivate,
1204
+ requiresConversion:
1205
+ !subnet1IsActuallyPrivate || !subnet2IsActuallyPrivate,
923
1206
  subnet1NeedsConversion: !subnet1IsActuallyPrivate,
924
- subnet2NeedsConversion: !subnet2IsActuallyPrivate
1207
+ subnet2NeedsConversion: !subnet2IsActuallyPrivate,
925
1208
  };
926
1209
 
927
1210
  if (subnetStatus.requiresConversion) {
928
1211
  console.log(`\nāš ļø SUBNET CONFIGURATION WARNING:`);
929
1212
  if (subnetStatus.subnet1NeedsConversion && privateSubnets[0]) {
930
- console.log(` - Subnet ${privateSubnets[0].SubnetId} is currently PUBLIC but will be used for Lambda`);
1213
+ console.log(
1214
+ ` - Subnet ${privateSubnets[0].SubnetId} is currently PUBLIC but will be used for Lambda`
1215
+ );
931
1216
  }
932
1217
  if (subnetStatus.subnet2NeedsConversion && privateSubnets[1]) {
933
- console.log(` - Subnet ${privateSubnets[1].SubnetId} is currently PUBLIC but will be used for Lambda`);
1218
+ console.log(
1219
+ ` - Subnet ${privateSubnets[1].SubnetId} is currently PUBLIC but will be used for Lambda`
1220
+ );
934
1221
  }
935
- console.log(` šŸ’” Enable selfHeal: true to automatically fix this`);
1222
+ console.log(
1223
+ ` šŸ’” Enable selfHeal: true to automatically fix this`
1224
+ );
936
1225
  }
937
1226
 
938
1227
  console.log(`\n${'═'.repeat(60)}`);
939
1228
  console.log('šŸ“‹ Discovery Summary:');
940
1229
  console.log(` VPC: ${vpc.VpcId}`);
941
- console.log(` Lambda Subnets: ${privateSubnets.map(s => s.SubnetId).join(', ')}`);
942
- console.log(` NAT Subnet: ${publicSubnet?.SubnetId || 'None (needs creation)'}`);
943
- console.log(` NAT Gateway: ${natGatewayId || 'None (will be created)'}`);
944
- console.log(` Elastic IP: ${elasticIpAllocationId || 'None (will be allocated)'}`);
1230
+ console.log(
1231
+ ` Lambda Subnets: ${privateSubnets
1232
+ .map((s) => s.SubnetId)
1233
+ .join(', ')}`
1234
+ );
1235
+ console.log(
1236
+ ` NAT Subnet: ${
1237
+ publicSubnet?.SubnetId || 'None (needs creation)'
1238
+ }`
1239
+ );
1240
+ console.log(
1241
+ ` NAT Gateway: ${natGatewayId || 'None (will be created)'}`
1242
+ );
1243
+ console.log(
1244
+ ` Elastic IP: ${
1245
+ elasticIpAllocationId || 'None (will be allocated)'
1246
+ }`
1247
+ );
945
1248
  if (subnetStatus.requiresConversion) {
946
1249
  console.log(` āš ļø Subnet Conversion Required: Yes`);
947
1250
  }
@@ -951,7 +1254,8 @@ class AWSDiscovery {
951
1254
  defaultVpcId: vpc.VpcId,
952
1255
  defaultSecurityGroupId: securityGroup.GroupId,
953
1256
  privateSubnetId1: privateSubnets[0]?.SubnetId,
954
- privateSubnetId2: privateSubnets[1]?.SubnetId || privateSubnets[0]?.SubnetId,
1257
+ privateSubnetId2:
1258
+ privateSubnets[1]?.SubnetId || privateSubnets[0]?.SubnetId,
955
1259
  publicSubnetId: publicSubnet?.SubnetId || null, // May be null if no public subnet exists
956
1260
  privateRouteTableId: routeTable.RouteTableId,
957
1261
  defaultKmsKeyId: kmsKeyArn,
@@ -961,14 +1265,20 @@ class AWSDiscovery {
961
1265
  subnetConversionRequired: subnetStatus.requiresConversion,
962
1266
  privateSubnetsWithWrongRoutes: (() => {
963
1267
  const wrongRoutes = [];
964
- if (subnetStatus.subnet1NeedsConversion && privateSubnets[0]) {
1268
+ if (
1269
+ subnetStatus.subnet1NeedsConversion &&
1270
+ privateSubnets[0]
1271
+ ) {
965
1272
  wrongRoutes.push(privateSubnets[0].SubnetId);
966
1273
  }
967
- if (subnetStatus.subnet2NeedsConversion && privateSubnets[1]) {
1274
+ if (
1275
+ subnetStatus.subnet2NeedsConversion &&
1276
+ privateSubnets[1]
1277
+ ) {
968
1278
  wrongRoutes.push(privateSubnets[1].SubnetId);
969
1279
  }
970
1280
  return wrongRoutes;
971
- })()
1281
+ })(),
972
1282
  };
973
1283
  } catch (error) {
974
1284
  console.error('Error discovering AWS resources:', error);
@@ -987,19 +1297,24 @@ class AWSDiscovery {
987
1297
  Filters: [
988
1298
  {
989
1299
  Name: 'attachment.vpc-id',
990
- Values: [vpcId]
1300
+ Values: [vpcId],
991
1301
  },
992
1302
  {
993
1303
  Name: 'attachment.state',
994
- Values: ['available']
995
- }
996
- ]
1304
+ Values: ['available'],
1305
+ },
1306
+ ],
997
1307
  });
998
1308
 
999
1309
  const response = await this.ec2Client.send(command);
1000
1310
 
1001
- if (response.InternetGateways && response.InternetGateways.length > 0) {
1002
- console.log(`Found existing Internet Gateway: ${response.InternetGateways[0].InternetGatewayId}`);
1311
+ if (
1312
+ response.InternetGateways &&
1313
+ response.InternetGateways.length > 0
1314
+ ) {
1315
+ console.log(
1316
+ `Found existing Internet Gateway: ${response.InternetGateways[0].InternetGatewayId}`
1317
+ );
1003
1318
  return response.InternetGateways[0];
1004
1319
  }
1005
1320
 
@@ -1023,28 +1338,28 @@ class AWSDiscovery {
1023
1338
  elasticIps: [],
1024
1339
  routeTables: [],
1025
1340
  subnets: [],
1026
- securityGroups: []
1341
+ securityGroups: [],
1027
1342
  };
1028
1343
 
1029
1344
  // Common filter for Frigg-managed resources
1030
1345
  const friggFilters = [
1031
1346
  {
1032
1347
  Name: 'tag:ManagedBy',
1033
- Values: ['Frigg']
1034
- }
1348
+ Values: ['Frigg'],
1349
+ },
1035
1350
  ];
1036
1351
 
1037
1352
  if (serviceName) {
1038
1353
  friggFilters.push({
1039
1354
  Name: 'tag:Service',
1040
- Values: [serviceName]
1355
+ Values: [serviceName],
1041
1356
  });
1042
1357
  }
1043
1358
 
1044
1359
  if (stage) {
1045
1360
  friggFilters.push({
1046
1361
  Name: 'tag:Stage',
1047
- Values: [stage]
1362
+ Values: [stage],
1048
1363
  });
1049
1364
  }
1050
1365
 
@@ -1055,9 +1370,9 @@ class AWSDiscovery {
1055
1370
  ...friggFilters,
1056
1371
  {
1057
1372
  Name: 'state',
1058
- Values: ['available']
1059
- }
1060
- ]
1373
+ Values: ['available'],
1374
+ },
1375
+ ],
1061
1376
  });
1062
1377
  const natResponse = await this.ec2Client.send(natCommand);
1063
1378
  results.natGateways = natResponse.NatGateways || [];
@@ -1068,7 +1383,7 @@ class AWSDiscovery {
1068
1383
  // Find Elastic IPs
1069
1384
  try {
1070
1385
  const eipCommand = new DescribeAddressesCommand({
1071
- Filters: friggFilters
1386
+ Filters: friggFilters,
1072
1387
  });
1073
1388
  const eipResponse = await this.ec2Client.send(eipCommand);
1074
1389
  results.elasticIps = eipResponse.Addresses || [];
@@ -1079,7 +1394,7 @@ class AWSDiscovery {
1079
1394
  // Find Route Tables
1080
1395
  try {
1081
1396
  const rtCommand = new DescribeRouteTablesCommand({
1082
- Filters: friggFilters
1397
+ Filters: friggFilters,
1083
1398
  });
1084
1399
  const rtResponse = await this.ec2Client.send(rtCommand);
1085
1400
  results.routeTables = rtResponse.RouteTables || [];
@@ -1090,7 +1405,7 @@ class AWSDiscovery {
1090
1405
  // Find Subnets
1091
1406
  try {
1092
1407
  const subnetCommand = new DescribeSubnetsCommand({
1093
- Filters: friggFilters
1408
+ Filters: friggFilters,
1094
1409
  });
1095
1410
  const subnetResponse = await this.ec2Client.send(subnetCommand);
1096
1411
  results.subnets = subnetResponse.Subnets || [];
@@ -1101,12 +1416,15 @@ class AWSDiscovery {
1101
1416
  // Find Security Groups
1102
1417
  try {
1103
1418
  const sgCommand = new DescribeSecurityGroupsCommand({
1104
- Filters: friggFilters
1419
+ Filters: friggFilters,
1105
1420
  });
1106
1421
  const sgResponse = await this.ec2Client.send(sgCommand);
1107
1422
  results.securityGroups = sgResponse.SecurityGroups || [];
1108
1423
  } catch (err) {
1109
- console.warn('Error finding Frigg Security Groups:', err.message);
1424
+ console.warn(
1425
+ 'Error finding Frigg Security Groups:',
1426
+ err.message
1427
+ );
1110
1428
  }
1111
1429
 
1112
1430
  console.log('Found Frigg-managed resources:', {
@@ -1114,7 +1432,7 @@ class AWSDiscovery {
1114
1432
  elasticIps: results.elasticIps.length,
1115
1433
  routeTables: results.routeTables.length,
1116
1434
  subnets: results.subnets.length,
1117
- securityGroups: results.securityGroups.length
1435
+ securityGroups: results.securityGroups.length,
1118
1436
  });
1119
1437
 
1120
1438
  return results;
@@ -1125,10 +1443,10 @@ class AWSDiscovery {
1125
1443
  elasticIps: [],
1126
1444
  routeTables: [],
1127
1445
  subnets: [],
1128
- securityGroups: []
1446
+ securityGroups: [],
1129
1447
  };
1130
1448
  }
1131
1449
  }
1132
1450
  }
1133
1451
 
1134
- module.exports = { AWSDiscovery };
1452
+ module.exports = { AWSDiscovery };