@friggframework/devtools 2.0.0--canary.474.86c5119.0 → 2.0.0--canary.474.898a56c.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/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +762 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +154 -1
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +20 -5
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +3 -3
- package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
- package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
- package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
- package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
- package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
- package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
- package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
- package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +645 -0
- package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.js +174 -91
- package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +332 -228
- package/infrastructure/domains/health/domain/services/logical-id-mapper.js +330 -0
- package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
- package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +108 -14
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +69 -12
- package/package.json +6 -6
|
@@ -12,8 +12,12 @@
|
|
|
12
12
|
* - Generate CloudFormation template snippets
|
|
13
13
|
* - Execute import operations (single or batch)
|
|
14
14
|
* - Track import operation status
|
|
15
|
+
* - Map orphaned resources to correct logical IDs using template comparison
|
|
15
16
|
*/
|
|
16
17
|
|
|
18
|
+
const { TemplateParser } = require('../../domain/services/template-parser');
|
|
19
|
+
const { LogicalIdMapper } = require('../../domain/services/logical-id-mapper');
|
|
20
|
+
|
|
17
21
|
class RepairViaImportUseCase {
|
|
18
22
|
/**
|
|
19
23
|
* Create use case with required dependencies
|
|
@@ -21,8 +25,11 @@ class RepairViaImportUseCase {
|
|
|
21
25
|
* @param {Object} params
|
|
22
26
|
* @param {IResourceImporter} params.resourceImporter - Resource import operations
|
|
23
27
|
* @param {IResourceDetector} params.resourceDetector - Resource discovery and details
|
|
28
|
+
* @param {IStackRepository} params.stackRepository - CloudFormation stack operations
|
|
29
|
+
* @param {TemplateParser} params.templateParser - CloudFormation template parsing
|
|
30
|
+
* @param {LogicalIdMapper} params.logicalIdMapper - Logical ID mapping service
|
|
24
31
|
*/
|
|
25
|
-
constructor({ resourceImporter, resourceDetector }) {
|
|
32
|
+
constructor({ resourceImporter, resourceDetector, stackRepository, templateParser, logicalIdMapper }) {
|
|
26
33
|
if (!resourceImporter) {
|
|
27
34
|
throw new Error('resourceImporter is required');
|
|
28
35
|
}
|
|
@@ -32,6 +39,9 @@ class RepairViaImportUseCase {
|
|
|
32
39
|
|
|
33
40
|
this.resourceImporter = resourceImporter;
|
|
34
41
|
this.resourceDetector = resourceDetector;
|
|
42
|
+
this.stackRepository = stackRepository;
|
|
43
|
+
this.templateParser = templateParser || new TemplateParser();
|
|
44
|
+
this.logicalIdMapper = logicalIdMapper || new LogicalIdMapper({ region: 'us-east-1' });
|
|
35
45
|
}
|
|
36
46
|
|
|
37
47
|
/**
|
|
@@ -224,6 +234,149 @@ class RepairViaImportUseCase {
|
|
|
224
234
|
properties: resourceDetails.properties,
|
|
225
235
|
};
|
|
226
236
|
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Import orphaned resources with automatic logical ID mapping
|
|
240
|
+
* Uses template comparison to find correct logical IDs
|
|
241
|
+
*
|
|
242
|
+
* @param {Object} params
|
|
243
|
+
* @param {StackIdentifier} params.stackIdentifier - Target stack
|
|
244
|
+
* @param {Array} params.orphanedResources - Orphaned resources to import
|
|
245
|
+
* @param {string} params.buildTemplatePath - Path to .serverless/cloudformation-template-update-stack.json
|
|
246
|
+
* @returns {Promise<Object>} Import result with mappings
|
|
247
|
+
*/
|
|
248
|
+
async importWithLogicalIdMapping({ stackIdentifier, orphanedResources, buildTemplatePath }) {
|
|
249
|
+
// 1. Validate build template exists
|
|
250
|
+
if (!buildTemplatePath) {
|
|
251
|
+
throw new Error('buildTemplatePath is required');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const fs = require('fs');
|
|
255
|
+
if (!fs.existsSync(buildTemplatePath)) {
|
|
256
|
+
throw new Error(
|
|
257
|
+
`Build template not found at: ${buildTemplatePath}\n\n` +
|
|
258
|
+
`Please run one of:\n` +
|
|
259
|
+
` • serverless package\n` +
|
|
260
|
+
` • frigg build\n` +
|
|
261
|
+
` • frigg deploy --stage dev\n\n` +
|
|
262
|
+
`Then try again:\n` +
|
|
263
|
+
` frigg repair --import ${stackIdentifier.stackName}`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 2. Parse build template
|
|
268
|
+
const buildTemplate = this.templateParser.parseTemplate(buildTemplatePath);
|
|
269
|
+
|
|
270
|
+
// 3. Get deployed template from CloudFormation
|
|
271
|
+
if (!this.stackRepository) {
|
|
272
|
+
throw new Error('stackRepository is required for template comparison');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const deployedTemplate = await this.stackRepository.getTemplate(stackIdentifier);
|
|
276
|
+
|
|
277
|
+
// 4. Map orphaned resources to logical IDs
|
|
278
|
+
const mappings = await this.logicalIdMapper.mapOrphanedResourcesToLogicalIds({
|
|
279
|
+
orphanedResources,
|
|
280
|
+
buildTemplate,
|
|
281
|
+
deployedTemplate,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// 5. Check for multiple resources of same type
|
|
285
|
+
const multiResourceWarnings = this._checkForMultipleResources(mappings);
|
|
286
|
+
|
|
287
|
+
// 6. Filter out unmapped resources and prepare for import
|
|
288
|
+
const mappedResources = mappings.filter((m) => m.logicalId !== null);
|
|
289
|
+
const unmappedResources = mappings.filter((m) => m.logicalId === null);
|
|
290
|
+
|
|
291
|
+
if (mappedResources.length === 0) {
|
|
292
|
+
return {
|
|
293
|
+
success: false,
|
|
294
|
+
message: 'No resources could be mapped to logical IDs',
|
|
295
|
+
unmappedCount: unmappedResources.length,
|
|
296
|
+
unmappedResources,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 7. Generate import-resources.json format
|
|
301
|
+
const resourcesToImport = mappedResources.map((mapping) => ({
|
|
302
|
+
ResourceType: mapping.resourceType,
|
|
303
|
+
LogicalResourceId: mapping.logicalId,
|
|
304
|
+
ResourceIdentifier: this._getResourceIdentifier(mapping),
|
|
305
|
+
}));
|
|
306
|
+
|
|
307
|
+
// 8. Return result with warnings for user review
|
|
308
|
+
return {
|
|
309
|
+
success: true,
|
|
310
|
+
mappedCount: mappedResources.length,
|
|
311
|
+
unmappedCount: unmappedResources.length,
|
|
312
|
+
mappings: mappedResources,
|
|
313
|
+
unmappedResources,
|
|
314
|
+
resourcesToImport,
|
|
315
|
+
warnings: multiResourceWarnings,
|
|
316
|
+
buildTemplatePath,
|
|
317
|
+
deployedTemplatePath: 'CloudFormation (deployed)',
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Check for multiple resources of same type
|
|
323
|
+
* Returns warnings when user needs to manually select
|
|
324
|
+
* @private
|
|
325
|
+
*/
|
|
326
|
+
_checkForMultipleResources(mappings) {
|
|
327
|
+
const warnings = [];
|
|
328
|
+
const byType = {};
|
|
329
|
+
|
|
330
|
+
// Group by resource type
|
|
331
|
+
mappings.forEach((mapping) => {
|
|
332
|
+
if (!byType[mapping.resourceType]) {
|
|
333
|
+
byType[mapping.resourceType] = [];
|
|
334
|
+
}
|
|
335
|
+
byType[mapping.resourceType].push(mapping);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Check for multiples
|
|
339
|
+
Object.entries(byType).forEach(([type, resources]) => {
|
|
340
|
+
if (resources.length > 1) {
|
|
341
|
+
const shortType = type.replace('AWS::EC2::', '');
|
|
342
|
+
warnings.push({
|
|
343
|
+
type: 'MULTIPLE_RESOURCES',
|
|
344
|
+
resourceType: type,
|
|
345
|
+
count: resources.length,
|
|
346
|
+
message: `Multiple ${shortType}s detected (${resources.length}). Review relationships before importing.`,
|
|
347
|
+
resources: resources.map((r) => ({
|
|
348
|
+
physicalId: r.physicalId,
|
|
349
|
+
logicalId: r.logicalId,
|
|
350
|
+
matchMethod: r.matchMethod,
|
|
351
|
+
confidence: r.confidence,
|
|
352
|
+
})),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
return warnings;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Get CloudFormation resource identifier for import
|
|
362
|
+
* @private
|
|
363
|
+
*/
|
|
364
|
+
_getResourceIdentifier(mapping) {
|
|
365
|
+
const { resourceType, physicalId } = mapping;
|
|
366
|
+
|
|
367
|
+
// Map resource types to their identifier format
|
|
368
|
+
const identifierMap = {
|
|
369
|
+
'AWS::EC2::VPC': { VpcId: physicalId },
|
|
370
|
+
'AWS::EC2::Subnet': { SubnetId: physicalId },
|
|
371
|
+
'AWS::EC2::SecurityGroup': { GroupId: physicalId },
|
|
372
|
+
'AWS::EC2::InternetGateway': { InternetGatewayId: physicalId },
|
|
373
|
+
'AWS::EC2::NatGateway': { NatGatewayId: physicalId },
|
|
374
|
+
'AWS::EC2::RouteTable': { RouteTableId: physicalId },
|
|
375
|
+
'AWS::EC2::VPCEndpoint': { VpcEndpointId: physicalId },
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
return identifierMap[resourceType] || { Id: physicalId };
|
|
379
|
+
}
|
|
227
380
|
}
|
|
228
381
|
|
|
229
382
|
module.exports = RepairViaImportUseCase;
|
|
@@ -55,16 +55,27 @@ class RunHealthCheckUseCase {
|
|
|
55
55
|
*
|
|
56
56
|
* @param {Object} params
|
|
57
57
|
* @param {StackIdentifier} params.stackIdentifier - Stack to check
|
|
58
|
+
* @param {Function} params.onProgress - Optional progress callback (step, message)
|
|
58
59
|
* @returns {Promise<StackHealthReport>} Comprehensive health report
|
|
59
60
|
*/
|
|
60
|
-
async execute({ stackIdentifier }) {
|
|
61
|
+
async execute({ stackIdentifier, onProgress }) {
|
|
62
|
+
// Helper to call progress callback if provided
|
|
63
|
+
const progress = (step, message) => {
|
|
64
|
+
if (onProgress) {
|
|
65
|
+
onProgress(step, message);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
61
69
|
// 1. Verify stack exists
|
|
70
|
+
progress('📋 Step 1/5:', 'Verifying stack exists...');
|
|
62
71
|
await this.stackRepository.getStack(stackIdentifier);
|
|
63
72
|
|
|
64
73
|
// 2. Detect stack-level drift
|
|
74
|
+
progress('🔍 Step 2/5:', 'Detecting stack drift...');
|
|
65
75
|
const driftDetection = await this.stackRepository.detectStackDrift(stackIdentifier);
|
|
66
76
|
|
|
67
77
|
// 3. Get all stack resources
|
|
78
|
+
progress('📊 Step 3/5:', 'Analyzing stack resources...');
|
|
68
79
|
const stackResources = await this.stackRepository.listResources(stackIdentifier);
|
|
69
80
|
|
|
70
81
|
// 4. Build resource entities with drift status
|
|
@@ -101,10 +112,12 @@ class RunHealthCheckUseCase {
|
|
|
101
112
|
resourceDrift.propertyDifferences &&
|
|
102
113
|
resourceDrift.propertyDifferences.length > 0
|
|
103
114
|
) {
|
|
104
|
-
const propertyMismatches = this.mismatchAnalyzer.
|
|
105
|
-
resourceDrift.
|
|
106
|
-
|
|
107
|
-
|
|
115
|
+
const propertyMismatches = this.mismatchAnalyzer.analyze({
|
|
116
|
+
expected: resourceDrift.expectedProperties,
|
|
117
|
+
actual: resourceDrift.actualProperties,
|
|
118
|
+
propertyMutability: {},
|
|
119
|
+
ignoreProperties: [],
|
|
120
|
+
});
|
|
108
121
|
|
|
109
122
|
// Create issue for each property mismatch using factory method
|
|
110
123
|
for (const mismatch of propertyMismatches) {
|
|
@@ -135,6 +148,7 @@ class RunHealthCheckUseCase {
|
|
|
135
148
|
}
|
|
136
149
|
|
|
137
150
|
// 5. Find orphaned resources (exist in cloud but not in stack)
|
|
151
|
+
progress('🔎 Step 4/5:', 'Checking for orphaned resources...');
|
|
138
152
|
const orphanedResources = await this.resourceDetector.findOrphanedResources({
|
|
139
153
|
stackIdentifier,
|
|
140
154
|
stackResources,
|
|
@@ -162,6 +176,7 @@ class RunHealthCheckUseCase {
|
|
|
162
176
|
}
|
|
163
177
|
|
|
164
178
|
// 6. Calculate health score using domain service
|
|
179
|
+
progress('🧮 Step 5/5:', 'Calculating health score...');
|
|
165
180
|
const healthScore = this.healthScoreCalculator.calculate({ resources, issues });
|
|
166
181
|
|
|
167
182
|
// 7. Build comprehensive health report (aggregate root)
|
package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js
CHANGED
|
@@ -33,7 +33,7 @@ describe('RunHealthCheckUseCase', () => {
|
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
mockMismatchAnalyzer = {
|
|
36
|
-
|
|
36
|
+
analyze: jest.fn(),
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
mockHealthScoreCalculator = {
|
|
@@ -149,7 +149,7 @@ describe('RunHealthCheckUseCase', () => {
|
|
|
149
149
|
mutability: PropertyMutability.MUTABLE,
|
|
150
150
|
});
|
|
151
151
|
|
|
152
|
-
mockMismatchAnalyzer.
|
|
152
|
+
mockMismatchAnalyzer.analyze.mockReturnValue([propertyMismatch]);
|
|
153
153
|
|
|
154
154
|
mockResourceDetector.findOrphanedResources.mockResolvedValue([]);
|
|
155
155
|
|
|
@@ -323,7 +323,7 @@ describe('RunHealthCheckUseCase', () => {
|
|
|
323
323
|
mutability: PropertyMutability.MUTABLE,
|
|
324
324
|
});
|
|
325
325
|
|
|
326
|
-
mockMismatchAnalyzer.
|
|
326
|
+
mockMismatchAnalyzer.analyze.mockReturnValue([propertyMismatch]);
|
|
327
327
|
|
|
328
328
|
// Mock orphaned resources
|
|
329
329
|
mockResourceDetector.findOrphanedResources.mockResolvedValue([
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# acme-integrations-dev Stack Drift Analysis
|
|
2
|
+
|
|
3
|
+
**Date**: 2025-10-27
|
|
4
|
+
**Stack**: acme-integrations-dev (us-east-1)
|
|
5
|
+
**Status**: 65/100 Health Score (degraded)
|
|
6
|
+
|
|
7
|
+
## Executive Summary
|
|
8
|
+
|
|
9
|
+
The Lambda functions were **manually moved from Frigg VPC to Default VPC**, causing:
|
|
10
|
+
- ✅ 16 orphaned resources (3 VPCs, 10 subnets, 3 security groups) - correctly detected
|
|
11
|
+
- ⚠️ 32 property mismatch warnings (VPC drift on all 16 Lambda functions)
|
|
12
|
+
|
|
13
|
+
**The template expects** Lambdas to use Frigg-managed VPC `vpc-0eadd96976d29ede7`.
|
|
14
|
+
**But Lambdas actually use** AWS default VPC `vpc-01f21101d4ed6db59`.
|
|
15
|
+
|
|
16
|
+
## Detailed Analysis
|
|
17
|
+
|
|
18
|
+
### CloudFormation Template Expectations
|
|
19
|
+
|
|
20
|
+
The template specifies Lambda functions should use:
|
|
21
|
+
|
|
22
|
+
```yaml
|
|
23
|
+
VpcConfig:
|
|
24
|
+
SecurityGroupIds:
|
|
25
|
+
- sg-07c01370e830b6ad6 # Frigg Lambda SG (in vpc-0eadd96976d29ede7)
|
|
26
|
+
SubnetIds:
|
|
27
|
+
- subnet-00ab9e0502e66aac3 # Private subnet 1 (in vpc-0eadd96976d29ede7)
|
|
28
|
+
- subnet-00d085a52937aaf91 # Private subnet 2 (in vpc-0eadd96976d29ede7)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**These resources belong to:** `vpc-0eadd96976d29ede7` (10.0.0.0/16)
|
|
32
|
+
|
|
33
|
+
### Actual Lambda Configuration
|
|
34
|
+
|
|
35
|
+
Lambda functions are actually running in:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"VpcId": "vpc-01f21101d4ed6db59", // AWS Default VPC (172.31.0.0/16)
|
|
40
|
+
"SecurityGroupIds": [
|
|
41
|
+
"sg-0aca40438d17344c4" // Default VPC security group (NOT in template)
|
|
42
|
+
],
|
|
43
|
+
"SubnetIds": [
|
|
44
|
+
"subnet-020d32e3ca398a041", // Default VPC subnet 1 (NOT in template)
|
|
45
|
+
"subnet-0c186318804aba790" // Default VPC subnet 2 (NOT in template)
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**These resources belong to:** `vpc-01f21101d4ed6db59` (172.31.0.0/16) - AWS Default VPC
|
|
51
|
+
|
|
52
|
+
### Orphaned Resources Analysis
|
|
53
|
+
|
|
54
|
+
#### 1. VPC: vpc-0eadd96976d29ede7 (10.0.0.0/16) ✅ CORRECT VPC TO IMPORT
|
|
55
|
+
|
|
56
|
+
**Status:**
|
|
57
|
+
- Has CloudFormation tags: `stack-name=acme-integrations-dev`, `logical-id=FriggVPC`
|
|
58
|
+
- NOT in CloudFormation stack (stack has 0 VPCs managed)
|
|
59
|
+
- Contains subnets that are **EXPECTED by template**:
|
|
60
|
+
- `subnet-00ab9e0502e66aac3` (10.0.0.0/24) - Expected by template ✅
|
|
61
|
+
- `subnet-00d085a52937aaf91` (10.0.1.0/24) - Expected by template ✅
|
|
62
|
+
- Contains security group that is **EXPECTED by template**:
|
|
63
|
+
- `sg-07c01370e830b6ad6` - Expected by template ✅
|
|
64
|
+
|
|
65
|
+
**Conclusion:** This is the **CORRECT VPC** that should be imported! The template expects Lambdas to use this VPC, but they were manually moved to default VPC.
|
|
66
|
+
|
|
67
|
+
**Stage Verification:** Has `STAGE=dev` tag ✅
|
|
68
|
+
|
|
69
|
+
#### 2. VPC: vpc-0e2351eac99adcb83 (10.0.0.0/16) - OLD/DUPLICATE
|
|
70
|
+
|
|
71
|
+
**Status:**
|
|
72
|
+
- Has CloudFormation tags: `stack-name=acme-integrations-dev`, `logical-id=FriggVPC`
|
|
73
|
+
- NOT in CloudFormation stack
|
|
74
|
+
- Contains orphaned subnets NOT referenced by template
|
|
75
|
+
- Same CIDR as vpc-0eadd96976d29ede7 (duplicate)
|
|
76
|
+
|
|
77
|
+
**Conclusion:** Old/duplicate VPC, should be **DELETED**
|
|
78
|
+
|
|
79
|
+
**Stage Verification:** Has `STAGE=dev` tag
|
|
80
|
+
|
|
81
|
+
#### 3. VPC: vpc-020a0365610c05f0b (10.0.0.0/16) - OLD/DUPLICATE
|
|
82
|
+
|
|
83
|
+
**Status:**
|
|
84
|
+
- Has CloudFormation tags: `stack-name=acme-integrations-dev`, `logical-id=FriggVPC`
|
|
85
|
+
- NOT in CloudFormation stack
|
|
86
|
+
- Contains orphaned subnets NOT referenced by template
|
|
87
|
+
- Same CIDR as vpc-0eadd96976d29ede7 (duplicate)
|
|
88
|
+
|
|
89
|
+
**Conclusion:** Old/duplicate VPC, should be **DELETED**
|
|
90
|
+
|
|
91
|
+
**Stage Verification:** Has `STAGE=dev` tag
|
|
92
|
+
|
|
93
|
+
## What Happened?
|
|
94
|
+
|
|
95
|
+
1. **Initial Deployment**: CloudFormation created `vpc-0eadd96976d29ede7` with subnets and security groups
|
|
96
|
+
2. **VPC Removed from Stack**: VPC was removed from CloudFormation management (but resources still exist)
|
|
97
|
+
3. **Manual Migration**: Lambda functions were manually updated to use default VPC instead
|
|
98
|
+
4. **Result**: Template expects Frigg VPC, but Lambdas use default VPC → drift
|
|
99
|
+
|
|
100
|
+
## Recommended Actions
|
|
101
|
+
|
|
102
|
+
### Option 1: Import Frigg VPC and Let CloudFormation Fix Drift (RECOMMENDED)
|
|
103
|
+
|
|
104
|
+
**Steps:**
|
|
105
|
+
|
|
106
|
+
1. **Import the correct VPC and its resources:**
|
|
107
|
+
```bash
|
|
108
|
+
# Import vpc-0eadd96976d29ede7 and its subnets/SG
|
|
109
|
+
frigg repair --import quo-integrations-dev
|
|
110
|
+
# When prompted, select ONLY vpc-0eadd96976d29ede7
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
2. **CloudFormation will automatically update Lambdas:**
|
|
114
|
+
- CloudFormation will detect the VPC/subnet/SG mismatch
|
|
115
|
+
- Next stack update will **automatically** update Lambda VPC configs
|
|
116
|
+
- Lambdas will be moved from default VPC back to Frigg VPC
|
|
117
|
+
|
|
118
|
+
3. **Delete the duplicate VPCs:**
|
|
119
|
+
```bash
|
|
120
|
+
aws ec2 delete-vpc --vpc-id vpc-0e2351eac99adcb83
|
|
121
|
+
aws ec2 delete-vpc --vpc-id vpc-020a0365610c05f0b
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**CloudFormation Behavior:**
|
|
125
|
+
- ✅ YES, CloudFormation WILL automatically update Lambda VPC configs
|
|
126
|
+
- ✅ CloudFormation will handle the migration safely (blue-green deployment)
|
|
127
|
+
- ✅ No downtime - new Lambda versions created, traffic switched over
|
|
128
|
+
|
|
129
|
+
**Benefits:**
|
|
130
|
+
- ✅ Stack returns to intended state (Lambdas in Frigg VPC)
|
|
131
|
+
- ✅ Health score improves to 100/100
|
|
132
|
+
- ✅ Proper VPC isolation restored
|
|
133
|
+
- ✅ CloudFormation manages all resources again
|
|
134
|
+
|
|
135
|
+
**Risks:**
|
|
136
|
+
- ⚠️ Lambda cold starts during VPC migration (~10-30 seconds)
|
|
137
|
+
- ⚠️ Must ensure Frigg VPC networking is configured correctly
|
|
138
|
+
|
|
139
|
+
### Option 2: Update Template to Use Default VPC (NOT RECOMMENDED)
|
|
140
|
+
|
|
141
|
+
**Steps:**
|
|
142
|
+
|
|
143
|
+
1. Update serverless.yml to remove VPC configuration
|
|
144
|
+
2. Deploy stack update
|
|
145
|
+
3. Delete all 3 orphaned Frigg VPCs
|
|
146
|
+
|
|
147
|
+
**Why NOT recommended:**
|
|
148
|
+
- ❌ Loses VPC isolation benefits
|
|
149
|
+
- ❌ Lambda functions exposed to internet (less secure)
|
|
150
|
+
- ❌ Shared default VPC across all accounts
|
|
151
|
+
- ❌ No control over networking
|
|
152
|
+
|
|
153
|
+
### Option 3: Delete All and Let CloudFormation Recreate (RISKY)
|
|
154
|
+
|
|
155
|
+
**Steps:**
|
|
156
|
+
|
|
157
|
+
1. Delete all 3 orphaned VPCs
|
|
158
|
+
2. Add VPC back to CloudFormation template
|
|
159
|
+
3. Deploy stack update
|
|
160
|
+
|
|
161
|
+
**Why RISKY:**
|
|
162
|
+
- ❌ CloudFormation will create NEW VPC with different ID
|
|
163
|
+
- ❌ Requires stack update to fix Lambda drift
|
|
164
|
+
- ❌ More disruptive than import
|
|
165
|
+
|
|
166
|
+
## Answering Your Questions
|
|
167
|
+
|
|
168
|
+
### Q1: What are the drifted properties?
|
|
169
|
+
|
|
170
|
+
**Answer:**
|
|
171
|
+
- **Expected** (from template): Subnets in `vpc-0eadd96976d29ede7` (Frigg VPC)
|
|
172
|
+
- **Actual** (in AWS): Subnets in `vpc-01f21101d4ed6db59` (default VPC)
|
|
173
|
+
- **Cause**: Lambdas were manually moved to default VPC
|
|
174
|
+
|
|
175
|
+
### Q2: Will CloudFormation migrate Lambdas if we import the VPC?
|
|
176
|
+
|
|
177
|
+
**Answer:** ✅ **YES!**
|
|
178
|
+
|
|
179
|
+
When you import `vpc-0eadd96976d29ede7` and its subnets/SG:
|
|
180
|
+
1. CloudFormation will recognize the resources exist
|
|
181
|
+
2. Next stack update will detect Lambda VPC config drift
|
|
182
|
+
3. CloudFormation will automatically update Lambda functions to use imported VPC
|
|
183
|
+
4. Migration happens safely with blue-green deployment (no downtime)
|
|
184
|
+
|
|
185
|
+
### Q3: What's the right approach?
|
|
186
|
+
|
|
187
|
+
**Answer:** **Import `vpc-0eadd96976d29ede7` and delete the other 2 VPCs**
|
|
188
|
+
|
|
189
|
+
This VPC is the one the template expects, and it contains the correct subnets/SG that match the template.
|
|
190
|
+
|
|
191
|
+
### Q4: Are these VPCs intended for -dev stage?
|
|
192
|
+
|
|
193
|
+
**Answer:** ✅ **YES, all 3 VPCs have `STAGE=dev` tags**
|
|
194
|
+
|
|
195
|
+
But only `vpc-0eadd96976d29ede7` contains the resources referenced by the template. The other 2 are duplicates/old deployments.
|
|
196
|
+
|
|
197
|
+
## Implementation Plan
|
|
198
|
+
|
|
199
|
+
1. ✅ **Verify VPC networking is correct:**
|
|
200
|
+
```bash
|
|
201
|
+
# Check route tables, NAT gateways, internet gateways
|
|
202
|
+
aws ec2 describe-route-tables --filters "Name=vpc-id,Values=vpc-0eadd96976d29ede7"
|
|
203
|
+
aws ec2 describe-nat-gateways --filter "Name=vpc-id,Values=vpc-0eadd96976d29ede7"
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
2. ✅ **Import the correct VPC:**
|
|
207
|
+
```bash
|
|
208
|
+
frigg repair --import quo-integrations-dev
|
|
209
|
+
# Select: vpc-0eadd96976d29ede7 ONLY
|
|
210
|
+
# Select: All subnets in vpc-0eadd96976d29ede7
|
|
211
|
+
# Select: sg-07c01370e830b6ad6
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
3. ✅ **Deploy stack update to fix Lambda drift:**
|
|
215
|
+
```bash
|
|
216
|
+
frigg deploy --stage dev
|
|
217
|
+
# CloudFormation will update Lambda VPC configs automatically
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
4. ✅ **Verify Lambdas migrated successfully:**
|
|
221
|
+
```bash
|
|
222
|
+
aws lambda get-function-configuration --function-name quo-integrations-dev-attio \
|
|
223
|
+
--query 'VpcConfig.{VpcId:VpcId,SubnetIds:SubnetIds}'
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
5. ✅ **Delete duplicate VPCs:**
|
|
227
|
+
```bash
|
|
228
|
+
# Delete subnets first, then VPCs
|
|
229
|
+
aws ec2 delete-vpc --vpc-id vpc-0e2351eac99adcb83
|
|
230
|
+
aws ec2 delete-vpc --vpc-id vpc-020a0365610c05f0b
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
6. ✅ **Re-run health check:**
|
|
234
|
+
```bash
|
|
235
|
+
frigg doctor quo-integrations-dev
|
|
236
|
+
# Should show 100/100 health score
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Next Steps for Relationship Analysis Implementation
|
|
240
|
+
|
|
241
|
+
Based on this real-world scenario, the relationship analysis should:
|
|
242
|
+
|
|
243
|
+
1. **Detect template-expected resources:**
|
|
244
|
+
- Parse CloudFormation template to find expected VPC config
|
|
245
|
+
- Extract subnet IDs, security group IDs from template
|
|
246
|
+
|
|
247
|
+
2. **Match orphans against expected resources:**
|
|
248
|
+
- `vpc-0eadd96976d29ede7` contains expected subnets → **HIGH priority import**
|
|
249
|
+
- Other VPCs don't contain expected resources → **LOW priority (delete)**
|
|
250
|
+
|
|
251
|
+
3. **Show recommendation:**
|
|
252
|
+
```
|
|
253
|
+
⚠ Multiple VPCs detected (3 orphaned)
|
|
254
|
+
|
|
255
|
+
Analysis:
|
|
256
|
+
1. vpc-0eadd96976d29ede7 - Contains resources expected by template [IMPORT THIS]
|
|
257
|
+
- subnet-00ab9e0502e66aac3 (expected)
|
|
258
|
+
- subnet-00d085a52937aaf91 (expected)
|
|
259
|
+
- sg-07c01370e830b6ad6 (expected)
|
|
260
|
+
|
|
261
|
+
2. vpc-0e2351eac99adcb83 - No expected resources [DELETE]
|
|
262
|
+
3. vpc-020a0365610c05f0b - No expected resources [DELETE]
|
|
263
|
+
|
|
264
|
+
Recommendation:
|
|
265
|
+
✅ Import vpc-0eadd96976d29ede7 to restore template compliance
|
|
266
|
+
❌ Delete vpc-0e2351eac99adcb83 and vpc-020a0365610c05f0b (old/unused)
|
|
267
|
+
```
|