@friggframework/devtools 2.0.0--canary.461.ec1ad4e.0 → 2.0.0--canary.461.4116d1e.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.
@@ -108,9 +108,15 @@ class CloudFormationDiscovery {
108
108
  * @param {Object} discovered - Object to populate with discovered resources
109
109
  */
110
110
  async _extractFromResources(resources, discovered) {
111
+ console.log(` DEBUG: Processing ${resources.length} CloudFormation resources...`);
111
112
  for (const resource of resources) {
112
113
  const { LogicalResourceId, PhysicalResourceId, ResourceType } = resource;
113
114
 
115
+ // Debug Aurora detection
116
+ if (LogicalResourceId.includes('Aurora')) {
117
+ console.log(` DEBUG: Found Aurora resource: ${LogicalResourceId} (${ResourceType})`);
118
+ }
119
+
114
120
  // Security Group - use to get VPC ID
115
121
  if (LogicalResourceId === 'FriggLambdaSecurityGroup' && ResourceType === 'AWS::EC2::SecurityGroup') {
116
122
  discovered.securityGroupId = PhysicalResourceId;
@@ -141,21 +147,21 @@ class CloudFormationDiscovery {
141
147
  if (LogicalResourceId === 'FriggAuroraCluster' && ResourceType === 'AWS::RDS::DBCluster') {
142
148
  discovered.auroraClusterId = PhysicalResourceId;
143
149
  console.log(` ✓ Found Aurora cluster in stack: ${PhysicalResourceId}`);
144
-
150
+
145
151
  // Query RDS to get cluster endpoint
146
152
  if (this.provider && !discovered.auroraClusterEndpoint) {
147
153
  try {
148
154
  console.log(` Querying RDS to get Aurora endpoint...`);
149
155
  const { DescribeDBClustersCommand } = require('@aws-sdk/client-rds');
150
156
  const { RDSClient } = require('@aws-sdk/client-rds');
151
-
157
+
152
158
  const rdsClient = new RDSClient({ region: this.provider.region });
153
159
  const clusterDetails = await rdsClient.send(
154
160
  new DescribeDBClustersCommand({
155
161
  DBClusterIdentifier: PhysicalResourceId
156
162
  })
157
163
  );
158
-
164
+
159
165
  if (clusterDetails.DBClusters && clusterDetails.DBClusters.length > 0) {
160
166
  const cluster = clusterDetails.DBClusters[0];
161
167
  discovered.auroraClusterEndpoint = cluster.Endpoint;
@@ -235,6 +241,58 @@ class CloudFormationDiscovery {
235
241
  discovered.sqsVpcEndpointId = PhysicalResourceId;
236
242
  }
237
243
  }
244
+
245
+ // If we have a VPC ID but no subnet IDs, query EC2 for Frigg-managed subnets
246
+ if (discovered.defaultVpcId && this.provider &&
247
+ !discovered.privateSubnetId1 && !discovered.publicSubnetId1) {
248
+ try {
249
+ console.log(' Querying EC2 for Frigg-managed subnets...');
250
+ const { DescribeSubnetsCommand } = require('@aws-sdk/client-ec2');
251
+ const subnetResponse = await this.provider.getEC2Client().send(
252
+ new DescribeSubnetsCommand({
253
+ Filters: [
254
+ { Name: 'vpc-id', Values: [discovered.defaultVpcId] },
255
+ { Name: 'tag:ManagedBy', Values: ['Frigg'] },
256
+ ],
257
+ })
258
+ );
259
+
260
+ if (subnetResponse.Subnets && subnetResponse.Subnets.length > 0) {
261
+ // Extract subnet IDs by logical ID from tags
262
+ const subnets = subnetResponse.Subnets.map(subnet => ({
263
+ subnetId: subnet.SubnetId,
264
+ logicalId: subnet.Tags?.find(t => t.Key === 'aws:cloudformation:logical-id')?.Value,
265
+ isPublic: subnet.MapPublicIpOnLaunch,
266
+ }));
267
+
268
+ // Find private subnets
269
+ const privateSubnets = subnets.filter(s => !s.isPublic).sort((a, b) =>
270
+ a.logicalId?.localeCompare(b.logicalId) || 0
271
+ );
272
+ if (privateSubnets.length >= 1) {
273
+ discovered.privateSubnetId1 = privateSubnets[0].subnetId;
274
+ }
275
+ if (privateSubnets.length >= 2) {
276
+ discovered.privateSubnetId2 = privateSubnets[1].subnetId;
277
+ }
278
+
279
+ // Find public subnets
280
+ const publicSubnets = subnets.filter(s => s.isPublic).sort((a, b) =>
281
+ a.logicalId?.localeCompare(b.logicalId) || 0
282
+ );
283
+ if (publicSubnets.length >= 1) {
284
+ discovered.publicSubnetId1 = publicSubnets[0].subnetId;
285
+ }
286
+ if (publicSubnets.length >= 2) {
287
+ discovered.publicSubnetId2 = publicSubnets[1].subnetId;
288
+ }
289
+
290
+ console.log(` ✓ Found ${subnets.length} Frigg-managed subnets via EC2 query`);
291
+ }
292
+ } catch (error) {
293
+ console.warn(` ⚠️ Could not query EC2 for subnets: ${error.message}`);
294
+ }
295
+ }
238
296
  }
239
297
  }
240
298
 
@@ -307,6 +307,98 @@ describe('CloudFormationDiscovery', () => {
307
307
 
308
308
  expect(result).toEqual({});
309
309
  });
310
+
311
+ it('should query EC2 for subnets when VPC found but no subnet resources in stack', async () => {
312
+ const mockStack = {
313
+ StackName: 'test-stack',
314
+ Outputs: [],
315
+ };
316
+
317
+ const mockResources = [
318
+ {
319
+ LogicalResourceId: 'FriggLambdaSecurityGroup',
320
+ PhysicalResourceId: 'sg-123',
321
+ ResourceType: 'AWS::EC2::SecurityGroup',
322
+ },
323
+ ];
324
+
325
+ const mockEC2Client = {
326
+ send: jest.fn(),
327
+ };
328
+
329
+ mockProvider.describeStack.mockResolvedValue(mockStack);
330
+ mockProvider.listStackResources.mockResolvedValue(mockResources);
331
+ mockProvider.getEC2Client = jest.fn().mockReturnValue(mockEC2Client);
332
+
333
+ // Mock security group query for VPC ID
334
+ mockEC2Client.send.mockResolvedValueOnce({
335
+ SecurityGroups: [{ VpcId: 'vpc-123' }],
336
+ });
337
+
338
+ // Mock subnet query
339
+ mockEC2Client.send.mockResolvedValueOnce({
340
+ Subnets: [
341
+ {
342
+ SubnetId: 'subnet-private-1',
343
+ MapPublicIpOnLaunch: false,
344
+ Tags: [
345
+ { Key: 'ManagedBy', Value: 'Frigg' },
346
+ { Key: 'aws:cloudformation:logical-id', Value: 'FriggPrivateSubnet1' },
347
+ ],
348
+ },
349
+ {
350
+ SubnetId: 'subnet-private-2',
351
+ MapPublicIpOnLaunch: false,
352
+ Tags: [
353
+ { Key: 'ManagedBy', Value: 'Frigg' },
354
+ { Key: 'aws:cloudformation:logical-id', Value: 'FriggPrivateSubnet2' },
355
+ ],
356
+ },
357
+ ],
358
+ });
359
+
360
+ const result = await cfDiscovery.discoverFromStack('test-stack');
361
+
362
+ expect(result.privateSubnetId1).toBe('subnet-private-1');
363
+ expect(result.privateSubnetId2).toBe('subnet-private-2');
364
+ expect(mockEC2Client.send).toHaveBeenCalledTimes(2);
365
+ });
366
+
367
+ it('should handle EC2 subnet query errors gracefully', async () => {
368
+ const mockStack = {
369
+ StackName: 'test-stack',
370
+ Outputs: [],
371
+ };
372
+
373
+ const mockResources = [
374
+ {
375
+ LogicalResourceId: 'FriggLambdaSecurityGroup',
376
+ PhysicalResourceId: 'sg-123',
377
+ ResourceType: 'AWS::EC2::SecurityGroup',
378
+ },
379
+ ];
380
+
381
+ const mockEC2Client = {
382
+ send: jest.fn(),
383
+ };
384
+
385
+ mockProvider.describeStack.mockResolvedValue(mockStack);
386
+ mockProvider.listStackResources.mockResolvedValue(mockResources);
387
+ mockProvider.getEC2Client = jest.fn().mockReturnValue(mockEC2Client);
388
+
389
+ // Mock security group query for VPC ID
390
+ mockEC2Client.send.mockResolvedValueOnce({
391
+ SecurityGroups: [{ VpcId: 'vpc-123' }],
392
+ });
393
+
394
+ // Mock subnet query failure
395
+ mockEC2Client.send.mockRejectedValueOnce(new Error('EC2 API Error'));
396
+
397
+ const result = await cfDiscovery.discoverFromStack('test-stack');
398
+
399
+ expect(result.defaultVpcId).toBe('vpc-123');
400
+ expect(result.privateSubnetId1).toBeUndefined();
401
+ });
310
402
  });
311
403
  });
312
404
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@friggframework/devtools",
3
3
  "prettier": "@friggframework/prettier-config",
4
- "version": "2.0.0--canary.461.ec1ad4e.0",
4
+ "version": "2.0.0--canary.461.4116d1e.0",
5
5
  "dependencies": {
6
6
  "@aws-sdk/client-ec2": "^3.835.0",
7
7
  "@aws-sdk/client-kms": "^3.835.0",
@@ -11,8 +11,8 @@
11
11
  "@babel/eslint-parser": "^7.18.9",
12
12
  "@babel/parser": "^7.25.3",
13
13
  "@babel/traverse": "^7.25.3",
14
- "@friggframework/schemas": "2.0.0--canary.461.ec1ad4e.0",
15
- "@friggframework/test": "2.0.0--canary.461.ec1ad4e.0",
14
+ "@friggframework/schemas": "2.0.0--canary.461.4116d1e.0",
15
+ "@friggframework/test": "2.0.0--canary.461.4116d1e.0",
16
16
  "@hapi/boom": "^10.0.1",
17
17
  "@inquirer/prompts": "^5.3.8",
18
18
  "axios": "^1.7.2",
@@ -34,8 +34,8 @@
34
34
  "serverless-http": "^2.7.0"
35
35
  },
36
36
  "devDependencies": {
37
- "@friggframework/eslint-config": "2.0.0--canary.461.ec1ad4e.0",
38
- "@friggframework/prettier-config": "2.0.0--canary.461.ec1ad4e.0",
37
+ "@friggframework/eslint-config": "2.0.0--canary.461.4116d1e.0",
38
+ "@friggframework/prettier-config": "2.0.0--canary.461.4116d1e.0",
39
39
  "aws-sdk-client-mock": "^4.1.0",
40
40
  "aws-sdk-client-mock-jest": "^4.1.0",
41
41
  "jest": "^30.1.3",
@@ -70,5 +70,5 @@
70
70
  "publishConfig": {
71
71
  "access": "public"
72
72
  },
73
- "gitHead": "ec1ad4ecc89d650ce9380aeed0a29831d8e2098b"
73
+ "gitHead": "4116d1e999b2ef801f8433755d107b34a12ad817"
74
74
  }