@friggframework/devtools 2.0.0-next.45 → 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 -2094
- /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,337 @@
|
|
|
1
|
+
const { MigrationResourceResolver } = require('./migration-resolver');
|
|
2
|
+
const { ResourceOwnership, createEmptyDiscoveryResult } = require('../shared/types');
|
|
3
|
+
|
|
4
|
+
describe('MigrationResourceResolver', () => {
|
|
5
|
+
let resolver;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
resolver = new MigrationResourceResolver();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('resolveBucket', () => {
|
|
12
|
+
describe('Explicit ownership intent', () => {
|
|
13
|
+
it('should respect ownership.bucket=stack when specified', () => {
|
|
14
|
+
const appDefinition = {
|
|
15
|
+
migration: {
|
|
16
|
+
ownership: { bucket: 'stack' }
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const discovery = createEmptyDiscoveryResult();
|
|
20
|
+
|
|
21
|
+
const decision = resolver.resolveBucket(appDefinition, discovery);
|
|
22
|
+
|
|
23
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
24
|
+
expect(decision.physicalId).toBeNull();
|
|
25
|
+
expect(decision.reason).toContain('Will create FriggMigrationStatusBucket in stack');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should respect ownership.bucket=external when bucket discovered', () => {
|
|
29
|
+
const appDefinition = {
|
|
30
|
+
migration: {
|
|
31
|
+
ownership: { bucket: 'external' }
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const discovery = createEmptyDiscoveryResult();
|
|
35
|
+
discovery.external.push({
|
|
36
|
+
physicalId: 'my-migration-bucket',
|
|
37
|
+
resourceType: 'AWS::S3::Bucket',
|
|
38
|
+
source: 'aws-discovery'
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const decision = resolver.resolveBucket(appDefinition, discovery);
|
|
42
|
+
|
|
43
|
+
expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
44
|
+
expect(decision.physicalId).toBe('my-migration-bucket');
|
|
45
|
+
expect(decision.reason).toContain('external');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should error when ownership.bucket=external but no bucket discovered', () => {
|
|
49
|
+
const appDefinition = {
|
|
50
|
+
migration: {
|
|
51
|
+
ownership: { bucket: 'external' }
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const discovery = createEmptyDiscoveryResult();
|
|
55
|
+
|
|
56
|
+
expect(() => resolver.resolveBucket(appDefinition, discovery))
|
|
57
|
+
.toThrow('ownership.bucket=external but no S3 bucket discovered');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('Auto resolution (ownership.bucket=auto)', () => {
|
|
62
|
+
it('should use stack bucket when found in CloudFormation', () => {
|
|
63
|
+
const appDefinition = {
|
|
64
|
+
migration: {
|
|
65
|
+
ownership: { bucket: 'auto' }
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const discovery = createEmptyDiscoveryResult();
|
|
69
|
+
discovery.fromCloudFormation = true;
|
|
70
|
+
discovery.stackManaged.push({
|
|
71
|
+
logicalId: 'FriggMigrationStatusBucket',
|
|
72
|
+
physicalId: 'stack-migration-bucket',
|
|
73
|
+
resourceType: 'AWS::S3::Bucket'
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const decision = resolver.resolveBucket(appDefinition, discovery);
|
|
77
|
+
|
|
78
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
79
|
+
expect(decision.physicalId).toBe('stack-migration-bucket');
|
|
80
|
+
expect(decision.reason).toContain('Found FriggMigrationStatusBucket in CloudFormation stack');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should use external bucket when found via discovery', () => {
|
|
84
|
+
const appDefinition = {
|
|
85
|
+
migration: {
|
|
86
|
+
ownership: { bucket: 'auto' }
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
const discovery = createEmptyDiscoveryResult();
|
|
90
|
+
discovery.external.push({
|
|
91
|
+
physicalId: 'external-migration-bucket',
|
|
92
|
+
resourceType: 'AWS::S3::Bucket',
|
|
93
|
+
source: 'aws-discovery'
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const decision = resolver.resolveBucket(appDefinition, discovery);
|
|
97
|
+
|
|
98
|
+
expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
99
|
+
expect(decision.physicalId).toBe('external-migration-bucket');
|
|
100
|
+
expect(decision.reason).toContain('Found external S3 bucket via discovery');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should create new bucket when none found', () => {
|
|
104
|
+
const appDefinition = {
|
|
105
|
+
migration: {
|
|
106
|
+
ownership: { bucket: 'auto' }
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const discovery = createEmptyDiscoveryResult();
|
|
110
|
+
|
|
111
|
+
const decision = resolver.resolveBucket(appDefinition, discovery);
|
|
112
|
+
|
|
113
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
114
|
+
expect(decision.physicalId).toBeNull();
|
|
115
|
+
expect(decision.reason).toContain('No existing migration bucket - will create in stack');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('Default behavior (no ownership specified)', () => {
|
|
120
|
+
it('should default to auto resolution', () => {
|
|
121
|
+
const appDefinition = {};
|
|
122
|
+
const discovery = createEmptyDiscoveryResult();
|
|
123
|
+
|
|
124
|
+
const decision = resolver.resolveBucket(appDefinition, discovery);
|
|
125
|
+
|
|
126
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
127
|
+
expect(decision.reason).toContain('No existing migration bucket - will create in stack');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('resolveQueue', () => {
|
|
133
|
+
describe('Explicit ownership intent', () => {
|
|
134
|
+
it('should respect ownership.queue=stack when specified', () => {
|
|
135
|
+
const appDefinition = {
|
|
136
|
+
migration: {
|
|
137
|
+
ownership: { queue: 'stack' }
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const discovery = createEmptyDiscoveryResult();
|
|
141
|
+
|
|
142
|
+
const decision = resolver.resolveQueue(appDefinition, discovery);
|
|
143
|
+
|
|
144
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
145
|
+
expect(decision.physicalId).toBeNull();
|
|
146
|
+
expect(decision.reason).toContain('Will create DbMigrationQueue in stack');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should respect ownership.queue=external when queue discovered', () => {
|
|
150
|
+
const appDefinition = {
|
|
151
|
+
migration: {
|
|
152
|
+
ownership: { queue: 'external' }
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const discovery = createEmptyDiscoveryResult();
|
|
156
|
+
discovery.external.push({
|
|
157
|
+
physicalId: 'https://sqs.us-east-1.amazonaws.com/123456789/my-migration-queue',
|
|
158
|
+
resourceType: 'AWS::SQS::Queue',
|
|
159
|
+
source: 'aws-discovery'
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const decision = resolver.resolveQueue(appDefinition, discovery);
|
|
163
|
+
|
|
164
|
+
expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
165
|
+
expect(decision.physicalId).toBe('https://sqs.us-east-1.amazonaws.com/123456789/my-migration-queue');
|
|
166
|
+
expect(decision.reason).toContain('external');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should error when ownership.queue=external but no queue discovered', () => {
|
|
170
|
+
const appDefinition = {
|
|
171
|
+
migration: {
|
|
172
|
+
ownership: { queue: 'external' }
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
const discovery = createEmptyDiscoveryResult();
|
|
176
|
+
|
|
177
|
+
expect(() => resolver.resolveQueue(appDefinition, discovery))
|
|
178
|
+
.toThrow('ownership.queue=external but no SQS queue discovered');
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('Auto resolution (ownership.queue=auto)', () => {
|
|
183
|
+
it('should use stack queue when found in CloudFormation', () => {
|
|
184
|
+
const appDefinition = {
|
|
185
|
+
migration: {
|
|
186
|
+
ownership: { queue: 'auto' }
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const discovery = createEmptyDiscoveryResult();
|
|
190
|
+
discovery.fromCloudFormation = true;
|
|
191
|
+
discovery.stackManaged.push({
|
|
192
|
+
logicalId: 'DbMigrationQueue',
|
|
193
|
+
physicalId: 'https://sqs.us-east-1.amazonaws.com/123456789/stack-migration-queue',
|
|
194
|
+
resourceType: 'AWS::SQS::Queue'
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const decision = resolver.resolveQueue(appDefinition, discovery);
|
|
198
|
+
|
|
199
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
200
|
+
expect(decision.physicalId).toBe('https://sqs.us-east-1.amazonaws.com/123456789/stack-migration-queue');
|
|
201
|
+
expect(decision.reason).toContain('Found DbMigrationQueue in CloudFormation stack');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should use external queue when found via discovery', () => {
|
|
205
|
+
const appDefinition = {
|
|
206
|
+
migration: {
|
|
207
|
+
ownership: { queue: 'auto' }
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
const discovery = createEmptyDiscoveryResult();
|
|
211
|
+
discovery.external.push({
|
|
212
|
+
physicalId: 'https://sqs.us-east-1.amazonaws.com/123456789/external-migration-queue',
|
|
213
|
+
resourceType: 'AWS::SQS::Queue',
|
|
214
|
+
source: 'aws-discovery'
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const decision = resolver.resolveQueue(appDefinition, discovery);
|
|
218
|
+
|
|
219
|
+
expect(decision.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
220
|
+
expect(decision.physicalId).toBe('https://sqs.us-east-1.amazonaws.com/123456789/external-migration-queue');
|
|
221
|
+
expect(decision.reason).toContain('Found external SQS queue via discovery');
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should create new queue when none found', () => {
|
|
225
|
+
const appDefinition = {
|
|
226
|
+
migration: {
|
|
227
|
+
ownership: { queue: 'auto' }
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
const discovery = createEmptyDiscoveryResult();
|
|
231
|
+
|
|
232
|
+
const decision = resolver.resolveQueue(appDefinition, discovery);
|
|
233
|
+
|
|
234
|
+
expect(decision.ownership).toBe(ResourceOwnership.STACK);
|
|
235
|
+
expect(decision.physicalId).toBeNull();
|
|
236
|
+
expect(decision.reason).toContain('No existing migration queue - will create in stack');
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('resolveAll', () => {
|
|
242
|
+
it('should return decisions for all migration resources', () => {
|
|
243
|
+
const appDefinition = {
|
|
244
|
+
database: {
|
|
245
|
+
postgres: {
|
|
246
|
+
enable: true
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
const discovery = createEmptyDiscoveryResult();
|
|
251
|
+
|
|
252
|
+
const decisions = resolver.resolveAll(appDefinition, discovery);
|
|
253
|
+
|
|
254
|
+
expect(decisions).toHaveProperty('bucket');
|
|
255
|
+
expect(decisions).toHaveProperty('queue');
|
|
256
|
+
expect(decisions.bucket.ownership).toBe(ResourceOwnership.STACK);
|
|
257
|
+
expect(decisions.queue.ownership).toBe(ResourceOwnership.STACK);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe('Real-world scenarios', () => {
|
|
262
|
+
it('should handle managementMode=managed scenario (create resources)', () => {
|
|
263
|
+
// In managed mode, we want to create resources in stack
|
|
264
|
+
const appDefinition = {
|
|
265
|
+
managementMode: 'managed',
|
|
266
|
+
migration: {
|
|
267
|
+
ownership: { bucket: 'stack', queue: 'stack' }
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
const discovery = createEmptyDiscoveryResult();
|
|
271
|
+
|
|
272
|
+
const decisions = resolver.resolveAll(appDefinition, discovery);
|
|
273
|
+
|
|
274
|
+
expect(decisions.bucket.ownership).toBe(ResourceOwnership.STACK);
|
|
275
|
+
expect(decisions.bucket.physicalId).toBeNull();
|
|
276
|
+
expect(decisions.queue.ownership).toBe(ResourceOwnership.STACK);
|
|
277
|
+
expect(decisions.queue.physicalId).toBeNull();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should handle existing stack resources (reuse)', () => {
|
|
281
|
+
// Stack already has migration resources from previous deployment
|
|
282
|
+
const appDefinition = {
|
|
283
|
+
migration: {
|
|
284
|
+
ownership: { bucket: 'auto', queue: 'auto' }
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
const discovery = createEmptyDiscoveryResult();
|
|
288
|
+
discovery.fromCloudFormation = true;
|
|
289
|
+
discovery.stackManaged.push({
|
|
290
|
+
logicalId: 'FriggMigrationStatusBucket',
|
|
291
|
+
physicalId: 'my-stack-bucket',
|
|
292
|
+
resourceType: 'AWS::S3::Bucket'
|
|
293
|
+
});
|
|
294
|
+
discovery.stackManaged.push({
|
|
295
|
+
logicalId: 'DbMigrationQueue',
|
|
296
|
+
physicalId: 'https://sqs.us-east-1.amazonaws.com/123/my-queue',
|
|
297
|
+
resourceType: 'AWS::SQS::Queue'
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const decisions = resolver.resolveAll(appDefinition, discovery);
|
|
301
|
+
|
|
302
|
+
expect(decisions.bucket.ownership).toBe(ResourceOwnership.STACK);
|
|
303
|
+
expect(decisions.bucket.physicalId).toBe('my-stack-bucket');
|
|
304
|
+
expect(decisions.queue.ownership).toBe(ResourceOwnership.STACK);
|
|
305
|
+
expect(decisions.queue.physicalId).toBe('https://sqs.us-east-1.amazonaws.com/123/my-queue');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should handle shared migration resources scenario', () => {
|
|
309
|
+
// Using shared infrastructure migration resources
|
|
310
|
+
const appDefinition = {
|
|
311
|
+
managementMode: 'managed',
|
|
312
|
+
vpcIsolation: 'shared',
|
|
313
|
+
migration: {
|
|
314
|
+
ownership: { bucket: 'auto', queue: 'auto' }
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
const discovery = createEmptyDiscoveryResult();
|
|
318
|
+
discovery.external.push({
|
|
319
|
+
physicalId: 'shared-migration-bucket',
|
|
320
|
+
resourceType: 'AWS::S3::Bucket',
|
|
321
|
+
source: 'aws-discovery'
|
|
322
|
+
});
|
|
323
|
+
discovery.external.push({
|
|
324
|
+
physicalId: 'https://sqs.us-east-1.amazonaws.com/123/shared-queue',
|
|
325
|
+
resourceType: 'AWS::SQS::Queue',
|
|
326
|
+
source: 'aws-discovery'
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
const decisions = resolver.resolveAll(appDefinition, discovery);
|
|
330
|
+
|
|
331
|
+
expect(decisions.bucket.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
332
|
+
expect(decisions.bucket.physicalId).toBe('shared-migration-bucket');
|
|
333
|
+
expect(decisions.queue.ownership).toBe(ResourceOwnership.EXTERNAL);
|
|
334
|
+
expect(decisions.queue.physicalId).toBe('https://sqs.us-east-1.amazonaws.com/123/shared-queue');
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IPropertyReconciler Port Interface
|
|
3
|
+
*
|
|
4
|
+
* Defines operations for reconciling property mismatches between CloudFormation
|
|
5
|
+
* template definitions and actual cloud resource properties. This is used by
|
|
6
|
+
* the "frigg repair --reconcile" command to fix mutable property drift.
|
|
7
|
+
*
|
|
8
|
+
* This is a port in the hexagonal architecture that will be implemented
|
|
9
|
+
* by provider-specific adapters (e.g., AWSPropertyReconciler).
|
|
10
|
+
*
|
|
11
|
+
* Purpose: Abstract property update operations from the domain layer
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
class IPropertyReconciler {
|
|
15
|
+
/**
|
|
16
|
+
* Check if a property mismatch can be auto-fixed
|
|
17
|
+
*
|
|
18
|
+
* @param {PropertyMismatch} mismatch - Property mismatch to evaluate
|
|
19
|
+
* @returns {Promise<boolean>} True if mismatch can be automatically fixed
|
|
20
|
+
*/
|
|
21
|
+
async canReconcile(mismatch) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
'IPropertyReconciler.canReconcile() must be implemented by adapter'
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Reconcile a single property mismatch
|
|
29
|
+
*
|
|
30
|
+
* @param {Object} params
|
|
31
|
+
* @param {StackIdentifier} params.stackIdentifier - Stack identifier
|
|
32
|
+
* @param {string} params.logicalId - Logical resource ID
|
|
33
|
+
* @param {PropertyMismatch} params.mismatch - Property mismatch to fix
|
|
34
|
+
* @param {string} [params.mode='template'] - Reconciliation mode:
|
|
35
|
+
* - 'template': Update CloudFormation template to match actual (default)
|
|
36
|
+
* - 'resource': Update cloud resource to match template
|
|
37
|
+
* @returns {Promise<Object>} Reconciliation result
|
|
38
|
+
* @returns {Promise<Object>} Result with properties:
|
|
39
|
+
* - success: boolean
|
|
40
|
+
* - mode: string ('template' or 'resource')
|
|
41
|
+
* - propertyPath: string
|
|
42
|
+
* - oldValue: any
|
|
43
|
+
* - newValue: any
|
|
44
|
+
* - message: string
|
|
45
|
+
* @throws {Error} If reconciliation fails
|
|
46
|
+
*/
|
|
47
|
+
async reconcileProperty({ stackIdentifier, logicalId, mismatch, mode = 'template' }) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
'IPropertyReconciler.reconcileProperty() must be implemented by adapter'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Reconcile multiple property mismatches for a resource
|
|
55
|
+
*
|
|
56
|
+
* @param {Object} params
|
|
57
|
+
* @param {StackIdentifier} params.stackIdentifier - Stack identifier
|
|
58
|
+
* @param {string} params.logicalId - Logical resource ID
|
|
59
|
+
* @param {PropertyMismatch[]} params.mismatches - Property mismatches to fix
|
|
60
|
+
* @param {string} [params.mode='template'] - Reconciliation mode
|
|
61
|
+
* @returns {Promise<Object>} Reconciliation result
|
|
62
|
+
* @returns {Promise<Object>} Result with properties:
|
|
63
|
+
* - reconciledCount: number
|
|
64
|
+
* - failedCount: number
|
|
65
|
+
* - results: Array<Object> (per-property results)
|
|
66
|
+
* - message: string
|
|
67
|
+
*/
|
|
68
|
+
async reconcileMultipleProperties({
|
|
69
|
+
stackIdentifier,
|
|
70
|
+
logicalId,
|
|
71
|
+
mismatches,
|
|
72
|
+
mode = 'template',
|
|
73
|
+
}) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
'IPropertyReconciler.reconcileMultipleProperties() must be implemented by adapter'
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Preview property reconciliation without applying changes
|
|
81
|
+
*
|
|
82
|
+
* @param {Object} params
|
|
83
|
+
* @param {StackIdentifier} params.stackIdentifier - Stack identifier
|
|
84
|
+
* @param {string} params.logicalId - Logical resource ID
|
|
85
|
+
* @param {PropertyMismatch} params.mismatch - Property mismatch to preview
|
|
86
|
+
* @param {string} [params.mode='template'] - Reconciliation mode
|
|
87
|
+
* @returns {Promise<Object>} Preview result
|
|
88
|
+
* @returns {Promise<Object>} Result with properties:
|
|
89
|
+
* - canReconcile: boolean
|
|
90
|
+
* - mode: string
|
|
91
|
+
* - propertyPath: string
|
|
92
|
+
* - currentValue: any
|
|
93
|
+
* - proposedValue: any
|
|
94
|
+
* - impact: string (description of impact)
|
|
95
|
+
* - warnings: Array<string>
|
|
96
|
+
*/
|
|
97
|
+
async previewReconciliation({ stackIdentifier, logicalId, mismatch, mode = 'template' }) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
'IPropertyReconciler.previewReconciliation() must be implemented by adapter'
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Update CloudFormation template property
|
|
105
|
+
*
|
|
106
|
+
* @param {Object} params
|
|
107
|
+
* @param {StackIdentifier} params.stackIdentifier - Stack identifier
|
|
108
|
+
* @param {string} params.logicalId - Logical resource ID
|
|
109
|
+
* @param {string} params.propertyPath - Property path (e.g., "Properties.Tags")
|
|
110
|
+
* @param {*} params.newValue - New property value
|
|
111
|
+
* @returns {Promise<Object>} Update result
|
|
112
|
+
* @returns {Promise<Object>} Result with properties:
|
|
113
|
+
* - success: boolean
|
|
114
|
+
* - changeSetId: string (CloudFormation change set ID)
|
|
115
|
+
* - message: string
|
|
116
|
+
* @throws {Error} If update fails
|
|
117
|
+
*/
|
|
118
|
+
async updateTemplateProperty({ stackIdentifier, logicalId, propertyPath, newValue }) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
'IPropertyReconciler.updateTemplateProperty() must be implemented by adapter'
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Update cloud resource property directly
|
|
126
|
+
*
|
|
127
|
+
* @param {Object} params
|
|
128
|
+
* @param {string} params.resourceType - CloudFormation resource type
|
|
129
|
+
* @param {string} params.physicalId - Physical resource ID
|
|
130
|
+
* @param {string} params.region - AWS region
|
|
131
|
+
* @param {string} params.propertyPath - Property path
|
|
132
|
+
* @param {*} params.newValue - New property value
|
|
133
|
+
* @returns {Promise<Object>} Update result
|
|
134
|
+
* @returns {Promise<Object>} Result with properties:
|
|
135
|
+
* - success: boolean
|
|
136
|
+
* - message: string
|
|
137
|
+
* - updatedAt: Date
|
|
138
|
+
* @throws {Error} If update fails or is not supported
|
|
139
|
+
*/
|
|
140
|
+
async updateResourceProperty({ resourceType, physicalId, region, propertyPath, newValue }) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
'IPropertyReconciler.updateResourceProperty() must be implemented by adapter'
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get reconciliation strategy for a resource type
|
|
148
|
+
*
|
|
149
|
+
* @param {string} resourceType - CloudFormation resource type
|
|
150
|
+
* @returns {Promise<Object>} Strategy information
|
|
151
|
+
* @returns {Promise<Object>} Strategy with properties:
|
|
152
|
+
* - supportsTemplateUpdate: boolean
|
|
153
|
+
* - supportsResourceUpdate: boolean
|
|
154
|
+
* - recommendedMode: string ('template' or 'resource')
|
|
155
|
+
* - limitations: Array<string>
|
|
156
|
+
*/
|
|
157
|
+
async getReconciliationStrategy(resourceType) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
'IPropertyReconciler.getReconciliationStrategy() must be implemented by adapter'
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = IPropertyReconciler;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IResourceDetector Port Interface
|
|
3
|
+
*
|
|
4
|
+
* Defines operations for detecting cloud resources directly (outside of CloudFormation).
|
|
5
|
+
* This is used to find orphaned resources that exist in the cloud but are not managed
|
|
6
|
+
* by CloudFormation.
|
|
7
|
+
*
|
|
8
|
+
* This is a port in the hexagonal architecture that will be implemented
|
|
9
|
+
* by provider-specific adapters (e.g., AWSResourceDetector).
|
|
10
|
+
*
|
|
11
|
+
* Purpose: Abstract cloud resource discovery APIs from the domain layer
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
class IResourceDetector {
|
|
15
|
+
/**
|
|
16
|
+
* Detect all resources of a specific type in a region
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} params
|
|
19
|
+
* @param {string} params.resourceType - CloudFormation resource type (e.g., AWS::EC2::VPC)
|
|
20
|
+
* @param {string} params.region - AWS region
|
|
21
|
+
* @param {Object} [params.filters={}] - Optional filters (e.g., tags, names)
|
|
22
|
+
* @returns {Promise<Array<Object>>} Array of detected resources
|
|
23
|
+
* @returns {Promise<Array<Object>>} Resources with properties:
|
|
24
|
+
* - physicalId: string (actual cloud resource ID)
|
|
25
|
+
* - resourceType: string (CloudFormation resource type)
|
|
26
|
+
* - properties: Object (resource properties from cloud API)
|
|
27
|
+
* - tags: Object (resource tags, if supported)
|
|
28
|
+
* - createdTime: Date (if available)
|
|
29
|
+
* @throws {Error} If resource type is not supported
|
|
30
|
+
*/
|
|
31
|
+
async detectResources({ resourceType, region, filters = {} }) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
'IResourceDetector.detectResources() must be implemented by adapter'
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get details for a specific resource
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} params
|
|
41
|
+
* @param {string} params.resourceType - CloudFormation resource type
|
|
42
|
+
* @param {string} params.physicalId - Physical resource ID
|
|
43
|
+
* @param {string} params.region - AWS region
|
|
44
|
+
* @returns {Promise<Object>} Resource details
|
|
45
|
+
* @returns {Promise<Object>} Resource with properties:
|
|
46
|
+
* - physicalId: string
|
|
47
|
+
* - resourceType: string
|
|
48
|
+
* - properties: Object (complete resource properties)
|
|
49
|
+
* - tags: Object
|
|
50
|
+
* - status: string (resource-specific status)
|
|
51
|
+
* - metadata: Object (additional resource metadata)
|
|
52
|
+
* @throws {Error} If resource does not exist
|
|
53
|
+
*/
|
|
54
|
+
async getResourceDetails({ resourceType, physicalId, region }) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
'IResourceDetector.getResourceDetails() must be implemented by adapter'
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Check if a resource exists
|
|
62
|
+
*
|
|
63
|
+
* @param {Object} params
|
|
64
|
+
* @param {string} params.resourceType - CloudFormation resource type
|
|
65
|
+
* @param {string} params.physicalId - Physical resource ID
|
|
66
|
+
* @param {string} params.region - AWS region
|
|
67
|
+
* @returns {Promise<boolean>} True if resource exists
|
|
68
|
+
*/
|
|
69
|
+
async resourceExists({ resourceType, physicalId, region }) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
'IResourceDetector.resourceExists() must be implemented by adapter'
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get list of supported resource types
|
|
77
|
+
*
|
|
78
|
+
* @returns {Promise<Array<string>>} Array of supported CloudFormation resource types
|
|
79
|
+
*/
|
|
80
|
+
async getSupportedResourceTypes() {
|
|
81
|
+
throw new Error(
|
|
82
|
+
'IResourceDetector.getSupportedResourceTypes() must be implemented by adapter'
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Detect resources by tags
|
|
88
|
+
*
|
|
89
|
+
* @param {Object} params
|
|
90
|
+
* @param {Object} params.tags - Tags to filter by (key-value pairs)
|
|
91
|
+
* @param {string} params.region - AWS region
|
|
92
|
+
* @param {string[]} [params.resourceTypes] - Optional: limit to specific resource types
|
|
93
|
+
* @returns {Promise<Array<Object>>} Array of resources matching tags
|
|
94
|
+
* @returns {Promise<Array<Object>>} Resources with properties:
|
|
95
|
+
* - physicalId: string
|
|
96
|
+
* - resourceType: string
|
|
97
|
+
* - properties: Object
|
|
98
|
+
* - tags: Object
|
|
99
|
+
*/
|
|
100
|
+
async detectResourcesByTags({ tags, region, resourceTypes = [] }) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
'IResourceDetector.detectResourcesByTags() must be implemented by adapter'
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Find orphaned resources (exist in cloud but not in any stack)
|
|
108
|
+
*
|
|
109
|
+
* @param {Object} params
|
|
110
|
+
* @param {string} params.region - AWS region
|
|
111
|
+
* @param {string[]} [params.resourceTypes] - Optional: limit to specific resource types
|
|
112
|
+
* @param {string[]} [params.excludePhysicalIds=[]] - Physical IDs to exclude from orphan check
|
|
113
|
+
* @returns {Promise<Array<Object>>} Array of orphaned resources
|
|
114
|
+
* @returns {Promise<Array<Object>>} Resources with properties:
|
|
115
|
+
* - physicalId: string
|
|
116
|
+
* - resourceType: string
|
|
117
|
+
* - properties: Object
|
|
118
|
+
* - tags: Object
|
|
119
|
+
* - isOrphaned: boolean (always true)
|
|
120
|
+
* - reason: string (explanation of why it's orphaned)
|
|
121
|
+
*/
|
|
122
|
+
async findOrphanedResources({ region, resourceTypes = [], excludePhysicalIds = [] }) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
'IResourceDetector.findOrphanedResources() must be implemented by adapter'
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = IResourceDetector;
|