@friggframework/devtools 2.0.0--canary.461.77c8d12.0 → 2.0.0--canary.461.5ebf96a.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 +38 -1
- package/infrastructure/domains/shared/cloudformation-discovery.js +37 -8
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +24 -0
- package/infrastructure/domains/shared/resource-discovery.js +15 -8
- package/package.json +6 -6
|
@@ -233,7 +233,14 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
233
233
|
existingEndpoints.kms || existingEndpoints.secretsManager || existingEndpoints.sqs;
|
|
234
234
|
|
|
235
235
|
if (appDefinition.vpc.enableVPCEndpoints !== false) {
|
|
236
|
-
if
|
|
236
|
+
// Check if resources came from CloudFormation stack
|
|
237
|
+
const fromCfStack = discoveredResources.fromCloudFormationStack === true;
|
|
238
|
+
const existingLogicalIds = discoveredResources.existingLogicalIds || [];
|
|
239
|
+
|
|
240
|
+
if (fromCfStack && existingLogicalIds.length > 0 && allEndpointsExist) {
|
|
241
|
+
console.log(' All VPC endpoints exist in CloudFormation stack - skipping creation');
|
|
242
|
+
// Skip VPC endpoint creation entirely
|
|
243
|
+
} else if (vpcManagement === 'create-new') {
|
|
237
244
|
// Always create in create-new mode
|
|
238
245
|
this.buildVpcEndpoints(appDefinition, discoveredResources, result, existingEndpoints);
|
|
239
246
|
} else if (vpcManagement === 'discover') {
|
|
@@ -418,6 +425,27 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
418
425
|
|
|
419
426
|
result.vpcId = discoveredResources.defaultVpcId;
|
|
420
427
|
|
|
428
|
+
// Check if resources came from CloudFormation stack
|
|
429
|
+
const fromCfStack = discoveredResources.fromCloudFormationStack === true;
|
|
430
|
+
const existingLogicalIds = discoveredResources.existingLogicalIds || [];
|
|
431
|
+
|
|
432
|
+
if (fromCfStack && existingLogicalIds.length > 0) {
|
|
433
|
+
console.log(` ✓ VPC discovered from CloudFormation stack: ${discoveredResources.stackName}`);
|
|
434
|
+
console.log(` ✓ Found ${existingLogicalIds.length} existing resources in stack`);
|
|
435
|
+
console.log(' ℹ Skipping resource creation - will reuse existing CloudFormation resources');
|
|
436
|
+
|
|
437
|
+
// Set security group IDs from discovered resources
|
|
438
|
+
if (discoveredResources.securityGroupId) {
|
|
439
|
+
result.vpcConfig.securityGroupIds = [discoveredResources.securityGroupId];
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Don't create any new resources - they already exist in the CF stack
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// VPC discovered from AWS API (not from CF stack) - create needed resources
|
|
447
|
+
console.log(' ℹ VPC discovered from AWS API - will create Lambda security group');
|
|
448
|
+
|
|
421
449
|
// Create a Lambda security group in the discovered VPC
|
|
422
450
|
// This is needed even in discover mode so other resources can reference it
|
|
423
451
|
result.resources.FriggLambdaSecurityGroup = {
|
|
@@ -695,6 +723,15 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
695
723
|
|
|
696
724
|
console.log(` NAT Gateway Management: ${natManagement}`);
|
|
697
725
|
|
|
726
|
+
// Check if resources came from CloudFormation stack
|
|
727
|
+
const fromCfStack = discoveredResources.fromCloudFormationStack === true;
|
|
728
|
+
const existingLogicalIds = discoveredResources.existingLogicalIds || [];
|
|
729
|
+
|
|
730
|
+
if (fromCfStack && existingLogicalIds.length > 0) {
|
|
731
|
+
console.log(' Skipping NAT Gateway - will reuse from CloudFormation stack');
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
|
|
698
735
|
// Check if we should create NAT Gateway
|
|
699
736
|
const needsNatGateway = natManagement === 'createAndManage' ||
|
|
700
737
|
discoveredResources.needsNewNatGateway === true;
|
|
@@ -22,7 +22,7 @@ class CloudFormationDiscovery {
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Discover resources from an existing CloudFormation stack
|
|
25
|
-
*
|
|
25
|
+
*
|
|
26
26
|
* @param {string} stackName - Name of the CloudFormation stack
|
|
27
27
|
* @returns {Promise<Object|null>} Discovered resources or null if stack doesn't exist
|
|
28
28
|
*/
|
|
@@ -35,7 +35,12 @@ class CloudFormationDiscovery {
|
|
|
35
35
|
const resources = await this.provider.listStackResources(stackName);
|
|
36
36
|
|
|
37
37
|
// Extract discovered resources from outputs and resources
|
|
38
|
-
const discovered = {
|
|
38
|
+
const discovered = {
|
|
39
|
+
// Metadata to indicate resources came from CloudFormation stack
|
|
40
|
+
fromCloudFormationStack: true,
|
|
41
|
+
stackName: stackName,
|
|
42
|
+
existingLogicalIds: []
|
|
43
|
+
};
|
|
39
44
|
|
|
40
45
|
// Extract from outputs
|
|
41
46
|
if (stack.Outputs && stack.Outputs.length > 0) {
|
|
@@ -46,6 +51,11 @@ class CloudFormationDiscovery {
|
|
|
46
51
|
// Always call this even if resources is empty, as it may query AWS for resources
|
|
47
52
|
await this._extractFromResources(resources || [], discovered);
|
|
48
53
|
|
|
54
|
+
// Clean up metadata if no resources were discovered
|
|
55
|
+
if (discovered.existingLogicalIds.length === 0) {
|
|
56
|
+
delete discovered.existingLogicalIds;
|
|
57
|
+
}
|
|
58
|
+
|
|
49
59
|
return discovered;
|
|
50
60
|
} catch (error) {
|
|
51
61
|
// Stack doesn't exist - return null to trigger fallback discovery
|
|
@@ -103,16 +113,26 @@ class CloudFormationDiscovery {
|
|
|
103
113
|
|
|
104
114
|
/**
|
|
105
115
|
* Extract discovered resources from CloudFormation stack resources
|
|
106
|
-
*
|
|
116
|
+
*
|
|
107
117
|
* @private
|
|
108
118
|
* @param {Array} resources - CloudFormation stack resources
|
|
109
119
|
* @param {Object} discovered - Object to populate with discovered resources
|
|
110
120
|
*/
|
|
111
121
|
async _extractFromResources(resources, discovered) {
|
|
112
122
|
console.log(` DEBUG: Processing ${resources.length} CloudFormation resources...`);
|
|
123
|
+
|
|
124
|
+
// Initialize existingLogicalIds array if not present
|
|
125
|
+
if (!discovered.existingLogicalIds) {
|
|
126
|
+
discovered.existingLogicalIds = [];
|
|
127
|
+
}
|
|
113
128
|
for (const resource of resources) {
|
|
114
129
|
const { LogicalResourceId, PhysicalResourceId, ResourceType } = resource;
|
|
115
130
|
|
|
131
|
+
// Track Frigg-managed resources by logical ID
|
|
132
|
+
if (LogicalResourceId.startsWith('Frigg') || LogicalResourceId.includes('Migration')) {
|
|
133
|
+
discovered.existingLogicalIds.push(LogicalResourceId);
|
|
134
|
+
}
|
|
135
|
+
|
|
116
136
|
// Debug Aurora detection
|
|
117
137
|
if (LogicalResourceId.includes('Aurora')) {
|
|
118
138
|
console.log(` DEBUG: Found Aurora resource: ${LogicalResourceId} (${ResourceType})`);
|
|
@@ -122,18 +142,21 @@ class CloudFormationDiscovery {
|
|
|
122
142
|
if (LogicalResourceId === 'FriggLambdaSecurityGroup' && ResourceType === 'AWS::EC2::SecurityGroup') {
|
|
123
143
|
discovered.securityGroupId = PhysicalResourceId;
|
|
124
144
|
console.log(` ✓ Found security group in stack: ${PhysicalResourceId}`);
|
|
125
|
-
|
|
126
|
-
|
|
145
|
+
|
|
146
|
+
// Query security group to get VPC ID (required because SG resource doesn't include VPC ID)
|
|
147
|
+
if (this.provider && this.provider.getEC2Client && !discovered.defaultVpcId) {
|
|
127
148
|
try {
|
|
128
|
-
console.log(` Querying
|
|
149
|
+
console.log(` Querying EC2 to get VPC ID from security group...`);
|
|
129
150
|
const { DescribeSecurityGroupsCommand } = require('@aws-sdk/client-ec2');
|
|
130
|
-
const
|
|
151
|
+
const ec2Client = this.provider.getEC2Client();
|
|
152
|
+
const sgDetails = await ec2Client.send(
|
|
131
153
|
new DescribeSecurityGroupsCommand({
|
|
132
154
|
GroupIds: [PhysicalResourceId]
|
|
133
155
|
})
|
|
134
156
|
);
|
|
157
|
+
|
|
135
158
|
if (sgDetails.SecurityGroups && sgDetails.SecurityGroups.length > 0) {
|
|
136
|
-
discovered.defaultVpcId = sgDetails.SecurityGroups[0].VpcId;
|
|
159
|
+
discovered.defaultVpcId = sgDetails.SecurityGroups[0].VpcId;
|
|
137
160
|
console.log(` ✓ Extracted VPC ID from security group: ${discovered.defaultVpcId}`);
|
|
138
161
|
} else {
|
|
139
162
|
console.warn(` ⚠️ Security group query returned no results`);
|
|
@@ -193,6 +216,12 @@ class CloudFormationDiscovery {
|
|
|
193
216
|
discovered.natGatewayId = PhysicalResourceId;
|
|
194
217
|
}
|
|
195
218
|
|
|
219
|
+
// VPC - direct extraction (primary method)
|
|
220
|
+
if (LogicalResourceId === 'FriggVPC' && ResourceType === 'AWS::EC2::VPC') {
|
|
221
|
+
discovered.defaultVpcId = PhysicalResourceId;
|
|
222
|
+
console.log(` ✓ Found VPC in stack: ${PhysicalResourceId}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
196
225
|
// KMS Key (alternative to output)
|
|
197
226
|
if (LogicalResourceId === 'FriggKMSKey' && ResourceType === 'AWS::KMS::Key') {
|
|
198
227
|
// Note: For KMS, we prefer the ARN from outputs, but this is a fallback
|
|
@@ -253,6 +253,30 @@ describe('CloudFormationDiscovery', () => {
|
|
|
253
253
|
});
|
|
254
254
|
});
|
|
255
255
|
|
|
256
|
+
it('should extract VPC directly from stack resources', async () => {
|
|
257
|
+
const mockStack = {
|
|
258
|
+
StackName: 'test-stack',
|
|
259
|
+
Outputs: [],
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const mockResources = [
|
|
263
|
+
{
|
|
264
|
+
LogicalResourceId: 'FriggVPC',
|
|
265
|
+
PhysicalResourceId: 'vpc-037ec55fe87aec1e7',
|
|
266
|
+
ResourceType: 'AWS::EC2::VPC',
|
|
267
|
+
},
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
271
|
+
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
272
|
+
|
|
273
|
+
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
274
|
+
|
|
275
|
+
expect(result).toEqual({
|
|
276
|
+
defaultVpcId: 'vpc-037ec55fe87aec1e7',
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
256
280
|
it('should combine outputs and resources correctly', async () => {
|
|
257
281
|
const mockStack = {
|
|
258
282
|
StackName: 'test-stack',
|
|
@@ -85,19 +85,26 @@ async function gatherDiscoveredResources(appDefinition) {
|
|
|
85
85
|
const hasAuroraData = stackResources?.auroraClusterId;
|
|
86
86
|
const hasSomeUsefulData = hasVpcData || hasKmsData || hasAuroraData;
|
|
87
87
|
|
|
88
|
+
// Check if we're in isolated mode (each stage gets its own VPC/Aurora)
|
|
89
|
+
const isIsolatedMode = appDefinition.managementMode === 'managed' &&
|
|
90
|
+
appDefinition.vpcIsolation === 'isolated';
|
|
91
|
+
|
|
88
92
|
if (stackResources && hasSomeUsefulData) {
|
|
89
93
|
console.log(' ✓ Discovered resources from existing CloudFormation stack');
|
|
90
94
|
console.log('✅ Cloud resource discovery completed successfully!');
|
|
91
95
|
return stackResources;
|
|
92
96
|
}
|
|
93
97
|
|
|
94
|
-
// In isolated mode,
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
// In isolated mode, NEVER fall back to AWS discovery for VPC/Aurora
|
|
99
|
+
// These resources must be isolated per stage, so we either:
|
|
100
|
+
// 1. Use resources from THIS stage's CloudFormation stack (handled above)
|
|
101
|
+
// 2. Return empty to CREATE fresh isolated resources for this stage
|
|
102
|
+
if (isIsolatedMode) {
|
|
103
|
+
console.log(' ℹ Isolated mode: No CloudFormation stack or no VPC/Aurora in stack');
|
|
104
|
+
console.log(' ℹ Will create fresh isolated VPC/Aurora for this stage');
|
|
105
|
+
console.log(' ℹ Checking for shared KMS key...');
|
|
98
106
|
|
|
99
|
-
//
|
|
100
|
-
// Pass serviceName and stage to search for stage-specific alias
|
|
107
|
+
// KMS keys CAN be shared across stages (encryption keys are safe to reuse)
|
|
101
108
|
const kmsDiscovery = new KmsDiscovery(provider);
|
|
102
109
|
const kmsConfig = {
|
|
103
110
|
serviceName: appDefinition.name || 'create-frigg-app',
|
|
@@ -108,12 +115,12 @@ async function gatherDiscoveredResources(appDefinition) {
|
|
|
108
115
|
|
|
109
116
|
if (kmsResult?.defaultKmsKeyId) {
|
|
110
117
|
console.log(' ✓ Found shared KMS key (can be reused across stages)');
|
|
111
|
-
console.log('✅ Cloud resource discovery completed
|
|
118
|
+
console.log('✅ Cloud resource discovery completed - will create isolated VPC/Aurora!');
|
|
112
119
|
return kmsResult;
|
|
113
120
|
}
|
|
114
121
|
|
|
115
122
|
console.log(' ℹ No existing KMS key found - will create new one');
|
|
116
|
-
console.log('✅ Cloud resource discovery completed
|
|
123
|
+
console.log('✅ Cloud resource discovery completed - will create fresh isolated resources!');
|
|
117
124
|
return {};
|
|
118
125
|
}
|
|
119
126
|
|
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.5ebf96a.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.5ebf96a.0",
|
|
15
|
+
"@friggframework/test": "2.0.0--canary.461.5ebf96a.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.5ebf96a.0",
|
|
38
|
+
"@friggframework/prettier-config": "2.0.0--canary.461.5ebf96a.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": "5ebf96a4b08d18a165c8451176b92705a1dcbb4f"
|
|
74
74
|
}
|