@friggframework/devtools 2.0.0--canary.474.da7b114.0 → 2.0.0--canary.474.82ba370.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/frigg-cli/CLI.md +69 -14
- package/infrastructure/MULTI_CLOUD_ARCHITECTURE.md +63 -379
- package/infrastructure/domains/health/domain/entities/issue.js +250 -0
- package/infrastructure/domains/health/domain/entities/issue.test.js +417 -0
- package/infrastructure/domains/health/domain/entities/resource.js +159 -0
- package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
- package/package.json +6 -6
package/frigg-cli/CLI.md
CHANGED
|
@@ -10,6 +10,32 @@ The Frigg CLI provides tools for building, deploying, and managing serverless in
|
|
|
10
10
|
|
|
11
11
|
### Core Commands
|
|
12
12
|
|
|
13
|
+
#### `frigg init [options]`
|
|
14
|
+
|
|
15
|
+
**Status:** To be documented (command may not be merged yet)
|
|
16
|
+
|
|
17
|
+
Initialize a new Frigg application with scaffolding and configuration.
|
|
18
|
+
|
|
19
|
+
**Usage:**
|
|
20
|
+
```bash
|
|
21
|
+
frigg init
|
|
22
|
+
frigg init my-app
|
|
23
|
+
frigg init --template typescript
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**What it does:**
|
|
27
|
+
- TBD - Full documentation pending implementation merge
|
|
28
|
+
|
|
29
|
+
**Options:**
|
|
30
|
+
- TBD
|
|
31
|
+
|
|
32
|
+
**Example Output:**
|
|
33
|
+
- TBD
|
|
34
|
+
|
|
35
|
+
> **Note**: This command may be part of an upcoming release. Documentation will be updated once the implementation is merged to the main branch.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
13
39
|
#### `frigg install <module-name>`
|
|
14
40
|
|
|
15
41
|
Install and configure an API integration module from the Frigg module library.
|
|
@@ -155,23 +181,30 @@ frigg deploy --stage production
|
|
|
155
181
|
frigg deploy --region us-west-2
|
|
156
182
|
frigg deploy --force
|
|
157
183
|
frigg deploy --skip-env-validation
|
|
184
|
+
frigg deploy --skip-doctor # Skip health check (not recommended)
|
|
185
|
+
frigg deploy --no-interactive # CI/CD mode (no prompts, auto-repair safe issues)
|
|
158
186
|
```
|
|
159
187
|
|
|
160
188
|
**What it does:**
|
|
161
|
-
1.
|
|
162
|
-
2.
|
|
163
|
-
3.
|
|
164
|
-
4.
|
|
165
|
-
5.
|
|
166
|
-
6.
|
|
167
|
-
7.
|
|
168
|
-
8.
|
|
189
|
+
1. **Runs health check** (`frigg doctor`) to detect infrastructure issues
|
|
190
|
+
2. **Auto-repairs safe issues** (or prompts for confirmation in interactive mode)
|
|
191
|
+
3. **Fails if critical issues found** (unless `--skip-doctor` flag used)
|
|
192
|
+
4. Loads app definition from index.js
|
|
193
|
+
5. Validates required environment variables
|
|
194
|
+
6. Discovers existing cloud resources (VPC, KMS, Aurora, etc.)
|
|
195
|
+
7. Makes ownership decisions (resources managed in CloudFormation stack)
|
|
196
|
+
8. Generates CloudFormation resources
|
|
197
|
+
9. Generates serverless.yml configuration
|
|
198
|
+
10. Executes `osls deploy` with filtered environment
|
|
199
|
+
11. Creates/updates stack in cloud provider
|
|
169
200
|
|
|
170
201
|
**Options:**
|
|
171
202
|
- `--stage <stage>` - Deployment stage (default: 'dev')
|
|
172
203
|
- `--region <region>` - Cloud provider region (overrides app definition)
|
|
173
204
|
- `--force` - Force deployment even if no changes detected
|
|
174
205
|
- `--skip-env-validation` - Skip environment variable validation warnings
|
|
206
|
+
- `--skip-doctor` - Skip infrastructure health check ⚠️ Not recommended
|
|
207
|
+
- `--no-interactive` - Non-interactive mode for CI/CD (auto-repair safe issues, fail on risky changes)
|
|
175
208
|
- `--verbose` - Enable verbose logging
|
|
176
209
|
|
|
177
210
|
**Example Output:**
|
|
@@ -222,11 +255,33 @@ Deployment completed in 3m 42s
|
|
|
222
255
|
- Validates required variables exist
|
|
223
256
|
- Warns about missing optional variables
|
|
224
257
|
|
|
225
|
-
**Resource
|
|
226
|
-
-
|
|
227
|
-
-
|
|
228
|
-
-
|
|
229
|
-
-
|
|
258
|
+
**Resource Ownership:**
|
|
259
|
+
- Resources can be marked as `STACK` (managed in CloudFormation) or `EXTERNAL` (use existing by ID)
|
|
260
|
+
- Default behavior: manage resources in CloudFormation stack for full lifecycle control
|
|
261
|
+
- Doctor/repair handles edge cases (orphaned resources, drift) before deployment
|
|
262
|
+
- Explicit ownership in app definition recommended for production
|
|
263
|
+
|
|
264
|
+
**CI/CD Deployment:**
|
|
265
|
+
- Use `--no-interactive` flag to prevent prompts
|
|
266
|
+
- Deployment will auto-fix safe issues (mutable property drift)
|
|
267
|
+
- Deployment will **fail fast** on risky issues (orphaned resources, immutable property changes)
|
|
268
|
+
- Prevents stack rollbacks and 2+ hour waits from bad deployments
|
|
269
|
+
- Returns non-zero exit code if issues require manual intervention
|
|
270
|
+
|
|
271
|
+
**Example CI/CD Pipeline:**
|
|
272
|
+
```yaml
|
|
273
|
+
# GitHub Actions / GitLab CI / etc.
|
|
274
|
+
- name: Deploy to production
|
|
275
|
+
run: |
|
|
276
|
+
frigg deploy \
|
|
277
|
+
--stage production \
|
|
278
|
+
--no-interactive \
|
|
279
|
+
--skip-env-validation
|
|
280
|
+
env:
|
|
281
|
+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
282
|
+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
283
|
+
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
284
|
+
```
|
|
230
285
|
|
|
231
286
|
---
|
|
232
287
|
|
|
@@ -566,7 +621,7 @@ frigg repair # Interactive mode
|
|
|
566
621
|
frigg repair --import # Import orphaned resources
|
|
567
622
|
frigg repair --reconcile # Fix property mismatches
|
|
568
623
|
frigg repair --clean-deploy # Delete and recreate resources
|
|
569
|
-
frigg repair --auto # Auto-fix all fixable issues
|
|
624
|
+
frigg repair --auto # Auto-fix all fixable issues (CI/CD mode)
|
|
570
625
|
frigg repair --dry-run # Show what would be fixed
|
|
571
626
|
frigg repair --issue <id> # Fix specific issue only
|
|
572
627
|
```
|
|
@@ -197,444 +197,128 @@ packages/devtools/infrastructure/
|
|
|
197
197
|
|
|
198
198
|
## Port Interfaces (Contracts)
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
Port interfaces define the contracts that provider-specific adapters must implement. These are the boundaries between the provider-agnostic domain layer and provider-specific infrastructure.
|
|
201
201
|
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Port: Stack Repository Interface
|
|
205
|
-
*
|
|
206
|
-
* Abstracts stack management operations (CloudFormation, Deployment Manager, ARM)
|
|
207
|
-
*/
|
|
208
|
-
class IStackRepository {
|
|
209
|
-
/**
|
|
210
|
-
* Get stack by identifier
|
|
211
|
-
* @param {StackIdentifier} identifier
|
|
212
|
-
* @returns {Promise<Stack|null>}
|
|
213
|
-
*/
|
|
214
|
-
async getStack(identifier) {
|
|
215
|
-
throw new Error('Not implemented');
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* List resources in stack
|
|
220
|
-
* @param {StackIdentifier} identifier
|
|
221
|
-
* @returns {Promise<Resource[]>}
|
|
222
|
-
*/
|
|
223
|
-
async listResources(identifier) {
|
|
224
|
-
throw new Error('Not implemented');
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Get stack outputs
|
|
229
|
-
* @param {StackIdentifier} identifier
|
|
230
|
-
* @returns {Promise<Object>}
|
|
231
|
-
*/
|
|
232
|
-
async getOutputs(identifier) {
|
|
233
|
-
throw new Error('Not implemented');
|
|
234
|
-
}
|
|
202
|
+
**Source files**: `packages/devtools/infrastructure/domains/health/application/ports/`
|
|
235
203
|
|
|
236
|
-
|
|
237
|
-
* Get stack parameters
|
|
238
|
-
* @param {StackIdentifier} identifier
|
|
239
|
-
* @returns {Promise<Object>}
|
|
240
|
-
*/
|
|
241
|
-
async getParameters(identifier) {
|
|
242
|
-
throw new Error('Not implemented');
|
|
243
|
-
}
|
|
204
|
+
### Key Ports
|
|
244
205
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
throw new Error('Not implemented');
|
|
252
|
-
}
|
|
253
|
-
}
|
|
206
|
+
**IStackRepository** - Stack management operations (CloudFormation, Deployment Manager, ARM)
|
|
207
|
+
```javascript
|
|
208
|
+
// Example methods:
|
|
209
|
+
async getStack(identifier)
|
|
210
|
+
async listResources(identifier)
|
|
211
|
+
async getOutputs(identifier)
|
|
254
212
|
```
|
|
213
|
+
📄 See: `application/ports/IStackRepository.js`
|
|
255
214
|
|
|
256
|
-
|
|
257
|
-
|
|
215
|
+
**IResourceDetector** - Cloud resource discovery (AWS APIs, GCP APIs, Azure APIs)
|
|
258
216
|
```javascript
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
class IResourceDetector {
|
|
265
|
-
/**
|
|
266
|
-
* Detect VPCs/Networks
|
|
267
|
-
* @param {string} region
|
|
268
|
-
* @returns {Promise<NetworkResource[]>}
|
|
269
|
-
*/
|
|
270
|
-
async detectNetworks(region) {
|
|
271
|
-
throw new Error('Not implemented');
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Detect database instances
|
|
276
|
-
* @param {string} region
|
|
277
|
-
* @returns {Promise<DatabaseResource[]>}
|
|
278
|
-
*/
|
|
279
|
-
async detectDatabases(region) {
|
|
280
|
-
throw new Error('Not implemented');
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Detect encryption keys
|
|
285
|
-
* @param {string} region
|
|
286
|
-
* @returns {Promise<KeyResource[]>}
|
|
287
|
-
*/
|
|
288
|
-
async detectKeys(region) {
|
|
289
|
-
throw new Error('Not implemented');
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Detect resource by physical ID
|
|
294
|
-
* @param {string} physicalId
|
|
295
|
-
* @param {string} resourceType
|
|
296
|
-
* @returns {Promise<Resource|null>}
|
|
297
|
-
*/
|
|
298
|
-
async detectResourceById(physicalId, resourceType) {
|
|
299
|
-
throw new Error('Not implemented');
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Get resource properties
|
|
304
|
-
* @param {string} physicalId
|
|
305
|
-
* @param {string} resourceType
|
|
306
|
-
* @returns {Promise<Object>}
|
|
307
|
-
*/
|
|
308
|
-
async getResourceProperties(physicalId, resourceType) {
|
|
309
|
-
throw new Error('Not implemented');
|
|
310
|
-
}
|
|
311
|
-
}
|
|
217
|
+
// Example methods:
|
|
218
|
+
async detectNetworks(region)
|
|
219
|
+
async detectDatabases(region)
|
|
220
|
+
async detectKeys(region)
|
|
221
|
+
async detectResourceById(physicalId, resourceType)
|
|
312
222
|
```
|
|
223
|
+
📄 See: `application/ports/IResourceDetector.js`
|
|
313
224
|
|
|
314
|
-
|
|
315
|
-
|
|
225
|
+
**IDriftDetector** - Compare desired state vs actual state
|
|
316
226
|
```javascript
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
* Abstracts drift detection logic
|
|
321
|
-
*/
|
|
322
|
-
class IDriftDetector {
|
|
323
|
-
/**
|
|
324
|
-
* Detect drift for a resource
|
|
325
|
-
* @param {Resource} resource - Resource from stack
|
|
326
|
-
* @param {Object} desiredProperties - Desired properties
|
|
327
|
-
* @returns {Promise<PropertyMismatch[]>}
|
|
328
|
-
*/
|
|
329
|
-
async detectDrift(resource, desiredProperties) {
|
|
330
|
-
throw new Error('Not implemented');
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Detect drift for entire stack
|
|
335
|
-
* @param {StackIdentifier} identifier
|
|
336
|
-
* @returns {Promise<DriftDetectionResult>}
|
|
337
|
-
*/
|
|
338
|
-
async detectStackDrift(identifier) {
|
|
339
|
-
throw new Error('Not implemented');
|
|
340
|
-
}
|
|
341
|
-
}
|
|
227
|
+
// Example methods:
|
|
228
|
+
async detectDrift(resource, desiredProperties)
|
|
229
|
+
async detectStackDrift(identifier)
|
|
342
230
|
```
|
|
231
|
+
📄 See: `application/ports/IDriftDetector.js`
|
|
343
232
|
|
|
344
|
-
|
|
345
|
-
|
|
233
|
+
**IResourceImporter** - Import existing resources into stack
|
|
346
234
|
```javascript
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
*/
|
|
352
|
-
class IResourceImporter {
|
|
353
|
-
/**
|
|
354
|
-
* Check if resource type is importable
|
|
355
|
-
* @param {string} resourceType
|
|
356
|
-
* @returns {boolean}
|
|
357
|
-
*/
|
|
358
|
-
isImportable(resourceType) {
|
|
359
|
-
throw new Error('Not implemented');
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* Create import change set
|
|
364
|
-
* @param {StackIdentifier} stackId
|
|
365
|
-
* @param {Resource[]} resources
|
|
366
|
-
* @returns {Promise<ImportChangeSet>}
|
|
367
|
-
*/
|
|
368
|
-
async createImportChangeSet(stackId, resources) {
|
|
369
|
-
throw new Error('Not implemented');
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Execute import operation
|
|
374
|
-
* @param {ImportChangeSet} changeSet
|
|
375
|
-
* @returns {Promise<ImportResult>}
|
|
376
|
-
*/
|
|
377
|
-
async executeImport(changeSet) {
|
|
378
|
-
throw new Error('Not implemented');
|
|
379
|
-
}
|
|
380
|
-
}
|
|
235
|
+
// Example methods:
|
|
236
|
+
isImportable(resourceType)
|
|
237
|
+
async createImportChangeSet(stackId, resources)
|
|
238
|
+
async executeImport(changeSet)
|
|
381
239
|
```
|
|
240
|
+
📄 See: `application/ports/IResourceImporter.js`
|
|
382
241
|
|
|
383
|
-
|
|
384
|
-
|
|
242
|
+
**IPropertyReconciler** - Fix property mismatches
|
|
385
243
|
```javascript
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
* Abstracts property reconciliation logic
|
|
390
|
-
*/
|
|
391
|
-
class IPropertyReconciler {
|
|
392
|
-
/**
|
|
393
|
-
* Reconcile property mismatch
|
|
394
|
-
* @param {PropertyMismatch} mismatch
|
|
395
|
-
* @param {Resource} resource
|
|
396
|
-
* @returns {Promise<ReconciliationResult>}
|
|
397
|
-
*/
|
|
398
|
-
async reconcile(mismatch, resource) {
|
|
399
|
-
throw new Error('Not implemented');
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Plan reconciliation (dry run)
|
|
404
|
-
* @param {PropertyMismatch[]} mismatches
|
|
405
|
-
* @returns {Promise<ReconciliationPlan>}
|
|
406
|
-
*/
|
|
407
|
-
async planReconciliation(mismatches) {
|
|
408
|
-
throw new Error('Not implemented');
|
|
409
|
-
}
|
|
410
|
-
}
|
|
244
|
+
// Example methods:
|
|
245
|
+
async reconcile(mismatch, resource)
|
|
246
|
+
async planReconciliation(mismatches)
|
|
411
247
|
```
|
|
248
|
+
📄 See: `application/ports/IPropertyReconciler.js`
|
|
249
|
+
|
|
250
|
+
> **Note**: Full interface definitions are maintained in source files. See the actual TypeScript/JSDoc definitions for complete method signatures and documentation.
|
|
412
251
|
|
|
413
252
|
---
|
|
414
253
|
|
|
415
254
|
## AWS Adapter Implementations
|
|
416
255
|
|
|
256
|
+
AWS-specific implementations of the port interfaces using AWS SDK v3.
|
|
257
|
+
|
|
258
|
+
**Source files**: `packages/devtools/infrastructure/domains/health/infrastructure/adapters/aws/`
|
|
259
|
+
|
|
417
260
|
### AWSStackRepository
|
|
418
261
|
|
|
419
|
-
|
|
420
|
-
const { CloudFormationClient, DescribeStacksCommand, ListStackResourcesCommand } = require('@aws-sdk/client-cloudformation');
|
|
421
|
-
const IStackRepository = require('../../application/ports/IStackRepository');
|
|
262
|
+
Implements `IStackRepository` using CloudFormation API.
|
|
422
263
|
|
|
264
|
+
```javascript
|
|
423
265
|
class AWSStackRepository extends IStackRepository {
|
|
424
266
|
constructor({ region }) {
|
|
425
|
-
super();
|
|
426
267
|
this.client = new CloudFormationClient({ region });
|
|
427
268
|
}
|
|
428
269
|
|
|
429
270
|
async getStack(identifier) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
try {
|
|
435
|
-
const response = await this.client.send(command);
|
|
436
|
-
return response.Stacks[0] || null;
|
|
437
|
-
} catch (error) {
|
|
438
|
-
if (error.name === 'ValidationError') {
|
|
439
|
-
return null; // Stack doesn't exist
|
|
440
|
-
}
|
|
441
|
-
throw error;
|
|
442
|
-
}
|
|
271
|
+
// Uses DescribeStacksCommand
|
|
272
|
+
// Returns Stack or null if not found
|
|
443
273
|
}
|
|
444
274
|
|
|
445
275
|
async listResources(identifier) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
const response = await this.client.send(command);
|
|
451
|
-
|
|
452
|
-
return response.StackResourceSummaries.map(resource => ({
|
|
453
|
-
logicalId: resource.LogicalResourceId,
|
|
454
|
-
physicalId: resource.PhysicalResourceId,
|
|
455
|
-
type: resource.ResourceType,
|
|
456
|
-
status: resource.ResourceStatus,
|
|
457
|
-
timestamp: resource.LastUpdatedTimestamp,
|
|
458
|
-
}));
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
async getOutputs(identifier) {
|
|
462
|
-
const stack = await this.getStack(identifier);
|
|
463
|
-
if (!stack) return {};
|
|
464
|
-
|
|
465
|
-
return (stack.Outputs || []).reduce((acc, output) => {
|
|
466
|
-
acc[output.OutputKey] = output.OutputValue;
|
|
467
|
-
return acc;
|
|
468
|
-
}, {});
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
async exists(identifier) {
|
|
472
|
-
return (await this.getStack(identifier)) !== null;
|
|
276
|
+
// Uses ListStackResourcesCommand
|
|
277
|
+
// Maps to standard resource format
|
|
473
278
|
}
|
|
474
279
|
}
|
|
475
|
-
|
|
476
|
-
module.exports = AWSStackRepository;
|
|
477
280
|
```
|
|
478
281
|
|
|
282
|
+
📄 See full implementation: `adapters/aws/AWSStackRepository.js`
|
|
283
|
+
|
|
479
284
|
### AWSResourceDetector
|
|
480
285
|
|
|
481
|
-
|
|
482
|
-
const { EC2Client, DescribeVpcsCommand, DescribeSubnetsCommand } = require('@aws-sdk/client-ec2');
|
|
483
|
-
const { RDSClient, DescribeDBClustersCommand } = require('@aws-sdk/client-rds');
|
|
484
|
-
const { KMSClient, ListKeysCommand, DescribeKeyCommand } = require('@aws-sdk/client-kms');
|
|
485
|
-
const IResourceDetector = require('../../application/ports/IResourceDetector');
|
|
286
|
+
Implements `IResourceDetector` using AWS service APIs (EC2, RDS, KMS, etc.).
|
|
486
287
|
|
|
288
|
+
```javascript
|
|
487
289
|
class AWSResourceDetector extends IResourceDetector {
|
|
488
290
|
constructor({ region }) {
|
|
489
|
-
super();
|
|
490
|
-
this.region = region;
|
|
491
291
|
this.ec2 = new EC2Client({ region });
|
|
492
292
|
this.rds = new RDSClient({ region });
|
|
493
293
|
this.kms = new KMSClient({ region });
|
|
494
294
|
}
|
|
495
295
|
|
|
496
296
|
async detectNetworks(region) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
return response.Vpcs.map(vpc => ({
|
|
501
|
-
type: 'AWS::EC2::VPC',
|
|
502
|
-
physicalId: vpc.VpcId,
|
|
503
|
-
properties: {
|
|
504
|
-
CidrBlock: vpc.CidrBlock,
|
|
505
|
-
Tags: vpc.Tags,
|
|
506
|
-
IsDefault: vpc.IsDefault,
|
|
507
|
-
},
|
|
508
|
-
}));
|
|
297
|
+
// Uses DescribeVpcsCommand
|
|
298
|
+
// Returns standardized network resources
|
|
509
299
|
}
|
|
510
300
|
|
|
511
301
|
async detectDatabases(region) {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
return response.DBClusters.map(cluster => ({
|
|
516
|
-
type: 'AWS::RDS::DBCluster',
|
|
517
|
-
physicalId: cluster.DBClusterIdentifier,
|
|
518
|
-
properties: {
|
|
519
|
-
Engine: cluster.Engine,
|
|
520
|
-
EngineVersion: cluster.EngineVersion,
|
|
521
|
-
DatabaseName: cluster.DatabaseName,
|
|
522
|
-
MasterUsername: cluster.MasterUsername,
|
|
523
|
-
Port: cluster.Port,
|
|
524
|
-
},
|
|
525
|
-
}));
|
|
302
|
+
// Uses DescribeDBClustersCommand
|
|
303
|
+
// Returns standardized database resources
|
|
526
304
|
}
|
|
527
305
|
|
|
528
306
|
async detectKeys(region) {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
const keys = [];
|
|
533
|
-
for (const key of response.Keys) {
|
|
534
|
-
const describeCommand = new DescribeKeyCommand({ KeyId: key.KeyId });
|
|
535
|
-
const keyDetails = await this.kms.send(describeCommand);
|
|
536
|
-
|
|
537
|
-
keys.push({
|
|
538
|
-
type: 'AWS::KMS::Key',
|
|
539
|
-
physicalId: keyDetails.KeyMetadata.KeyId,
|
|
540
|
-
properties: {
|
|
541
|
-
Description: keyDetails.KeyMetadata.Description,
|
|
542
|
-
Enabled: keyDetails.KeyMetadata.Enabled,
|
|
543
|
-
KeyUsage: keyDetails.KeyMetadata.KeyUsage,
|
|
544
|
-
},
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
return keys;
|
|
307
|
+
// Uses ListKeysCommand + DescribeKeyCommand
|
|
308
|
+
// Returns standardized key resources
|
|
549
309
|
}
|
|
310
|
+
}
|
|
311
|
+
```
|
|
550
312
|
|
|
551
|
-
|
|
552
|
-
// Route to appropriate detector based on resource type
|
|
553
|
-
switch (resourceType) {
|
|
554
|
-
case 'AWS::EC2::VPC':
|
|
555
|
-
return this.detectVpcById(physicalId);
|
|
556
|
-
case 'AWS::RDS::DBCluster':
|
|
557
|
-
return this.detectClusterById(physicalId);
|
|
558
|
-
case 'AWS::KMS::Key':
|
|
559
|
-
return this.detectKeyById(physicalId);
|
|
560
|
-
default:
|
|
561
|
-
throw new Error(`Unsupported resource type: ${resourceType}`);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
async getResourceProperties(physicalId, resourceType) {
|
|
566
|
-
const resource = await this.detectResourceById(physicalId, resourceType);
|
|
567
|
-
return resource ? resource.properties : null;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// Private helper methods
|
|
571
|
-
async detectVpcById(vpcId) {
|
|
572
|
-
const command = new DescribeVpcsCommand({ VpcIds: [vpcId] });
|
|
573
|
-
const response = await this.ec2.send(command);
|
|
574
|
-
const vpc = response.Vpcs[0];
|
|
575
|
-
|
|
576
|
-
if (!vpc) return null;
|
|
577
|
-
|
|
578
|
-
return {
|
|
579
|
-
type: 'AWS::EC2::VPC',
|
|
580
|
-
physicalId: vpc.VpcId,
|
|
581
|
-
properties: {
|
|
582
|
-
CidrBlock: vpc.CidrBlock,
|
|
583
|
-
Tags: vpc.Tags,
|
|
584
|
-
IsDefault: vpc.IsDefault,
|
|
585
|
-
},
|
|
586
|
-
};
|
|
587
|
-
}
|
|
313
|
+
📄 See full implementation: `adapters/aws/AWSResourceDetector.js`
|
|
588
314
|
|
|
589
|
-
|
|
590
|
-
const command = new DescribeDBClustersCommand({ DBClusterIdentifier: clusterId });
|
|
591
|
-
try {
|
|
592
|
-
const response = await this.rds.send(command);
|
|
593
|
-
const cluster = response.DBClusters[0];
|
|
594
|
-
|
|
595
|
-
return {
|
|
596
|
-
type: 'AWS::RDS::DBCluster',
|
|
597
|
-
physicalId: cluster.DBClusterIdentifier,
|
|
598
|
-
properties: {
|
|
599
|
-
Engine: cluster.Engine,
|
|
600
|
-
EngineVersion: cluster.EngineVersion,
|
|
601
|
-
DatabaseName: cluster.DatabaseName,
|
|
602
|
-
MasterUsername: cluster.MasterUsername,
|
|
603
|
-
Port: cluster.Port,
|
|
604
|
-
},
|
|
605
|
-
};
|
|
606
|
-
} catch (error) {
|
|
607
|
-
if (error.name === 'DBClusterNotFoundFault') {
|
|
608
|
-
return null;
|
|
609
|
-
}
|
|
610
|
-
throw error;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
315
|
+
### Other AWS Adapters
|
|
613
316
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
const response = await this.kms.send(command);
|
|
618
|
-
return {
|
|
619
|
-
type: 'AWS::KMS::Key',
|
|
620
|
-
physicalId: response.KeyMetadata.KeyId,
|
|
621
|
-
properties: {
|
|
622
|
-
Description: response.KeyMetadata.Description,
|
|
623
|
-
Enabled: response.KeyMetadata.Enabled,
|
|
624
|
-
KeyUsage: response.KeyMetadata.KeyUsage,
|
|
625
|
-
},
|
|
626
|
-
};
|
|
627
|
-
} catch (error) {
|
|
628
|
-
if (error.name === 'NotFoundException') {
|
|
629
|
-
return null;
|
|
630
|
-
}
|
|
631
|
-
throw error;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
}
|
|
317
|
+
- **AWSDriftDetector** - Uses CloudFormation drift detection API
|
|
318
|
+
- **AWSResourceImporter** - Uses CloudFormation import change sets
|
|
319
|
+
- **AWSPropertyReconciler** - Uses CloudFormation update stacks
|
|
635
320
|
|
|
636
|
-
|
|
637
|
-
```
|
|
321
|
+
📄 See: `adapters/aws/` directory for all implementations
|
|
638
322
|
|
|
639
323
|
---
|
|
640
324
|
|