@friggframework/devtools 2.0.0--canary.461.e4a989e.0 → 2.0.0--canary.461.d94b7a7.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.
@@ -235,6 +235,20 @@ class AuroraBuilder extends InfrastructureBuilder {
235
235
  Resource: { Ref: 'FriggDBSecret' },
236
236
  });
237
237
 
238
+ // Add self-referencing security group ingress rule to allow Lambda to connect to Aurora
239
+ // Since both Lambda and Aurora share the same security group, we need to allow the SG to accept traffic from itself
240
+ result.resources.FriggAuroraIngressRule = {
241
+ Type: 'AWS::EC2::SecurityGroupIngress',
242
+ Properties: {
243
+ GroupId: { Ref: 'FriggLambdaSecurityGroup' },
244
+ IpProtocol: 'tcp',
245
+ FromPort: 5432,
246
+ ToPort: 5432,
247
+ SourceSecurityGroupId: { Ref: 'FriggLambdaSecurityGroup' },
248
+ Description: 'Allow Lambda functions to connect to Aurora PostgreSQL (self-referencing rule)',
249
+ },
250
+ };
251
+
238
252
  console.log(' ✅ Aurora Serverless v2 cluster resources created');
239
253
  }
240
254
 
@@ -625,12 +625,18 @@ describe('AuroraBuilder', () => {
625
625
 
626
626
  expect(result.resources.FriggAuroraCluster).toBeDefined();
627
627
  expect(result.resources.FriggAuroraCluster.Type).toBe('AWS::RDS::DBCluster');
628
-
628
+
629
629
  // PubliclyAccessible is NOT supported on Aurora clusters (only on instances)
630
630
  expect(result.resources.FriggAuroraCluster.Properties.PubliclyAccessible).toBeUndefined();
631
-
631
+
632
632
  // Port should be explicitly set to PostgreSQL standard (5432)
633
633
  expect(result.resources.FriggAuroraCluster.Properties.Port).toBe(5432);
634
+
635
+ // Should create self-referencing security group ingress rule
636
+ expect(result.resources.FriggAuroraIngressRule).toBeDefined();
637
+ expect(result.resources.FriggAuroraIngressRule.Type).toBe('AWS::EC2::SecurityGroupIngress');
638
+ expect(result.resources.FriggAuroraIngressRule.Properties.FromPort).toBe(5432);
639
+ expect(result.resources.FriggAuroraIngressRule.Properties.ToPort).toBe(5432);
634
640
  });
635
641
 
636
642
  it('should create database subnet group', async () => {
@@ -141,18 +141,32 @@ class VpcBuilder extends InfrastructureBuilder {
141
141
  // Build VPC Endpoints if enabled
142
142
  const vpcManagement = appDefinition.vpc.management || 'discover';
143
143
  const selfHeal = appDefinition.vpc.selfHeal !== false;
144
- const vpcEndpointsExist = discoveredResources.s3VpcEndpointId && discoveredResources.dynamodbVpcEndpointId;
144
+ // Check which VPC endpoints already exist
145
+ const existingEndpoints = {
146
+ s3: discoveredResources.s3VpcEndpointId,
147
+ dynamodb: discoveredResources.dynamodbVpcEndpointId,
148
+ kms: discoveredResources.kmsVpcEndpointId,
149
+ secretsManager: discoveredResources.secretsManagerVpcEndpointId,
150
+ };
151
+ const allEndpointsExist = existingEndpoints.s3 && existingEndpoints.dynamodb &&
152
+ existingEndpoints.kms && existingEndpoints.secretsManager;
153
+ const someEndpointsExist = existingEndpoints.s3 || existingEndpoints.dynamodb ||
154
+ existingEndpoints.kms || existingEndpoints.secretsManager;
145
155
 
146
156
  if (appDefinition.vpc.enableVPCEndpoints !== false) {
147
157
  if (vpcManagement === 'create-new') {
148
158
  // Always create in create-new mode
149
- this.buildVpcEndpoints(appDefinition, discoveredResources, result);
159
+ this.buildVpcEndpoints(appDefinition, discoveredResources, result, existingEndpoints);
150
160
  } else if (vpcManagement === 'discover') {
151
- if (vpcEndpointsExist) {
152
- console.log(' VPC endpoints already exist - skipping creation');
161
+ if (allEndpointsExist) {
162
+ console.log(' All VPC endpoints already exist - skipping creation');
153
163
  } else if (selfHeal) {
154
- console.log(' No VPC endpoints found - selfHeal creating them');
155
- this.buildVpcEndpoints(appDefinition, discoveredResources, result);
164
+ if (someEndpointsExist) {
165
+ console.log(' Some VPC endpoints found - selfHeal creating missing ones');
166
+ } else {
167
+ console.log(' No VPC endpoints found - selfHeal creating them');
168
+ }
169
+ this.buildVpcEndpoints(appDefinition, discoveredResources, result, existingEndpoints);
156
170
  } else {
157
171
  console.log(' VPC endpoints not found and selfHeal disabled - skipping');
158
172
  }
@@ -662,8 +676,19 @@ class VpcBuilder extends InfrastructureBuilder {
662
676
  /**
663
677
  * Build VPC Endpoints for AWS services
664
678
  */
665
- buildVpcEndpoints(appDefinition, discoveredResources, result) {
666
- console.log(' Creating VPC Endpoints...');
679
+ buildVpcEndpoints(appDefinition, discoveredResources, result, existingEndpoints = {}) {
680
+ const missing = [];
681
+ if (!existingEndpoints.s3) missing.push('S3');
682
+ if (!existingEndpoints.dynamodb) missing.push('DynamoDB');
683
+ if (!existingEndpoints.kms && appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') missing.push('KMS');
684
+ if (!existingEndpoints.secretsManager) missing.push('Secrets Manager');
685
+
686
+ if (missing.length > 0) {
687
+ console.log(` Creating missing VPC Endpoints: ${missing.join(', ')}...`);
688
+ } else {
689
+ console.log(' All required VPC Endpoints already exist - skipping creation');
690
+ return;
691
+ }
667
692
 
668
693
  const vpcId = result.vpcId || discoveredResources.defaultVpcId;
669
694
 
@@ -682,8 +707,9 @@ class VpcBuilder extends InfrastructureBuilder {
682
707
  };
683
708
  }
684
709
 
685
- // S3 Gateway Endpoint
686
- result.resources.FriggS3VPCEndpoint = {
710
+ // S3 Gateway Endpoint (only if missing)
711
+ if (!existingEndpoints.s3) {
712
+ result.resources.FriggS3VPCEndpoint = {
687
713
  Type: 'AWS::EC2::VPCEndpoint',
688
714
  Properties: {
689
715
  VpcId: vpcId,
@@ -692,9 +718,11 @@ class VpcBuilder extends InfrastructureBuilder {
692
718
  RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }],
693
719
  },
694
720
  };
721
+ }
695
722
 
696
- // DynamoDB Gateway Endpoint
697
- result.resources.FriggDynamoDBVPCEndpoint = {
723
+ // DynamoDB Gateway Endpoint (only if missing)
724
+ if (!existingEndpoints.dynamodb) {
725
+ result.resources.FriggDynamoDBVPCEndpoint = {
698
726
  Type: 'AWS::EC2::VPCEndpoint',
699
727
  Properties: {
700
728
  VpcId: vpcId,
@@ -703,9 +731,11 @@ class VpcBuilder extends InfrastructureBuilder {
703
731
  RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }],
704
732
  },
705
733
  };
734
+ }
706
735
 
707
- // VPC Endpoint Security Group
708
- result.resources.FriggVPCEndpointSecurityGroup = {
736
+ // VPC Endpoint Security Group (only if KMS or Secrets Manager are missing)
737
+ if (!existingEndpoints.kms || !existingEndpoints.secretsManager) {
738
+ result.resources.FriggVPCEndpointSecurityGroup = {
709
739
  Type: 'AWS::EC2::SecurityGroup',
710
740
  Properties: {
711
741
  GroupDescription: 'Security group for VPC Endpoints',
@@ -725,9 +755,10 @@ class VpcBuilder extends InfrastructureBuilder {
725
755
  ],
726
756
  },
727
757
  };
758
+ }
728
759
 
729
- // KMS Interface Endpoint (if KMS encryption enabled)
730
- if (appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') {
760
+ // KMS Interface Endpoint (only if missing AND KMS encryption is enabled)
761
+ if (!existingEndpoints.kms && appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') {
731
762
  result.resources.FriggKMSVPCEndpoint = {
732
763
  Type: 'AWS::EC2::VPCEndpoint',
733
764
  Properties: {
@@ -741,8 +772,9 @@ class VpcBuilder extends InfrastructureBuilder {
741
772
  };
742
773
  }
743
774
 
744
- // Secrets Manager Interface Endpoint
745
- result.resources.FriggSecretsManagerVPCEndpoint = {
775
+ // Secrets Manager Interface Endpoint (only if missing)
776
+ if (!existingEndpoints.secretsManager) {
777
+ result.resources.FriggSecretsManagerVPCEndpoint = {
746
778
  Type: 'AWS::EC2::VPCEndpoint',
747
779
  Properties: {
748
780
  VpcId: vpcId,
@@ -753,8 +785,9 @@ class VpcBuilder extends InfrastructureBuilder {
753
785
  PrivateDnsEnabled: true,
754
786
  },
755
787
  };
788
+ }
756
789
 
757
- console.log('VPC Endpoints created (S3, DynamoDB, KMS, Secrets Manager)');
790
+ console.log(`Created ${missing.length} VPC endpoint(s): ${missing.join(', ')}`);
758
791
  }
759
792
  }
760
793
 
@@ -114,6 +114,12 @@ class VpcDiscovery {
114
114
  const dynamodbEndpoint = rawResources.vpcEndpoints.find(
115
115
  ep => ep.ServiceName && ep.ServiceName.includes('.dynamodb')
116
116
  );
117
+ const kmsEndpoint = rawResources.vpcEndpoints.find(
118
+ ep => ep.ServiceName && ep.ServiceName.includes('.kms')
119
+ );
120
+ const secretsManagerEndpoint = rawResources.vpcEndpoints.find(
121
+ ep => ep.ServiceName && ep.ServiceName.includes('.secretsmanager')
122
+ );
117
123
 
118
124
  if (s3Endpoint) {
119
125
  result.s3VpcEndpointId = s3Endpoint.VpcEndpointId;
@@ -121,6 +127,12 @@ class VpcDiscovery {
121
127
  if (dynamodbEndpoint) {
122
128
  result.dynamodbVpcEndpointId = dynamodbEndpoint.VpcEndpointId;
123
129
  }
130
+ if (kmsEndpoint) {
131
+ result.kmsVpcEndpointId = kmsEndpoint.VpcEndpointId;
132
+ }
133
+ if (secretsManagerEndpoint) {
134
+ result.secretsManagerVpcEndpointId = secretsManagerEndpoint.VpcEndpointId;
135
+ }
124
136
  }
125
137
 
126
138
  console.log(` ✓ Found VPC: ${result.defaultVpcId}`);
@@ -133,8 +145,8 @@ class VpcDiscovery {
133
145
  if (result.existingNatGatewayId) {
134
146
  console.log(` ✓ Found NAT Gateway: ${result.existingNatGatewayId}`);
135
147
  }
136
- if (result.s3VpcEndpointId || result.dynamodbVpcEndpointId) {
137
- console.log(` ✓ Found VPC Endpoints: S3=${result.s3VpcEndpointId ? 'Yes' : 'No'}, DynamoDB=${result.dynamodbVpcEndpointId ? 'Yes' : 'No'}`);
148
+ if (result.s3VpcEndpointId || result.dynamodbVpcEndpointId || result.kmsVpcEndpointId || result.secretsManagerVpcEndpointId) {
149
+ console.log(` ✓ Found VPC Endpoints: S3=${result.s3VpcEndpointId ? 'Yes' : 'No'}, DynamoDB=${result.dynamodbVpcEndpointId ? 'Yes' : 'No'}, KMS=${result.kmsVpcEndpointId ? 'Yes' : 'No'}, SecretsManager=${result.secretsManagerVpcEndpointId ? 'Yes' : 'No'}`);
138
150
  }
139
151
 
140
152
  return result;
@@ -252,6 +252,99 @@ describe('VpcDiscovery', () => {
252
252
 
253
253
  expect(mockProvider.discoverVpc).toHaveBeenCalledWith(config);
254
254
  });
255
+
256
+ it('should discover all VPC endpoints (S3, DynamoDB, KMS, Secrets Manager)', async () => {
257
+ mockProvider.discoverVpc.mockResolvedValue({
258
+ vpcId: 'vpc-123',
259
+ vpcCidr: '10.0.0.0/16',
260
+ subnets: [],
261
+ securityGroups: [],
262
+ routeTables: [],
263
+ natGateways: [],
264
+ internetGateways: [],
265
+ vpcEndpoints: [
266
+ {
267
+ VpcEndpointId: 'vpce-s3-123',
268
+ ServiceName: 'com.amazonaws.us-east-1.s3',
269
+ State: 'available',
270
+ },
271
+ {
272
+ VpcEndpointId: 'vpce-ddb-456',
273
+ ServiceName: 'com.amazonaws.us-east-1.dynamodb',
274
+ State: 'available',
275
+ },
276
+ {
277
+ VpcEndpointId: 'vpce-kms-789',
278
+ ServiceName: 'com.amazonaws.us-east-1.kms',
279
+ State: 'available',
280
+ },
281
+ {
282
+ VpcEndpointId: 'vpce-sm-abc',
283
+ ServiceName: 'com.amazonaws.us-east-1.secretsmanager',
284
+ State: 'available',
285
+ },
286
+ ],
287
+ });
288
+
289
+ const result = await vpcDiscovery.discover({});
290
+
291
+ expect(result.s3VpcEndpointId).toBe('vpce-s3-123');
292
+ expect(result.dynamodbVpcEndpointId).toBe('vpce-ddb-456');
293
+ expect(result.kmsVpcEndpointId).toBe('vpce-kms-789');
294
+ expect(result.secretsManagerVpcEndpointId).toBe('vpce-sm-abc');
295
+ });
296
+
297
+ it('should handle partial VPC endpoint discovery', async () => {
298
+ mockProvider.discoverVpc.mockResolvedValue({
299
+ vpcId: 'vpc-123',
300
+ vpcCidr: '10.0.0.0/16',
301
+ subnets: [],
302
+ securityGroups: [],
303
+ routeTables: [],
304
+ natGateways: [],
305
+ internetGateways: [],
306
+ vpcEndpoints: [
307
+ {
308
+ VpcEndpointId: 'vpce-s3-123',
309
+ ServiceName: 'com.amazonaws.us-east-1.s3',
310
+ State: 'available',
311
+ },
312
+ {
313
+ VpcEndpointId: 'vpce-ddb-456',
314
+ ServiceName: 'com.amazonaws.us-east-1.dynamodb',
315
+ State: 'available',
316
+ },
317
+ // KMS and Secrets Manager are missing
318
+ ],
319
+ });
320
+
321
+ const result = await vpcDiscovery.discover({});
322
+
323
+ expect(result.s3VpcEndpointId).toBe('vpce-s3-123');
324
+ expect(result.dynamodbVpcEndpointId).toBe('vpce-ddb-456');
325
+ expect(result.kmsVpcEndpointId).toBeUndefined();
326
+ expect(result.secretsManagerVpcEndpointId).toBeUndefined();
327
+ });
328
+
329
+ it('should handle no VPC endpoints', async () => {
330
+ mockProvider.discoverVpc.mockResolvedValue({
331
+ vpcId: 'vpc-123',
332
+ vpcCidr: '10.0.0.0/16',
333
+ subnets: [],
334
+ securityGroups: [],
335
+ routeTables: [],
336
+ natGateways: [],
337
+ internetGateways: [],
338
+ vpcEndpoints: [],
339
+ });
340
+
341
+ const result = await vpcDiscovery.discover({});
342
+
343
+ expect(result.s3VpcEndpointId).toBeUndefined();
344
+ expect(result.dynamodbVpcEndpointId).toBeUndefined();
345
+ expect(result.kmsVpcEndpointId).toBeUndefined();
346
+ expect(result.secretsManagerVpcEndpointId).toBeUndefined();
347
+ });
255
348
  });
256
349
  });
257
350
 
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.e4a989e.0",
4
+ "version": "2.0.0--canary.461.d94b7a7.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.e4a989e.0",
15
- "@friggframework/test": "2.0.0--canary.461.e4a989e.0",
14
+ "@friggframework/schemas": "2.0.0--canary.461.d94b7a7.0",
15
+ "@friggframework/test": "2.0.0--canary.461.d94b7a7.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.e4a989e.0",
38
- "@friggframework/prettier-config": "2.0.0--canary.461.e4a989e.0",
37
+ "@friggframework/eslint-config": "2.0.0--canary.461.d94b7a7.0",
38
+ "@friggframework/prettier-config": "2.0.0--canary.461.d94b7a7.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": "e4a989e8eaa78cd8020962c0fc7f52b06d2565cb"
73
+ "gitHead": "d94b7a7ce1086865523407232f49071136c8eb9b"
74
74
  }