@friggframework/devtools 2.0.0--canary.461.9f988ca.0 → 2.0.0--canary.461.ae78cad.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.
|
@@ -110,7 +110,23 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
110
110
|
Resource: '*',
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
// Normalize shareAcrossStages into management mode
|
|
114
|
+
// This provides a simpler API for users while maintaining backwards compatibility
|
|
115
|
+
let management = appDefinition.vpc.management;
|
|
116
|
+
if (!management && appDefinition.vpc.shareAcrossStages !== undefined) {
|
|
117
|
+
// Explicit shareAcrossStages setting overrides default
|
|
118
|
+
management = appDefinition.vpc.shareAcrossStages ? 'discover' : 'create-new';
|
|
119
|
+
console.log(` VPC Sharing: ${appDefinition.vpc.shareAcrossStages ? 'shared' : 'isolated'} (translated to ${management})`);
|
|
120
|
+
|
|
121
|
+
// When creating isolated VPC, also create isolated NAT Gateway
|
|
122
|
+
if (!appDefinition.vpc.shareAcrossStages && !appDefinition.vpc.natGateway?.management) {
|
|
123
|
+
appDefinition.vpc.natGateway = appDefinition.vpc.natGateway || {};
|
|
124
|
+
appDefinition.vpc.natGateway.management = 'createAndManage';
|
|
125
|
+
console.log(` NAT Gateway: creating isolated NAT (shareAcrossStages=false)`);
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
management = management || 'discover'; // Default to sharing for backwards compatibility
|
|
129
|
+
}
|
|
114
130
|
console.log(` VPC Management Mode: ${management}`);
|
|
115
131
|
|
|
116
132
|
// Handle self-healing if enabled
|
|
@@ -372,10 +388,17 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
372
388
|
*/
|
|
373
389
|
async buildSubnets(appDefinition, discoveredResources, result) {
|
|
374
390
|
const vpcManagement = appDefinition.vpc.management || 'discover';
|
|
375
|
-
|
|
391
|
+
// Default subnet management depends on context:
|
|
392
|
+
// - use-existing mode with subnet IDs provided: use-existing
|
|
393
|
+
// - create-new mode: create
|
|
394
|
+
// - discover mode: create (for stage isolation)
|
|
395
|
+
let defaultSubnetManagement = 'create';
|
|
396
|
+
if (vpcManagement === 'use-existing' && appDefinition.vpc.subnets?.ids?.length >= 2) {
|
|
397
|
+
defaultSubnetManagement = 'use-existing';
|
|
398
|
+
}
|
|
376
399
|
const subnetManagement = appDefinition.vpc.subnets?.management || defaultSubnetManagement;
|
|
377
400
|
|
|
378
|
-
console.log(` Subnet Management Mode: ${subnetManagement}`);
|
|
401
|
+
console.log(` Subnet Management Mode: ${subnetManagement} (default: ${defaultSubnetManagement}, explicit: ${appDefinition.vpc.subnets?.management})`);
|
|
379
402
|
|
|
380
403
|
switch (subnetManagement) {
|
|
381
404
|
case 'create':
|
|
@@ -508,17 +531,19 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
508
531
|
return;
|
|
509
532
|
}
|
|
510
533
|
|
|
511
|
-
//
|
|
534
|
+
// User explicitly set subnets.management: 'discover', so use discovered subnets
|
|
535
|
+
// NOTE: This may cause route table conflicts if multiple stages share subnets
|
|
536
|
+
// Default behavior is now to create stage-specific subnets (subnets.management: 'create')
|
|
512
537
|
if (discoveredResources.privateSubnetId1 && discoveredResources.privateSubnetId2) {
|
|
513
538
|
result.vpcConfig.subnetIds = [
|
|
514
539
|
discoveredResources.privateSubnetId1,
|
|
515
540
|
discoveredResources.privateSubnetId2,
|
|
516
541
|
];
|
|
517
|
-
console.log(' ✅
|
|
542
|
+
console.log(' ✅ Using discovered subnets (backwards compatibility mode)');
|
|
518
543
|
return;
|
|
519
544
|
}
|
|
520
545
|
|
|
521
|
-
//
|
|
546
|
+
// No subnets found - create if self-heal enabled
|
|
522
547
|
if (appDefinition.vpc.selfHeal) {
|
|
523
548
|
console.log(' ⚠️ No subnets found - self-heal will create them');
|
|
524
549
|
this.createSubnets(appDefinition, discoveredResources, result, 'discover');
|
|
@@ -241,7 +241,7 @@ describe('VpcBuilder', () => {
|
|
|
241
241
|
});
|
|
242
242
|
|
|
243
243
|
describe('build() - discover mode', () => {
|
|
244
|
-
it('should use discovered VPC
|
|
244
|
+
it('should use discovered VPC but create stage-specific subnets by default', async () => {
|
|
245
245
|
const appDefinition = {
|
|
246
246
|
vpc: {
|
|
247
247
|
enable: true,
|
|
@@ -251,6 +251,7 @@ describe('VpcBuilder', () => {
|
|
|
251
251
|
|
|
252
252
|
const discoveredResources = {
|
|
253
253
|
defaultVpcId: 'vpc-discovered',
|
|
254
|
+
// Even though subnets are discovered, we should create new ones for stage isolation
|
|
254
255
|
privateSubnetId1: 'subnet-private1',
|
|
255
256
|
privateSubnetId2: 'subnet-private2',
|
|
256
257
|
defaultSecurityGroupId: 'sg-discovered',
|
|
@@ -258,13 +259,43 @@ describe('VpcBuilder', () => {
|
|
|
258
259
|
|
|
259
260
|
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
260
261
|
|
|
261
|
-
|
|
262
|
+
// NEW BEHAVIOR: Create stage-specific subnets for isolation (prevent route table conflicts)
|
|
263
|
+
expect(result.vpcConfig.subnetIds).toEqual([
|
|
264
|
+
{ Ref: 'FriggPrivateSubnet1' },
|
|
265
|
+
{ Ref: 'FriggPrivateSubnet2' },
|
|
266
|
+
]);
|
|
267
|
+
expect(result.resources.FriggPrivateSubnet1).toBeDefined();
|
|
268
|
+
expect(result.resources.FriggPrivateSubnet2).toBeDefined();
|
|
262
269
|
// In discover mode, we create FriggLambdaSecurityGroup in the discovered VPC
|
|
263
270
|
expect(result.vpcConfig.securityGroupIds).toEqual([{ Ref: 'FriggLambdaSecurityGroup' }]);
|
|
264
271
|
expect(result.resources.FriggLambdaSecurityGroup).toBeDefined();
|
|
265
272
|
expect(result.resources.FriggLambdaSecurityGroup.Properties.VpcId).toBe('vpc-discovered');
|
|
266
273
|
});
|
|
267
274
|
|
|
275
|
+
it('should allow sharing discovered subnets when explicitly configured', async () => {
|
|
276
|
+
const appDefinition = {
|
|
277
|
+
vpc: {
|
|
278
|
+
enable: true,
|
|
279
|
+
management: 'discover',
|
|
280
|
+
subnets: {
|
|
281
|
+
management: 'discover', // Explicitly opt-in to subnet sharing
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const discoveredResources = {
|
|
287
|
+
defaultVpcId: 'vpc-discovered',
|
|
288
|
+
privateSubnetId1: 'subnet-shared-1',
|
|
289
|
+
privateSubnetId2: 'subnet-shared-2',
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
293
|
+
|
|
294
|
+
// OLD BEHAVIOR: When explicitly set to 'discover', reuse discovered subnets
|
|
295
|
+
expect(result.vpcConfig.subnetIds).toEqual(['subnet-shared-1', 'subnet-shared-2']);
|
|
296
|
+
expect(result.resources.FriggPrivateSubnet1).toBeUndefined();
|
|
297
|
+
});
|
|
298
|
+
|
|
268
299
|
it('should create VPC endpoints in discover mode with selfHeal when none exist', async () => {
|
|
269
300
|
const appDefinition = {
|
|
270
301
|
vpc: {
|
|
@@ -778,6 +809,81 @@ describe('VpcBuilder', () => {
|
|
|
778
809
|
});
|
|
779
810
|
});
|
|
780
811
|
|
|
812
|
+
describe('VPC Sharing Control', () => {
|
|
813
|
+
it('should share VPC across stages when shareAcrossStages is true (default)', async () => {
|
|
814
|
+
const appDefinition = {
|
|
815
|
+
vpc: {
|
|
816
|
+
enable: true,
|
|
817
|
+
shareAcrossStages: true, // Explicit opt-in to sharing
|
|
818
|
+
},
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
const discoveredResources = {
|
|
822
|
+
defaultVpcId: 'vpc-shared',
|
|
823
|
+
natGatewayId: 'nat-shared',
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
827
|
+
|
|
828
|
+
// Should use discovered VPC (not create new one)
|
|
829
|
+
expect(result.vpcId).toBe('vpc-shared');
|
|
830
|
+
expect(result.resources.FriggVPC).toBeUndefined();
|
|
831
|
+
|
|
832
|
+
// Should create stage-specific subnets for isolation
|
|
833
|
+
expect(result.resources.FriggPrivateSubnet1).toBeDefined();
|
|
834
|
+
expect(result.resources.FriggPrivateSubnet2).toBeDefined();
|
|
835
|
+
|
|
836
|
+
// Should reuse discovered NAT Gateway
|
|
837
|
+
expect(result.resources.FriggNATGateway).toBeUndefined();
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
it('should create isolated VPC when shareAcrossStages is false', async () => {
|
|
841
|
+
const appDefinition = {
|
|
842
|
+
vpc: {
|
|
843
|
+
enable: true,
|
|
844
|
+
shareAcrossStages: false, // Explicit opt-out of sharing
|
|
845
|
+
},
|
|
846
|
+
};
|
|
847
|
+
|
|
848
|
+
const discoveredResources = {
|
|
849
|
+
defaultVpcId: 'vpc-shared',
|
|
850
|
+
natGatewayId: 'nat-shared',
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
854
|
+
|
|
855
|
+
// Should create new VPC (ignore discovered resources)
|
|
856
|
+
expect(result.vpcId).toEqual({ Ref: 'FriggVPC' });
|
|
857
|
+
expect(result.resources.FriggVPC).toBeDefined();
|
|
858
|
+
|
|
859
|
+
// Should create stage-specific subnets
|
|
860
|
+
expect(result.resources.FriggPrivateSubnet1).toBeDefined();
|
|
861
|
+
expect(result.resources.FriggPrivateSubnet2).toBeDefined();
|
|
862
|
+
|
|
863
|
+
// Should create new NAT Gateway
|
|
864
|
+
expect(result.resources.FriggNATGateway).toBeDefined();
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
it('should default to shared VPC when shareAcrossStages is not specified', async () => {
|
|
868
|
+
const appDefinition = {
|
|
869
|
+
vpc: {
|
|
870
|
+
enable: true,
|
|
871
|
+
// shareAcrossStages not specified - should default to true
|
|
872
|
+
},
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
const discoveredResources = {
|
|
876
|
+
defaultVpcId: 'vpc-discovered',
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
880
|
+
|
|
881
|
+
// Should use discovered VPC by default (backwards compatibility)
|
|
882
|
+
expect(result.vpcId).toBe('vpc-discovered');
|
|
883
|
+
expect(result.resources.FriggVPC).toBeUndefined();
|
|
884
|
+
});
|
|
885
|
+
});
|
|
886
|
+
|
|
781
887
|
describe('Outputs', () => {
|
|
782
888
|
it.skip('should generate VPC ID output', async () => {
|
|
783
889
|
const appDefinition = {
|
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.
|
|
4
|
+
"version": "2.0.0--canary.461.ae78cad.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.
|
|
15
|
-
"@friggframework/test": "2.0.0--canary.461.
|
|
14
|
+
"@friggframework/schemas": "2.0.0--canary.461.ae78cad.0",
|
|
15
|
+
"@friggframework/test": "2.0.0--canary.461.ae78cad.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.
|
|
38
|
-
"@friggframework/prettier-config": "2.0.0--canary.461.
|
|
37
|
+
"@friggframework/eslint-config": "2.0.0--canary.461.ae78cad.0",
|
|
38
|
+
"@friggframework/prettier-config": "2.0.0--canary.461.ae78cad.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": "
|
|
73
|
+
"gitHead": "ae78cad00740f759a7a99f5e6565bdfbea31b01e"
|
|
74
74
|
}
|