@friggframework/devtools 2.0.0--canary.461.41b7566.0 → 2.0.0--canary.461.4066059.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.
- package/infrastructure/domains/networking/vpc-builder.js +15 -2
- package/infrastructure/domains/networking/vpc-builder.test.js +32 -5
- package/infrastructure/domains/shared/cloudformation-discovery.js +41 -0
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +84 -0
- package/package.json +6 -6
|
@@ -430,13 +430,26 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
430
430
|
*/
|
|
431
431
|
async buildSubnets(appDefinition, discoveredResources, result, vpcManagement) {
|
|
432
432
|
// Default subnet management depends on context:
|
|
433
|
+
// - Stack-managed subnets discovered: discover (reuse existing)
|
|
433
434
|
// - use-existing mode with subnet IDs provided: use-existing
|
|
434
435
|
// - create-new mode: create
|
|
435
|
-
// - discover mode: create (for stage isolation)
|
|
436
|
+
// - discover mode without stack subnets: create (for stage isolation)
|
|
436
437
|
let defaultSubnetManagement = 'create';
|
|
437
|
-
|
|
438
|
+
|
|
439
|
+
// Check if stack-managed subnets were discovered from CloudFormation
|
|
440
|
+
// Only reuse if they're actual subnet IDs (strings), not CloudFormation Refs (objects)
|
|
441
|
+
const hasStackManagedSubnets =
|
|
442
|
+
discoveredResources?.privateSubnetId1 &&
|
|
443
|
+
discoveredResources?.privateSubnetId2 &&
|
|
444
|
+
typeof discoveredResources.privateSubnetId1 === 'string' &&
|
|
445
|
+
typeof discoveredResources.privateSubnetId2 === 'string';
|
|
446
|
+
|
|
447
|
+
if (hasStackManagedSubnets) {
|
|
448
|
+
defaultSubnetManagement = 'discover';
|
|
449
|
+
} else if (vpcManagement === 'use-existing' && appDefinition.vpc.subnets?.ids?.length >= 2) {
|
|
438
450
|
defaultSubnetManagement = 'use-existing';
|
|
439
451
|
}
|
|
452
|
+
|
|
440
453
|
const subnetManagement = appDefinition.vpc.subnets?.management || defaultSubnetManagement;
|
|
441
454
|
|
|
442
455
|
console.log(` Subnet Management Mode: ${subnetManagement} (default: ${defaultSubnetManagement}, explicit: ${appDefinition.vpc.subnets?.management})`);
|
|
@@ -241,7 +241,36 @@ describe('VpcBuilder', () => {
|
|
|
241
241
|
});
|
|
242
242
|
|
|
243
243
|
describe('build() - discover mode', () => {
|
|
244
|
-
it('should
|
|
244
|
+
it('should reuse stack-managed subnets when discovered from CloudFormation', async () => {
|
|
245
|
+
const appDefinition = {
|
|
246
|
+
vpc: { enable: true },
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const discoveredResources = {
|
|
250
|
+
defaultVpcId: 'vpc-discovered',
|
|
251
|
+
privateSubnetId1: 'subnet-stack-private-1',
|
|
252
|
+
privateSubnetId2: 'subnet-stack-private-2',
|
|
253
|
+
publicSubnetId1: 'subnet-stack-public-1',
|
|
254
|
+
publicSubnetId2: 'subnet-stack-public-2',
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
258
|
+
|
|
259
|
+
// Should use discovered VPC
|
|
260
|
+
expect(result.vpcId).toBe('vpc-discovered');
|
|
261
|
+
|
|
262
|
+
// Should reuse stack-managed subnets (not create new ones)
|
|
263
|
+
expect(result.vpcConfig.subnetIds).toEqual([
|
|
264
|
+
'subnet-stack-private-1',
|
|
265
|
+
'subnet-stack-private-2',
|
|
266
|
+
]);
|
|
267
|
+
|
|
268
|
+
// Should NOT create new subnet resources
|
|
269
|
+
expect(result.resources.FriggPrivateSubnet1).toBeUndefined();
|
|
270
|
+
expect(result.resources.FriggPrivateSubnet2).toBeUndefined();
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should use discovered VPC but create stage-specific subnets when no stack subnets exist', async () => {
|
|
245
274
|
const appDefinition = {
|
|
246
275
|
vpc: {
|
|
247
276
|
enable: true,
|
|
@@ -251,15 +280,13 @@ describe('VpcBuilder', () => {
|
|
|
251
280
|
|
|
252
281
|
const discoveredResources = {
|
|
253
282
|
defaultVpcId: 'vpc-discovered',
|
|
254
|
-
//
|
|
255
|
-
privateSubnetId1: 'subnet-private1',
|
|
256
|
-
privateSubnetId2: 'subnet-private2',
|
|
283
|
+
// No stack-managed subnets, so create new ones for stage isolation
|
|
257
284
|
defaultSecurityGroupId: 'sg-discovered',
|
|
258
285
|
};
|
|
259
286
|
|
|
260
287
|
const result = await vpcBuilder.build(appDefinition, discoveredResources);
|
|
261
288
|
|
|
262
|
-
//
|
|
289
|
+
// Should create new stage-specific subnets for isolation (prevent route table conflicts)
|
|
263
290
|
expect(result.vpcConfig.subnetIds).toEqual([
|
|
264
291
|
{ Ref: 'FriggPrivateSubnet1' },
|
|
265
292
|
{ Ref: 'FriggPrivateSubnet2' },
|
|
@@ -164,6 +164,47 @@ class CloudFormationDiscovery {
|
|
|
164
164
|
discovered.defaultKmsKeyId = PhysicalResourceId;
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
|
+
|
|
168
|
+
// Subnets
|
|
169
|
+
if (LogicalResourceId === 'FriggPrivateSubnet1' && ResourceType === 'AWS::EC2::Subnet') {
|
|
170
|
+
discovered.privateSubnetId1 = PhysicalResourceId;
|
|
171
|
+
}
|
|
172
|
+
if (LogicalResourceId === 'FriggPrivateSubnet2' && ResourceType === 'AWS::EC2::Subnet') {
|
|
173
|
+
discovered.privateSubnetId2 = PhysicalResourceId;
|
|
174
|
+
}
|
|
175
|
+
if (LogicalResourceId === 'FriggPublicSubnet' && ResourceType === 'AWS::EC2::Subnet') {
|
|
176
|
+
discovered.publicSubnetId1 = PhysicalResourceId;
|
|
177
|
+
}
|
|
178
|
+
if (LogicalResourceId === 'FriggPublicSubnet2' && ResourceType === 'AWS::EC2::Subnet') {
|
|
179
|
+
discovered.publicSubnetId2 = PhysicalResourceId;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Route Tables
|
|
183
|
+
if (LogicalResourceId === 'FriggLambdaRouteTable' && ResourceType === 'AWS::EC2::RouteTable') {
|
|
184
|
+
discovered.routeTableId = PhysicalResourceId;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// VPC Endpoint Security Group
|
|
188
|
+
if (LogicalResourceId === 'FriggVPCEndpointSecurityGroup' && ResourceType === 'AWS::EC2::SecurityGroup') {
|
|
189
|
+
discovered.vpcEndpointSecurityGroupId = PhysicalResourceId;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// VPC Endpoints
|
|
193
|
+
if (LogicalResourceId === 'FriggS3VPCEndpoint' && ResourceType === 'AWS::EC2::VPCEndpoint') {
|
|
194
|
+
discovered.s3VpcEndpointId = PhysicalResourceId;
|
|
195
|
+
}
|
|
196
|
+
if (LogicalResourceId === 'FriggDynamoDBVPCEndpoint' && ResourceType === 'AWS::EC2::VPCEndpoint') {
|
|
197
|
+
discovered.dynamoDbVpcEndpointId = PhysicalResourceId;
|
|
198
|
+
}
|
|
199
|
+
if (LogicalResourceId === 'FriggKMSVPCEndpoint' && ResourceType === 'AWS::EC2::VPCEndpoint') {
|
|
200
|
+
discovered.kmsVpcEndpointId = PhysicalResourceId;
|
|
201
|
+
}
|
|
202
|
+
if (LogicalResourceId === 'FriggSecretsManagerVPCEndpoint' && ResourceType === 'AWS::EC2::VPCEndpoint') {
|
|
203
|
+
discovered.secretsManagerVpcEndpointId = PhysicalResourceId;
|
|
204
|
+
}
|
|
205
|
+
if (LogicalResourceId === 'FriggSQSVPCEndpoint' && ResourceType === 'AWS::EC2::VPCEndpoint') {
|
|
206
|
+
discovered.sqsVpcEndpointId = PhysicalResourceId;
|
|
207
|
+
}
|
|
167
208
|
}
|
|
168
209
|
}
|
|
169
210
|
}
|
|
@@ -73,6 +73,50 @@ describe('CloudFormationDiscovery', () => {
|
|
|
73
73
|
});
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
+
it('should extract VPC subnets from stack resources', async () => {
|
|
77
|
+
const mockStack = { StackName: 'test-stack', Outputs: [] };
|
|
78
|
+
const mockResources = [
|
|
79
|
+
{ LogicalResourceId: 'FriggPrivateSubnet1', PhysicalResourceId: 'subnet-priv-1', ResourceType: 'AWS::EC2::Subnet' },
|
|
80
|
+
{ LogicalResourceId: 'FriggPrivateSubnet2', PhysicalResourceId: 'subnet-priv-2', ResourceType: 'AWS::EC2::Subnet' },
|
|
81
|
+
{ LogicalResourceId: 'FriggPublicSubnet', PhysicalResourceId: 'subnet-pub-1', ResourceType: 'AWS::EC2::Subnet' },
|
|
82
|
+
{ LogicalResourceId: 'FriggPublicSubnet2', PhysicalResourceId: 'subnet-pub-2', ResourceType: 'AWS::EC2::Subnet' },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
86
|
+
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
87
|
+
|
|
88
|
+
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
89
|
+
|
|
90
|
+
expect(result.privateSubnetId1).toBe('subnet-priv-1');
|
|
91
|
+
expect(result.privateSubnetId2).toBe('subnet-priv-2');
|
|
92
|
+
expect(result.publicSubnetId1).toBe('subnet-pub-1');
|
|
93
|
+
expect(result.publicSubnetId2).toBe('subnet-pub-2');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should extract route tables and VPC endpoints from stack resources', async () => {
|
|
97
|
+
const mockStack = { StackName: 'test-stack', Outputs: [] };
|
|
98
|
+
const mockResources = [
|
|
99
|
+
{ LogicalResourceId: 'FriggLambdaRouteTable', PhysicalResourceId: 'rtb-123', ResourceType: 'AWS::EC2::RouteTable' },
|
|
100
|
+
{ LogicalResourceId: 'FriggVPCEndpointSecurityGroup', PhysicalResourceId: 'sg-vpce-123', ResourceType: 'AWS::EC2::SecurityGroup' },
|
|
101
|
+
{ LogicalResourceId: 'FriggS3VPCEndpoint', PhysicalResourceId: 'vpce-s3-123', ResourceType: 'AWS::EC2::VPCEndpoint' },
|
|
102
|
+
{ LogicalResourceId: 'FriggDynamoDBVPCEndpoint', PhysicalResourceId: 'vpce-ddb-123', ResourceType: 'AWS::EC2::VPCEndpoint' },
|
|
103
|
+
{ LogicalResourceId: 'FriggKMSVPCEndpoint', PhysicalResourceId: 'vpce-kms-123', ResourceType: 'AWS::EC2::VPCEndpoint' },
|
|
104
|
+
{ LogicalResourceId: 'FriggSecretsManagerVPCEndpoint', PhysicalResourceId: 'vpce-sm-123', ResourceType: 'AWS::EC2::VPCEndpoint' },
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
108
|
+
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
109
|
+
|
|
110
|
+
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
111
|
+
|
|
112
|
+
expect(result.routeTableId).toBe('rtb-123');
|
|
113
|
+
expect(result.vpcEndpointSecurityGroupId).toBe('sg-vpce-123');
|
|
114
|
+
expect(result.s3VpcEndpointId).toBe('vpce-s3-123');
|
|
115
|
+
expect(result.dynamoDbVpcEndpointId).toBe('vpce-ddb-123');
|
|
116
|
+
expect(result.kmsVpcEndpointId).toBe('vpce-kms-123');
|
|
117
|
+
expect(result.secretsManagerVpcEndpointId).toBe('vpce-sm-123');
|
|
118
|
+
});
|
|
119
|
+
|
|
76
120
|
it('should extract Aurora cluster from stack resources', async () => {
|
|
77
121
|
const mockStack = {
|
|
78
122
|
StackName: 'test-stack',
|
|
@@ -97,6 +141,46 @@ describe('CloudFormationDiscovery', () => {
|
|
|
97
141
|
});
|
|
98
142
|
});
|
|
99
143
|
|
|
144
|
+
it('should extract subnets from stack resources', async () => {
|
|
145
|
+
const mockStack = {
|
|
146
|
+
StackName: 'test-stack',
|
|
147
|
+
Outputs: [],
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const mockResources = [
|
|
151
|
+
{
|
|
152
|
+
LogicalResourceId: 'FriggPrivateSubnet1',
|
|
153
|
+
PhysicalResourceId: 'subnet-private-1',
|
|
154
|
+
ResourceType: 'AWS::EC2::Subnet',
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
LogicalResourceId: 'FriggPrivateSubnet2',
|
|
158
|
+
PhysicalResourceId: 'subnet-private-2',
|
|
159
|
+
ResourceType: 'AWS::EC2::Subnet',
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
LogicalResourceId: 'FriggPublicSubnet',
|
|
163
|
+
PhysicalResourceId: 'subnet-public-1',
|
|
164
|
+
ResourceType: 'AWS::EC2::Subnet',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
LogicalResourceId: 'FriggPublicSubnet2',
|
|
168
|
+
PhysicalResourceId: 'subnet-public-2',
|
|
169
|
+
ResourceType: 'AWS::EC2::Subnet',
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
174
|
+
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
175
|
+
|
|
176
|
+
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
177
|
+
|
|
178
|
+
expect(result.privateSubnetId1).toBe('subnet-private-1');
|
|
179
|
+
expect(result.privateSubnetId2).toBe('subnet-private-2');
|
|
180
|
+
expect(result.publicSubnetId1).toBe('subnet-public-1');
|
|
181
|
+
expect(result.publicSubnetId2).toBe('subnet-public-2');
|
|
182
|
+
});
|
|
183
|
+
|
|
100
184
|
it('should extract S3 migration bucket from stack resources', async () => {
|
|
101
185
|
const mockStack = {
|
|
102
186
|
StackName: 'test-stack',
|
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.4066059.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.4066059.0",
|
|
15
|
+
"@friggframework/test": "2.0.0--canary.461.4066059.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.4066059.0",
|
|
38
|
+
"@friggframework/prettier-config": "2.0.0--canary.461.4066059.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": "4066059afecf11231ca7ff94cc4483b47f5b6e74"
|
|
74
74
|
}
|