@friggframework/devtools 2.0.0-next.44 → 2.0.0-next.46
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/ARCHITECTURE.md +487 -0
- package/infrastructure/HEALTH.md +468 -0
- package/infrastructure/README.md +51 -0
- package/infrastructure/__tests__/postgres-config.test.js +914 -0
- package/infrastructure/__tests__/template-generation.test.js +687 -0
- package/infrastructure/create-frigg-infrastructure.js +1 -1
- package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
- package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
- package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
- package/infrastructure/domains/database/aurora-builder.js +809 -0
- package/infrastructure/domains/database/aurora-builder.test.js +950 -0
- package/infrastructure/domains/database/aurora-discovery.js +87 -0
- package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
- package/infrastructure/domains/database/aurora-resolver.js +210 -0
- package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
- package/infrastructure/domains/database/migration-builder.js +633 -0
- package/infrastructure/domains/database/migration-builder.test.js +294 -0
- package/infrastructure/domains/database/migration-resolver.js +163 -0
- package/infrastructure/domains/database/migration-resolver.test.js +337 -0
- package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
- package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
- package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
- package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
- package/infrastructure/domains/health/application/ports/index.js +26 -0
- package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
- 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 +1130 -0
- package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
- package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
- package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
- package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
- 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/entities/issue.js +299 -0
- package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
- package/infrastructure/domains/health/domain/entities/resource.js +159 -0
- package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
- package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
- package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
- package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
- package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
- package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
- package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
- package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
- package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
- package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
- package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -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-property-reconciler.js +784 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
- package/infrastructure/domains/integration/integration-builder.js +397 -0
- package/infrastructure/domains/integration/integration-builder.test.js +593 -0
- package/infrastructure/domains/integration/integration-resolver.js +170 -0
- package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
- package/infrastructure/domains/integration/websocket-builder.js +69 -0
- package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
- package/infrastructure/domains/networking/vpc-builder.js +1829 -0
- package/infrastructure/domains/networking/vpc-builder.test.js +1262 -0
- package/infrastructure/domains/networking/vpc-discovery.js +177 -0
- package/infrastructure/domains/networking/vpc-discovery.test.js +350 -0
- package/infrastructure/domains/networking/vpc-resolver.js +324 -0
- package/infrastructure/domains/networking/vpc-resolver.test.js +501 -0
- package/infrastructure/domains/parameters/ssm-builder.js +79 -0
- package/infrastructure/domains/parameters/ssm-builder.test.js +189 -0
- package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
- package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
- package/infrastructure/{iam-generator.js → domains/security/iam-generator.js} +2 -2
- package/infrastructure/domains/security/kms-builder.js +366 -0
- package/infrastructure/domains/security/kms-builder.test.js +374 -0
- package/infrastructure/domains/security/kms-discovery.js +80 -0
- package/infrastructure/domains/security/kms-discovery.test.js +177 -0
- package/infrastructure/domains/security/kms-resolver.js +96 -0
- package/infrastructure/domains/security/kms-resolver.test.js +216 -0
- package/infrastructure/domains/shared/base-builder.js +112 -0
- package/infrastructure/domains/shared/base-resolver.js +186 -0
- package/infrastructure/domains/shared/base-resolver.test.js +305 -0
- package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
- package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
- package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
- package/infrastructure/domains/shared/cloudformation-discovery.js +375 -0
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +590 -0
- package/infrastructure/domains/shared/environment-builder.js +119 -0
- package/infrastructure/domains/shared/environment-builder.test.js +247 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.js +544 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +377 -0
- package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
- package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
- package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
- package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
- package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
- package/infrastructure/domains/shared/resource-discovery.js +192 -0
- package/infrastructure/domains/shared/resource-discovery.test.js +552 -0
- package/infrastructure/domains/shared/types/app-definition.js +205 -0
- package/infrastructure/domains/shared/types/discovery-result.js +106 -0
- package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
- package/infrastructure/domains/shared/types/index.js +46 -0
- package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
- package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +380 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
- package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
- package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +55 -0
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +138 -0
- package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +2 -1
- package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
- package/infrastructure/esbuild.config.js +53 -0
- package/infrastructure/infrastructure-composer.js +87 -0
- package/infrastructure/{serverless-template.test.js → infrastructure-composer.test.js} +115 -24
- package/infrastructure/scripts/build-prisma-layer.js +553 -0
- package/infrastructure/scripts/build-prisma-layer.test.js +102 -0
- package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +80 -48
- package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
- package/layers/prisma/nodejs/package.json +8 -0
- package/management-ui/server/utils/cliIntegration.js +1 -1
- package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
- package/package.json +11 -11
- package/frigg-cli/.eslintrc.js +0 -141
- package/frigg-cli/__tests__/unit/commands/build.test.js +0 -251
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +0 -548
- package/frigg-cli/__tests__/unit/commands/install.test.js +0 -400
- package/frigg-cli/__tests__/unit/commands/ui.test.js +0 -346
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +0 -366
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +0 -304
- package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +0 -486
- package/frigg-cli/__tests__/utils/mock-factory.js +0 -270
- package/frigg-cli/__tests__/utils/prisma-mock.js +0 -194
- package/frigg-cli/__tests__/utils/test-fixtures.js +0 -463
- package/frigg-cli/__tests__/utils/test-setup.js +0 -287
- package/frigg-cli/build-command/index.js +0 -65
- package/frigg-cli/db-setup-command/index.js +0 -193
- package/frigg-cli/deploy-command/index.js +0 -175
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +0 -301
- package/frigg-cli/generate-command/azure-generator.js +0 -43
- package/frigg-cli/generate-command/gcp-generator.js +0 -47
- package/frigg-cli/generate-command/index.js +0 -332
- package/frigg-cli/generate-command/terraform-generator.js +0 -555
- package/frigg-cli/generate-iam-command.js +0 -118
- package/frigg-cli/index.js +0 -75
- package/frigg-cli/index.test.js +0 -158
- package/frigg-cli/init-command/backend-first-handler.js +0 -756
- package/frigg-cli/init-command/index.js +0 -93
- package/frigg-cli/init-command/template-handler.js +0 -143
- package/frigg-cli/install-command/backend-js.js +0 -33
- package/frigg-cli/install-command/commit-changes.js +0 -16
- package/frigg-cli/install-command/environment-variables.js +0 -127
- package/frigg-cli/install-command/environment-variables.test.js +0 -136
- package/frigg-cli/install-command/index.js +0 -54
- package/frigg-cli/install-command/install-package.js +0 -13
- package/frigg-cli/install-command/integration-file.js +0 -30
- package/frigg-cli/install-command/logger.js +0 -12
- package/frigg-cli/install-command/template.js +0 -90
- package/frigg-cli/install-command/validate-package.js +0 -75
- package/frigg-cli/jest.config.js +0 -124
- package/frigg-cli/package.json +0 -54
- package/frigg-cli/start-command/index.js +0 -149
- package/frigg-cli/start-command/start-command.test.js +0 -297
- package/frigg-cli/test/init-command.test.js +0 -180
- package/frigg-cli/test/npm-registry.test.js +0 -319
- package/frigg-cli/ui-command/index.js +0 -154
- package/frigg-cli/utils/app-resolver.js +0 -319
- package/frigg-cli/utils/backend-path.js +0 -25
- package/frigg-cli/utils/database-validator.js +0 -161
- package/frigg-cli/utils/error-messages.js +0 -257
- package/frigg-cli/utils/npm-registry.js +0 -167
- package/frigg-cli/utils/prisma-runner.js +0 -280
- package/frigg-cli/utils/process-manager.js +0 -199
- package/frigg-cli/utils/repo-detection.js +0 -405
- package/infrastructure/aws-discovery.js +0 -1176
- package/infrastructure/aws-discovery.test.js +0 -1220
- package/infrastructure/serverless-template.js +0 -2074
- /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
- /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
- /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
- /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
- /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
- /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
- /package/infrastructure/{run-discovery.js → scripts/run-discovery.js} +0 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TDD Test for Orphan Detection with Relationship Analysis
|
|
3
|
+
*
|
|
4
|
+
* PROBLEM: When multiple orphaned resources of the same type are detected,
|
|
5
|
+
* we need to help users identify which ones are actually relevant to import.
|
|
6
|
+
*
|
|
7
|
+
* Real-world example from acme-integrations-dev:
|
|
8
|
+
* - 3 orphaned VPCs detected (vpc-0eadd96976d29ede7, vpc-0e2351eac99adcb83, vpc-020a0365610c05f0b)
|
|
9
|
+
* - 10 orphaned subnets detected
|
|
10
|
+
* - 3 orphaned security groups detected
|
|
11
|
+
* - 16 Lambda functions with VPC drift (all reference same VPC/subnets/SGs)
|
|
12
|
+
*
|
|
13
|
+
* ACTUAL AWS REALITY (verified 2025-10-27):
|
|
14
|
+
* - Lambda functions use DEFAULT VPC: vpc-01f21101d4ed6db59 (172.31.0.0/16, no Frigg tags)
|
|
15
|
+
* - Lambda subnets: subnet-020d32e3ca398a041, subnet-0c186318804aba790 (in default VPC)
|
|
16
|
+
* - Lambda security group: sg-0aca40438d17344c4 (in default VPC)
|
|
17
|
+
* - 3 orphaned VPCs are ALL unused (10.0.0.0/16, all have Frigg CFN tags but not in stack)
|
|
18
|
+
* - CloudFormation stack has ZERO VPCs (stack doesn't manage VPC)
|
|
19
|
+
*
|
|
20
|
+
* SOLUTION: Analyze relationships between drifted resources and orphaned resources
|
|
21
|
+
* to identify which orphaned resources are actually being referenced.
|
|
22
|
+
*
|
|
23
|
+
* Key Insight: If 16 Lambda functions are all drifted to use:
|
|
24
|
+
* - VPC: vpc-01f21101d4ed6db59 (default VPC, no tags)
|
|
25
|
+
* - SecurityGroup: sg-0aca40438d17344c4
|
|
26
|
+
* - Subnets: subnet-020d32e3ca398a041, subnet-0c186318804aba790
|
|
27
|
+
*
|
|
28
|
+
* But we detect 3 orphaned VPCs with Frigg tags:
|
|
29
|
+
* - vpc-0eadd96976d29ede7 (10.0.0.0/16)
|
|
30
|
+
* - vpc-0e2351eac99adcb83 (10.0.0.0/16)
|
|
31
|
+
* - vpc-020a0365610c05f0b (10.0.0.0/16)
|
|
32
|
+
*
|
|
33
|
+
* Then NONE of these orphaned VPCs are actually being used! They're old unused
|
|
34
|
+
* resources from previous deployments that should be cleaned up, not imported.
|
|
35
|
+
*
|
|
36
|
+
* RELATIONSHIP ANALYSIS:
|
|
37
|
+
* 1. Extract referenced resource IDs from drift issues
|
|
38
|
+
* - Lambda VpcConfig.SecurityGroupIds → extract SG IDs
|
|
39
|
+
* - Lambda VpcConfig.SubnetIds → extract subnet IDs
|
|
40
|
+
* - Subnets reference VPCs
|
|
41
|
+
* - Security groups reference VPCs
|
|
42
|
+
*
|
|
43
|
+
* 2. Group orphaned resources by type and count
|
|
44
|
+
*
|
|
45
|
+
* 3. For each orphaned resource:
|
|
46
|
+
* - Check if it's referenced by any drifted resource
|
|
47
|
+
* - If yes, mark as "actively used" (high priority to import)
|
|
48
|
+
* - If no, mark as "unused orphan" (likely old/irrelevant)
|
|
49
|
+
*
|
|
50
|
+
* 4. Add relationship metadata to orphaned resources:
|
|
51
|
+
* - referencedBy: [list of resources that reference this orphan]
|
|
52
|
+
* - relatedOrphans: [other orphans in same VPC/group]
|
|
53
|
+
*
|
|
54
|
+
* 5. When multiple resources of same type exist, show warning:
|
|
55
|
+
* "Multiple VPCs detected. Review relationships before importing."
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
const AWSResourceDetector = require('../aws-resource-detector');
|
|
59
|
+
const StackIdentifier = require('../../../domain/value-objects/stack-identifier');
|
|
60
|
+
const Issue = require('../../../domain/entities/issue');
|
|
61
|
+
const PropertyMismatch = require('../../../domain/entities/property-mismatch');
|
|
62
|
+
const PropertyMutability = require('../../../domain/value-objects/property-mutability');
|
|
63
|
+
|
|
64
|
+
// Mock AWS SDK
|
|
65
|
+
jest.mock('@aws-sdk/client-ec2', () => ({
|
|
66
|
+
EC2Client: jest.fn(),
|
|
67
|
+
DescribeVpcsCommand: jest.fn(),
|
|
68
|
+
DescribeSubnetsCommand: jest.fn(),
|
|
69
|
+
DescribeSecurityGroupsCommand: jest.fn(),
|
|
70
|
+
DescribeRouteTablesCommand: jest.fn(),
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
jest.mock('@aws-sdk/client-rds', () => ({
|
|
74
|
+
RDSClient: jest.fn(),
|
|
75
|
+
DescribeDBClustersCommand: jest.fn(),
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
jest.mock('@aws-sdk/client-kms', () => ({
|
|
79
|
+
KMSClient: jest.fn(),
|
|
80
|
+
ListKeysCommand: jest.fn(),
|
|
81
|
+
DescribeKeyCommand: jest.fn(),
|
|
82
|
+
ListAliasesCommand: jest.fn(),
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
describe('Orphan Detection with Relationship Analysis (TDD)', () => {
|
|
86
|
+
let detector;
|
|
87
|
+
let mockEC2Send;
|
|
88
|
+
let mockRDSSend;
|
|
89
|
+
let mockKMSSend;
|
|
90
|
+
|
|
91
|
+
beforeEach(() => {
|
|
92
|
+
jest.clearAllMocks();
|
|
93
|
+
|
|
94
|
+
// Mock EC2 client
|
|
95
|
+
mockEC2Send = jest.fn();
|
|
96
|
+
const { EC2Client } = require('@aws-sdk/client-ec2');
|
|
97
|
+
EC2Client.mockImplementation(() => ({ send: mockEC2Send }));
|
|
98
|
+
|
|
99
|
+
// Mock RDS client
|
|
100
|
+
mockRDSSend = jest.fn().mockResolvedValue({ DBClusters: [] });
|
|
101
|
+
const { RDSClient } = require('@aws-sdk/client-rds');
|
|
102
|
+
RDSClient.mockImplementation(() => ({ send: mockRDSSend }));
|
|
103
|
+
|
|
104
|
+
// Mock KMS client
|
|
105
|
+
mockKMSSend = jest.fn().mockResolvedValue({ Keys: [] });
|
|
106
|
+
const { KMSClient } = require('@aws-sdk/client-kms');
|
|
107
|
+
KMSClient.mockImplementation(() => ({ send: mockKMSSend }));
|
|
108
|
+
|
|
109
|
+
detector = new AWSResourceDetector({ region: 'us-east-1' });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('Real-world scenario: acme-integrations-dev multiple VPCs', () => {
|
|
113
|
+
test('should analyze relationships between drifted Lambdas and orphaned VPC resources', async () => {
|
|
114
|
+
const stackIdentifier = new StackIdentifier({
|
|
115
|
+
stackName: 'acme-integrations-dev',
|
|
116
|
+
region: 'us-east-1',
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Stack has 16 Lambda functions (all with VPC drift)
|
|
120
|
+
const stackResources = [
|
|
121
|
+
{
|
|
122
|
+
logicalId: 'AttioFunction',
|
|
123
|
+
physicalId: 'acme-integrations-dev-attio',
|
|
124
|
+
resourceType: 'AWS::Lambda::Function',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
logicalId: 'AuthFunction',
|
|
128
|
+
physicalId: 'acme-integrations-dev-auth',
|
|
129
|
+
resourceType: 'AWS::Lambda::Function',
|
|
130
|
+
},
|
|
131
|
+
// ... 14 more Lambda functions
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
// Mock EC2 responses - 3 VPCs, 10 subnets, 3 security groups
|
|
135
|
+
mockEC2Send.mockImplementation((command) => {
|
|
136
|
+
if (command.constructor.name === 'DescribeVpcsCommand') {
|
|
137
|
+
return Promise.resolve({
|
|
138
|
+
Vpcs: [
|
|
139
|
+
{
|
|
140
|
+
// VPC #1: Old VPC with Frigg tags but not in stack
|
|
141
|
+
VpcId: 'vpc-0eadd96976d29ede7',
|
|
142
|
+
CidrBlock: '10.0.0.0/16',
|
|
143
|
+
State: 'available',
|
|
144
|
+
Tags: [
|
|
145
|
+
{
|
|
146
|
+
Key: 'aws:cloudformation:stack-name',
|
|
147
|
+
Value: 'acme-integrations-dev',
|
|
148
|
+
},
|
|
149
|
+
{ Key: 'Name', Value: 'Old VPC' },
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
// VPC #2: Another old VPC
|
|
154
|
+
VpcId: 'vpc-0e2351eac99adcb83',
|
|
155
|
+
CidrBlock: '10.1.0.0/16',
|
|
156
|
+
State: 'available',
|
|
157
|
+
Tags: [
|
|
158
|
+
{
|
|
159
|
+
Key: 'aws:cloudformation:stack-name',
|
|
160
|
+
Value: 'acme-integrations-dev',
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
// VPC #3: Current VPC that Lambdas actually use
|
|
166
|
+
VpcId: 'vpc-020a0365610c05f0b',
|
|
167
|
+
CidrBlock: '10.2.0.0/16',
|
|
168
|
+
State: 'available',
|
|
169
|
+
Tags: [
|
|
170
|
+
{
|
|
171
|
+
Key: 'aws:cloudformation:stack-name',
|
|
172
|
+
Value: 'acme-integrations-dev',
|
|
173
|
+
},
|
|
174
|
+
{ Key: 'Name', Value: 'Current VPC' },
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (command.constructor.name === 'DescribeSubnetsCommand') {
|
|
182
|
+
return Promise.resolve({
|
|
183
|
+
Subnets: [
|
|
184
|
+
// Subnets in VPC #3 (current VPC) - these are the ones Lambdas reference
|
|
185
|
+
{
|
|
186
|
+
SubnetId: 'subnet-020d32e3ca398a041',
|
|
187
|
+
VpcId: 'vpc-020a0365610c05f0b',
|
|
188
|
+
CidrBlock: '10.2.1.0/24',
|
|
189
|
+
AvailabilityZone: 'us-east-1a',
|
|
190
|
+
State: 'available',
|
|
191
|
+
Tags: [
|
|
192
|
+
{
|
|
193
|
+
Key: 'aws:cloudformation:stack-name',
|
|
194
|
+
Value: 'acme-integrations-dev',
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
SubnetId: 'subnet-0c186318804aba790',
|
|
200
|
+
VpcId: 'vpc-020a0365610c05f0b',
|
|
201
|
+
CidrBlock: '10.2.2.0/24',
|
|
202
|
+
AvailabilityZone: 'us-east-1b',
|
|
203
|
+
State: 'available',
|
|
204
|
+
Tags: [
|
|
205
|
+
{
|
|
206
|
+
Key: 'aws:cloudformation:stack-name',
|
|
207
|
+
Value: 'acme-integrations-dev',
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
},
|
|
211
|
+
// Subnets in old VPCs (unused)
|
|
212
|
+
{
|
|
213
|
+
SubnetId: 'subnet-0ad31b5ee6814b8fa',
|
|
214
|
+
VpcId: 'vpc-0eadd96976d29ede7',
|
|
215
|
+
CidrBlock: '10.0.1.0/24',
|
|
216
|
+
AvailabilityZone: 'us-east-1a',
|
|
217
|
+
State: 'available',
|
|
218
|
+
Tags: [
|
|
219
|
+
{
|
|
220
|
+
Key: 'aws:cloudformation:stack-name',
|
|
221
|
+
Value: 'acme-integrations-dev',
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
// ... 7 more unused subnets
|
|
226
|
+
],
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (command.constructor.name === 'DescribeSecurityGroupsCommand') {
|
|
231
|
+
return Promise.resolve({
|
|
232
|
+
SecurityGroups: [
|
|
233
|
+
// SG in current VPC (unused orphan - Lambdas use different SG)
|
|
234
|
+
{
|
|
235
|
+
GroupId: 'sg-07c01370e830b6ad6',
|
|
236
|
+
GroupName: 'default',
|
|
237
|
+
VpcId: 'vpc-020a0365610c05f0b',
|
|
238
|
+
Tags: [
|
|
239
|
+
{
|
|
240
|
+
Key: 'aws:cloudformation:stack-name',
|
|
241
|
+
Value: 'acme-integrations-dev',
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
// SGs in old VPCs (unused)
|
|
246
|
+
{
|
|
247
|
+
GroupId: 'sg-03abddb7fb50aeaff',
|
|
248
|
+
GroupName: 'default',
|
|
249
|
+
VpcId: 'vpc-0eadd96976d29ede7',
|
|
250
|
+
Tags: [
|
|
251
|
+
{
|
|
252
|
+
Key: 'aws:cloudformation:stack-name',
|
|
253
|
+
Value: 'acme-integrations-dev',
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
GroupId: 'sg-027f44ad46727df93',
|
|
259
|
+
GroupName: 'default',
|
|
260
|
+
VpcId: 'vpc-0e2351eac99adcb83',
|
|
261
|
+
Tags: [
|
|
262
|
+
{
|
|
263
|
+
Key: 'aws:cloudformation:stack-name',
|
|
264
|
+
Value: 'acme-integrations-dev',
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return Promise.resolve({});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Drift issues: Lambda functions reference subnets in current VPC
|
|
276
|
+
const driftIssues = [
|
|
277
|
+
Issue.propertyMismatch({
|
|
278
|
+
resourceType: 'AWS::Lambda::Function',
|
|
279
|
+
resourceId: 'acme-integrations-dev-attio',
|
|
280
|
+
mismatch: new PropertyMismatch({
|
|
281
|
+
propertyPath: 'VpcConfig.SubnetIds',
|
|
282
|
+
expectedValue: 'subnet-00ab9e0502e66aac3,subnet-00d085a52937aaf91',
|
|
283
|
+
actualValue: 'subnet-020d32e3ca398a041,subnet-0c186318804aba790', // ← References current VPC subnets
|
|
284
|
+
mutability: PropertyMutability.MUTABLE,
|
|
285
|
+
}),
|
|
286
|
+
}),
|
|
287
|
+
Issue.propertyMismatch({
|
|
288
|
+
resourceType: 'AWS::Lambda::Function',
|
|
289
|
+
resourceId: 'acme-integrations-dev-auth',
|
|
290
|
+
mismatch: new PropertyMismatch({
|
|
291
|
+
propertyPath: 'VpcConfig.SubnetIds',
|
|
292
|
+
expectedValue: 'subnet-00ab9e0502e66aac3,subnet-00d085a52937aaf91',
|
|
293
|
+
actualValue: 'subnet-020d32e3ca398a041,subnet-0c186318804aba790', // ← Same subnets
|
|
294
|
+
mutability: PropertyMutability.MUTABLE,
|
|
295
|
+
}),
|
|
296
|
+
}),
|
|
297
|
+
// ... 14 more Lambda functions with same subnet drift
|
|
298
|
+
];
|
|
299
|
+
|
|
300
|
+
// Act
|
|
301
|
+
const orphans = await detector.findOrphanedResourcesWithRelationships({
|
|
302
|
+
stackIdentifier,
|
|
303
|
+
stackResources,
|
|
304
|
+
driftIssues,
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Assert: Should identify relationship metadata
|
|
308
|
+
expect(orphans.length).toBeGreaterThan(0);
|
|
309
|
+
|
|
310
|
+
// Find the orphaned subnets that are actually referenced
|
|
311
|
+
const referencedSubnets = orphans.filter(
|
|
312
|
+
(o) =>
|
|
313
|
+
o.resourceType === 'AWS::EC2::Subnet' &&
|
|
314
|
+
(o.physicalId === 'subnet-020d32e3ca398a041' ||
|
|
315
|
+
o.physicalId === 'subnet-0c186318804aba790')
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
// These subnets should be marked as "actively used"
|
|
319
|
+
for (const subnet of referencedSubnets) {
|
|
320
|
+
expect(subnet.metadata).toHaveProperty('referencedBy');
|
|
321
|
+
expect(subnet.metadata.referencedBy.length).toBeGreaterThan(0);
|
|
322
|
+
expect(subnet.metadata.isActivelyUsed).toBe(true);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Find the VPC that contains these subnets
|
|
326
|
+
const currentVpc = orphans.find((o) => o.physicalId === 'vpc-020a0365610c05f0b');
|
|
327
|
+
|
|
328
|
+
if (currentVpc) {
|
|
329
|
+
expect(currentVpc.metadata).toHaveProperty('containsReferencedResources');
|
|
330
|
+
expect(currentVpc.metadata.containsReferencedResources).toBe(true);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Old VPCs should NOT be marked as actively used
|
|
334
|
+
const oldVpc1 = orphans.find((o) => o.physicalId === 'vpc-0eadd96976d29ede7');
|
|
335
|
+
if (oldVpc1) {
|
|
336
|
+
expect(oldVpc1.metadata?.isActivelyUsed).toBeFalsy();
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test('should flag multiple resources of same type for manual review', async () => {
|
|
341
|
+
const stackIdentifier = new StackIdentifier({
|
|
342
|
+
stackName: 'acme-integrations-dev',
|
|
343
|
+
region: 'us-east-1',
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const stackResources = [
|
|
347
|
+
{
|
|
348
|
+
logicalId: 'MyLambda',
|
|
349
|
+
physicalId: 'my-lambda',
|
|
350
|
+
resourceType: 'AWS::Lambda::Function',
|
|
351
|
+
},
|
|
352
|
+
];
|
|
353
|
+
|
|
354
|
+
// Mock 3 orphaned VPCs
|
|
355
|
+
mockEC2Send.mockResolvedValue({
|
|
356
|
+
Vpcs: [
|
|
357
|
+
{
|
|
358
|
+
VpcId: 'vpc-1',
|
|
359
|
+
CidrBlock: '10.0.0.0/16',
|
|
360
|
+
State: 'available',
|
|
361
|
+
Tags: [{ Key: 'aws:cloudformation:stack-name', Value: 'acme-integrations-dev' }],
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
VpcId: 'vpc-2',
|
|
365
|
+
CidrBlock: '10.1.0.0/16',
|
|
366
|
+
State: 'available',
|
|
367
|
+
Tags: [{ Key: 'aws:cloudformation:stack-name', Value: 'acme-integrations-dev' }],
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
VpcId: 'vpc-3',
|
|
371
|
+
CidrBlock: '10.2.0.0/16',
|
|
372
|
+
State: 'available',
|
|
373
|
+
Tags: [{ Key: 'aws:cloudformation:stack-name', Value: 'acme-integrations-dev' }],
|
|
374
|
+
},
|
|
375
|
+
],
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const result = await detector.findOrphanedResourcesWithRelationships({
|
|
379
|
+
stackIdentifier,
|
|
380
|
+
stackResources,
|
|
381
|
+
driftIssues: [],
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Should include warning metadata
|
|
385
|
+
const summary = detector.analyzeOrphanSummary(result);
|
|
386
|
+
|
|
387
|
+
expect(summary.warnings).toContain(
|
|
388
|
+
'Multiple VPCs detected (3). Review relationships before importing.'
|
|
389
|
+
);
|
|
390
|
+
expect(summary.multipleResourceTypes).toContain('AWS::EC2::VPC');
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
describe('Helper method: extractReferencedResourceIds', () => {
|
|
395
|
+
test('should extract subnet IDs from VpcConfig.SubnetIds drift', () => {
|
|
396
|
+
const driftIssue = Issue.propertyMismatch({
|
|
397
|
+
resourceType: 'AWS::Lambda::Function',
|
|
398
|
+
resourceId: 'my-lambda',
|
|
399
|
+
mismatch: new PropertyMismatch({
|
|
400
|
+
propertyPath: 'VpcConfig.SubnetIds',
|
|
401
|
+
expectedValue: 'subnet-old-1,subnet-old-2',
|
|
402
|
+
actualValue: 'subnet-020d32e3ca398a041,subnet-0c186318804aba790',
|
|
403
|
+
mutability: PropertyMutability.MUTABLE,
|
|
404
|
+
}),
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const detector = new AWSResourceDetector({ region: 'us-east-1' });
|
|
408
|
+
const referenced = detector._extractReferencedResourceIds([driftIssue]);
|
|
409
|
+
|
|
410
|
+
expect(referenced.subnetIds).toContain('subnet-020d32e3ca398a041');
|
|
411
|
+
expect(referenced.subnetIds).toContain('subnet-0c186318804aba790');
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test('should extract security group IDs from VpcConfig.SecurityGroupIds drift', () => {
|
|
415
|
+
const driftIssue = Issue.propertyMismatch({
|
|
416
|
+
resourceType: 'AWS::Lambda::Function',
|
|
417
|
+
resourceId: 'my-lambda',
|
|
418
|
+
mismatch: new PropertyMismatch({
|
|
419
|
+
propertyPath: 'VpcConfig.SecurityGroupIds',
|
|
420
|
+
expectedValue: 'sg-old',
|
|
421
|
+
actualValue: 'sg-0aca40438d17344c4',
|
|
422
|
+
mutability: PropertyMutability.MUTABLE,
|
|
423
|
+
}),
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
const detector = new AWSResourceDetector({ region: 'us-east-1' });
|
|
427
|
+
const referenced = detector._extractReferencedResourceIds([driftIssue]);
|
|
428
|
+
|
|
429
|
+
expect(referenced.securityGroupIds).toContain('sg-0aca40438d17344c4');
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
});
|