@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,535 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RepairViaImportUseCase - Import Orphaned Resources into CloudFormation Stack
|
|
3
|
+
*
|
|
4
|
+
* Application Layer - Use Case
|
|
5
|
+
*
|
|
6
|
+
* Business logic for the "frigg repair --import" command. Orchestrates resource
|
|
7
|
+
* import operations to fix orphaned resources by bringing them under CloudFormation management.
|
|
8
|
+
*
|
|
9
|
+
* Responsibilities:
|
|
10
|
+
* - Validate resources can be imported
|
|
11
|
+
* - Retrieve resource details from cloud
|
|
12
|
+
* - Generate CloudFormation template snippets
|
|
13
|
+
* - Execute import operations (single or batch)
|
|
14
|
+
* - Track import operation status
|
|
15
|
+
* - Map orphaned resources to correct logical IDs using template comparison
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const { TemplateParser } = require('../../domain/services/template-parser');
|
|
19
|
+
const { LogicalIdMapper } = require('../../domain/services/logical-id-mapper');
|
|
20
|
+
|
|
21
|
+
class RepairViaImportUseCase {
|
|
22
|
+
/**
|
|
23
|
+
* Create use case with required dependencies
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} params
|
|
26
|
+
* @param {IResourceImporter} params.resourceImporter - Resource import operations
|
|
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
|
|
31
|
+
*/
|
|
32
|
+
constructor({ resourceImporter, resourceDetector, stackRepository, templateParser, logicalIdMapper }) {
|
|
33
|
+
if (!resourceImporter) {
|
|
34
|
+
throw new Error('resourceImporter is required');
|
|
35
|
+
}
|
|
36
|
+
if (!resourceDetector) {
|
|
37
|
+
throw new Error('resourceDetector is required');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.resourceImporter = resourceImporter;
|
|
41
|
+
this.resourceDetector = resourceDetector;
|
|
42
|
+
this.stackRepository = stackRepository;
|
|
43
|
+
this.templateParser = templateParser || new TemplateParser();
|
|
44
|
+
this.logicalIdMapper = logicalIdMapper || new LogicalIdMapper({ region: 'us-east-1' });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Import a single orphaned resource into a CloudFormation stack
|
|
49
|
+
*
|
|
50
|
+
* @param {Object} params
|
|
51
|
+
* @param {StackIdentifier} params.stackIdentifier - Target stack
|
|
52
|
+
* @param {string} params.logicalId - Desired logical ID for resource in template
|
|
53
|
+
* @param {string} params.physicalId - Physical ID of resource in cloud
|
|
54
|
+
* @param {string} params.resourceType - CloudFormation resource type
|
|
55
|
+
* @returns {Promise<Object>} Import result
|
|
56
|
+
*/
|
|
57
|
+
async importSingleResource({ stackIdentifier, logicalId, physicalId, resourceType }) {
|
|
58
|
+
// 1. Validate resource can be imported
|
|
59
|
+
const validation = await this.resourceImporter.validateImport({
|
|
60
|
+
resourceType,
|
|
61
|
+
physicalId,
|
|
62
|
+
region: stackIdentifier.region,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!validation.canImport) {
|
|
66
|
+
throw new Error(validation.reason);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 2. Get detailed resource properties from cloud
|
|
70
|
+
const resourceDetails = await this.resourceDetector.getResourceDetails({
|
|
71
|
+
resourceType,
|
|
72
|
+
physicalId,
|
|
73
|
+
region: stackIdentifier.region,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// 3. Execute import operation
|
|
77
|
+
const importResult = await this.resourceImporter.importResource({
|
|
78
|
+
stackIdentifier,
|
|
79
|
+
logicalId,
|
|
80
|
+
resourceType,
|
|
81
|
+
physicalId,
|
|
82
|
+
properties: resourceDetails.properties,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// 4. Return result with warnings if any
|
|
86
|
+
return {
|
|
87
|
+
success: true,
|
|
88
|
+
operationId: importResult.operationId,
|
|
89
|
+
status: importResult.status,
|
|
90
|
+
message: importResult.message,
|
|
91
|
+
warnings: validation.warnings || [],
|
|
92
|
+
resource: {
|
|
93
|
+
logicalId,
|
|
94
|
+
physicalId,
|
|
95
|
+
resourceType,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Import multiple orphaned resources into a stack in batch
|
|
102
|
+
*
|
|
103
|
+
* @param {Object} params
|
|
104
|
+
* @param {StackIdentifier} params.stackIdentifier - Target stack
|
|
105
|
+
* @param {Array<Object>} params.resources - Resources to import
|
|
106
|
+
* @param {string} params.resources[].logicalId - Logical ID
|
|
107
|
+
* @param {string} params.resources[].physicalId - Physical ID
|
|
108
|
+
* @param {string} params.resources[].resourceType - Resource type
|
|
109
|
+
* @returns {Promise<Object>} Batch import result
|
|
110
|
+
*/
|
|
111
|
+
async importMultipleResources({ stackIdentifier, resources }) {
|
|
112
|
+
const validationErrors = [];
|
|
113
|
+
const validResources = [];
|
|
114
|
+
|
|
115
|
+
// 1. Validate all resources first
|
|
116
|
+
for (const resource of resources) {
|
|
117
|
+
try {
|
|
118
|
+
const validation = await this.resourceImporter.validateImport({
|
|
119
|
+
resourceType: resource.resourceType,
|
|
120
|
+
physicalId: resource.physicalId,
|
|
121
|
+
region: stackIdentifier.region,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (!validation.canImport) {
|
|
125
|
+
validationErrors.push({
|
|
126
|
+
logicalId: resource.logicalId,
|
|
127
|
+
physicalId: resource.physicalId,
|
|
128
|
+
reason: validation.reason,
|
|
129
|
+
});
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Get resource details
|
|
134
|
+
const resourceDetails = await this.resourceDetector.getResourceDetails({
|
|
135
|
+
resourceType: resource.resourceType,
|
|
136
|
+
physicalId: resource.physicalId,
|
|
137
|
+
region: stackIdentifier.region,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
validResources.push({
|
|
141
|
+
logicalId: resource.logicalId,
|
|
142
|
+
resourceType: resource.resourceType,
|
|
143
|
+
physicalId: resource.physicalId,
|
|
144
|
+
properties: resourceDetails.properties,
|
|
145
|
+
});
|
|
146
|
+
} catch (error) {
|
|
147
|
+
validationErrors.push({
|
|
148
|
+
logicalId: resource.logicalId,
|
|
149
|
+
physicalId: resource.physicalId,
|
|
150
|
+
reason: error.message,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 2. If ANY validation failed, fail the entire batch (all-or-nothing approach)
|
|
156
|
+
if (validationErrors.length > 0) {
|
|
157
|
+
return {
|
|
158
|
+
success: false,
|
|
159
|
+
importedCount: 0,
|
|
160
|
+
failedCount: validationErrors.length,
|
|
161
|
+
validationErrors,
|
|
162
|
+
message: `${validationErrors.length} resource(s) failed validation - batch import aborted`,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 3. All validations passed - import resources in batch
|
|
167
|
+
if (validResources.length > 0) {
|
|
168
|
+
const importResult = await this.resourceImporter.importMultipleResources({
|
|
169
|
+
stackIdentifier,
|
|
170
|
+
resources: validResources,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
success: true,
|
|
175
|
+
importedCount: importResult.importedCount,
|
|
176
|
+
failedCount: importResult.failedCount,
|
|
177
|
+
operationId: importResult.operationId,
|
|
178
|
+
status: importResult.status,
|
|
179
|
+
message: importResult.message,
|
|
180
|
+
details: importResult.details,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 4. No resources provided
|
|
185
|
+
return {
|
|
186
|
+
success: false,
|
|
187
|
+
importedCount: 0,
|
|
188
|
+
failedCount: 0,
|
|
189
|
+
message: 'No resources provided for import',
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get status of an ongoing import operation
|
|
195
|
+
*
|
|
196
|
+
* @param {Object} params
|
|
197
|
+
* @param {string} params.operationId - CloudFormation change set ID
|
|
198
|
+
* @returns {Promise<Object>} Operation status
|
|
199
|
+
*/
|
|
200
|
+
async getImportStatus({ operationId }) {
|
|
201
|
+
return await this.resourceImporter.getImportStatus(operationId);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Preview what template changes will be made for an import
|
|
206
|
+
*
|
|
207
|
+
* @param {Object} params
|
|
208
|
+
* @param {StackIdentifier} params.stackIdentifier - Target stack
|
|
209
|
+
* @param {string} params.logicalId - Desired logical ID
|
|
210
|
+
* @param {string} params.physicalId - Physical resource ID
|
|
211
|
+
* @param {string} params.resourceType - Resource type
|
|
212
|
+
* @returns {Promise<Object>} Preview with template snippet
|
|
213
|
+
*/
|
|
214
|
+
async previewImport({ stackIdentifier, logicalId, physicalId, resourceType }) {
|
|
215
|
+
// Get resource details from cloud
|
|
216
|
+
const resourceDetails = await this.resourceDetector.getResourceDetails({
|
|
217
|
+
resourceType,
|
|
218
|
+
physicalId,
|
|
219
|
+
region: stackIdentifier.region,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Generate template snippet
|
|
223
|
+
const templateSnippet = await this.resourceImporter.generateTemplateSnippet({
|
|
224
|
+
logicalId,
|
|
225
|
+
resourceType,
|
|
226
|
+
properties: resourceDetails.properties,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
logicalId,
|
|
231
|
+
physicalId,
|
|
232
|
+
resourceType,
|
|
233
|
+
templateSnippet,
|
|
234
|
+
properties: resourceDetails.properties,
|
|
235
|
+
};
|
|
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. Filter out unmapped resources
|
|
285
|
+
const mappedResources = mappings.filter((m) => m.logicalId !== null);
|
|
286
|
+
const unmappedResources = mappings.filter((m) => m.logicalId === null);
|
|
287
|
+
|
|
288
|
+
if (mappedResources.length === 0) {
|
|
289
|
+
return {
|
|
290
|
+
success: false,
|
|
291
|
+
message: 'No resources could be mapped to logical IDs',
|
|
292
|
+
unmappedCount: unmappedResources.length,
|
|
293
|
+
unmappedResources,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// 6. Deduplicate: Select ONE resource per logical ID based on deployed template
|
|
298
|
+
const { selectedResources, duplicates } = this._deduplicateResourcesByLogicalId(
|
|
299
|
+
mappedResources,
|
|
300
|
+
deployedTemplate
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
// 7. Check for warnings
|
|
304
|
+
const multiResourceWarnings = this._checkForMultipleResources(duplicates);
|
|
305
|
+
|
|
306
|
+
// 8. Generate import-resources.json format using SELECTED resources
|
|
307
|
+
const resourcesToImport = selectedResources.map((mapping) => ({
|
|
308
|
+
ResourceType: mapping.resourceType,
|
|
309
|
+
LogicalResourceId: mapping.logicalId,
|
|
310
|
+
ResourceIdentifier: this._getResourceIdentifier(mapping),
|
|
311
|
+
}));
|
|
312
|
+
|
|
313
|
+
// 9. Return result with deduplication info
|
|
314
|
+
return {
|
|
315
|
+
success: true,
|
|
316
|
+
mappedCount: selectedResources.length,
|
|
317
|
+
unmappedCount: unmappedResources.length,
|
|
318
|
+
duplicatesRemoved: duplicates.length,
|
|
319
|
+
mappings: selectedResources,
|
|
320
|
+
unmappedResources,
|
|
321
|
+
duplicates, // Resources that were filtered out
|
|
322
|
+
resourcesToImport,
|
|
323
|
+
warnings: multiResourceWarnings,
|
|
324
|
+
buildTemplatePath,
|
|
325
|
+
deployedTemplatePath: 'CloudFormation (deployed)',
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Deduplicate resources: Select ONE resource per logical ID
|
|
331
|
+
* When multiple resources have the same logical ID, pick the one that's
|
|
332
|
+
* actually referenced in the deployed template.
|
|
333
|
+
*
|
|
334
|
+
* @param {Array} mappedResources - Resources with logical IDs
|
|
335
|
+
* @param {Object} deployedTemplate - Deployed CloudFormation template
|
|
336
|
+
* @returns {Object} { selectedResources, duplicates }
|
|
337
|
+
* @private
|
|
338
|
+
*/
|
|
339
|
+
_deduplicateResourcesByLogicalId(mappedResources, deployedTemplate) {
|
|
340
|
+
// Group resources by logical ID
|
|
341
|
+
const byLogicalId = {};
|
|
342
|
+
mappedResources.forEach((resource) => {
|
|
343
|
+
if (!byLogicalId[resource.logicalId]) {
|
|
344
|
+
byLogicalId[resource.logicalId] = [];
|
|
345
|
+
}
|
|
346
|
+
byLogicalId[resource.logicalId].push(resource);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Extract all physical IDs referenced in deployed template
|
|
350
|
+
const referencedIds = this._extractReferencedIdsFromTemplate(deployedTemplate);
|
|
351
|
+
|
|
352
|
+
const selectedResources = [];
|
|
353
|
+
const duplicates = [];
|
|
354
|
+
|
|
355
|
+
// For each logical ID, select ONE resource
|
|
356
|
+
Object.entries(byLogicalId).forEach(([logicalId, resources]) => {
|
|
357
|
+
if (resources.length === 1) {
|
|
358
|
+
// Only one resource - select it
|
|
359
|
+
selectedResources.push(resources[0]);
|
|
360
|
+
} else {
|
|
361
|
+
// Multiple resources - pick the one in deployed template
|
|
362
|
+
let selected = null;
|
|
363
|
+
|
|
364
|
+
// Try to find resource that's actually referenced
|
|
365
|
+
for (const resource of resources) {
|
|
366
|
+
if (this._isResourceReferenced(resource, referencedIds)) {
|
|
367
|
+
selected = resource;
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Fallback: If none are referenced, pick first one
|
|
373
|
+
if (!selected) {
|
|
374
|
+
selected = resources[0];
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
selectedResources.push(selected);
|
|
378
|
+
|
|
379
|
+
// Mark others as duplicates
|
|
380
|
+
resources.forEach((r) => {
|
|
381
|
+
if (r.physicalId !== selected.physicalId) {
|
|
382
|
+
duplicates.push(r);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
return { selectedResources, duplicates };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Extract all physical resource IDs referenced in deployed template
|
|
393
|
+
* Looks for hardcoded IDs in Lambda VPC configs, security group rules, etc.
|
|
394
|
+
* @private
|
|
395
|
+
*/
|
|
396
|
+
_extractReferencedIdsFromTemplate(template) {
|
|
397
|
+
const referenced = {
|
|
398
|
+
vpcIds: new Set(),
|
|
399
|
+
subnetIds: new Set(),
|
|
400
|
+
securityGroupIds: new Set(),
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
if (!template || !template.resources) {
|
|
404
|
+
return referenced;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Traverse all resources in template
|
|
408
|
+
Object.values(template.resources).forEach((resource) => {
|
|
409
|
+
// Lambda VPC config contains hardcoded IDs
|
|
410
|
+
if (
|
|
411
|
+
resource.Type === 'AWS::Lambda::Function' &&
|
|
412
|
+
resource.Properties?.VpcConfig
|
|
413
|
+
) {
|
|
414
|
+
const { SubnetIds, SecurityGroupIds } = resource.Properties.VpcConfig;
|
|
415
|
+
|
|
416
|
+
if (SubnetIds) {
|
|
417
|
+
SubnetIds.forEach((id) => {
|
|
418
|
+
if (typeof id === 'string' && id.startsWith('subnet-')) {
|
|
419
|
+
referenced.subnetIds.add(id);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (SecurityGroupIds) {
|
|
425
|
+
SecurityGroupIds.forEach((id) => {
|
|
426
|
+
if (typeof id === 'string' && id.startsWith('sg-')) {
|
|
427
|
+
referenced.securityGroupIds.add(id);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Security group rules may reference other security groups
|
|
434
|
+
if (resource.Type === 'AWS::EC2::SecurityGroupIngress' ||
|
|
435
|
+
resource.Type === 'AWS::EC2::SecurityGroupEgress') {
|
|
436
|
+
const groupId = resource.Properties?.GroupId;
|
|
437
|
+
const sourceSecurityGroupId = resource.Properties?.SourceSecurityGroupId;
|
|
438
|
+
|
|
439
|
+
if (typeof groupId === 'string' && groupId.startsWith('sg-')) {
|
|
440
|
+
referenced.securityGroupIds.add(groupId);
|
|
441
|
+
}
|
|
442
|
+
if (typeof sourceSecurityGroupId === 'string' && sourceSecurityGroupId.startsWith('sg-')) {
|
|
443
|
+
referenced.securityGroupIds.add(sourceSecurityGroupId);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
return referenced;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Check if a resource is referenced in the deployed template
|
|
453
|
+
* @private
|
|
454
|
+
*/
|
|
455
|
+
_isResourceReferenced(resource, referencedIds) {
|
|
456
|
+
const { resourceType, physicalId } = resource;
|
|
457
|
+
|
|
458
|
+
if (resourceType === 'AWS::EC2::VPC') {
|
|
459
|
+
return referencedIds.vpcIds.has(physicalId);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (resourceType === 'AWS::EC2::Subnet') {
|
|
463
|
+
return referencedIds.subnetIds.has(physicalId);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (resourceType === 'AWS::EC2::SecurityGroup') {
|
|
467
|
+
return referencedIds.securityGroupIds.has(physicalId);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// For other resource types, we can't determine
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Check for multiple resources of same type
|
|
476
|
+
* Returns warnings when user needs to manually select
|
|
477
|
+
* @private
|
|
478
|
+
*/
|
|
479
|
+
_checkForMultipleResources(mappings) {
|
|
480
|
+
const warnings = [];
|
|
481
|
+
const byType = {};
|
|
482
|
+
|
|
483
|
+
// Group by resource type
|
|
484
|
+
mappings.forEach((mapping) => {
|
|
485
|
+
if (!byType[mapping.resourceType]) {
|
|
486
|
+
byType[mapping.resourceType] = [];
|
|
487
|
+
}
|
|
488
|
+
byType[mapping.resourceType].push(mapping);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// Check for multiples
|
|
492
|
+
Object.entries(byType).forEach(([type, resources]) => {
|
|
493
|
+
if (resources.length > 1) {
|
|
494
|
+
const shortType = type.replace('AWS::EC2::', '');
|
|
495
|
+
warnings.push({
|
|
496
|
+
type: 'MULTIPLE_RESOURCES',
|
|
497
|
+
resourceType: type,
|
|
498
|
+
count: resources.length,
|
|
499
|
+
message: `Multiple ${shortType}s detected (${resources.length}). Review relationships before importing.`,
|
|
500
|
+
resources: resources.map((r) => ({
|
|
501
|
+
physicalId: r.physicalId,
|
|
502
|
+
logicalId: r.logicalId,
|
|
503
|
+
matchMethod: r.matchMethod,
|
|
504
|
+
confidence: r.confidence,
|
|
505
|
+
})),
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
return warnings;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Get CloudFormation resource identifier for import
|
|
515
|
+
* @private
|
|
516
|
+
*/
|
|
517
|
+
_getResourceIdentifier(mapping) {
|
|
518
|
+
const { resourceType, physicalId } = mapping;
|
|
519
|
+
|
|
520
|
+
// Map resource types to their identifier format
|
|
521
|
+
const identifierMap = {
|
|
522
|
+
'AWS::EC2::VPC': { VpcId: physicalId },
|
|
523
|
+
'AWS::EC2::Subnet': { SubnetId: physicalId },
|
|
524
|
+
'AWS::EC2::SecurityGroup': { GroupId: physicalId },
|
|
525
|
+
'AWS::EC2::InternetGateway': { InternetGatewayId: physicalId },
|
|
526
|
+
'AWS::EC2::NatGateway': { NatGatewayId: physicalId },
|
|
527
|
+
'AWS::EC2::RouteTable': { RouteTableId: physicalId },
|
|
528
|
+
'AWS::EC2::VPCEndpoint': { VpcEndpointId: physicalId },
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
return identifierMap[resourceType] || { Id: physicalId };
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
module.exports = RepairViaImportUseCase;
|