@friggframework/devtools 2.0.0--canary.461.6b7bf79.0 → 2.0.0--canary.461.77c8d12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/infrastructure/domains/networking/vpc-builder.js +1 -1
- package/infrastructure/domains/shared/cloudformation-discovery.js +111 -7
- package/infrastructure/domains/shared/cloudformation-discovery.test.js +229 -0
- package/infrastructure/domains/shared/providers/aws-provider-adapter.js +21 -1
- package/infrastructure/domains/shared/resource-discovery.js +2 -1
- package/package.json +6 -6
|
@@ -989,7 +989,7 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
989
989
|
}
|
|
990
990
|
|
|
991
991
|
// VPC Endpoint Security Group (only if KMS, Secrets Manager, or SQS are not stack-managed and missing)
|
|
992
|
-
const needsSecurityGroup =
|
|
992
|
+
const needsSecurityGroup =
|
|
993
993
|
(!stackManagedEndpoints.kms && !existingEndpoints.kms && appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') ||
|
|
994
994
|
(!stackManagedEndpoints.secretsManager && !existingEndpoints.secretsManager) ||
|
|
995
995
|
(!stackManagedEndpoints.sqs && !existingEndpoints.sqs);
|
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
class CloudFormationDiscovery {
|
|
17
|
-
constructor(provider) {
|
|
17
|
+
constructor(provider, config = {}) {
|
|
18
18
|
this.provider = provider;
|
|
19
|
+
this.serviceName = config.serviceName;
|
|
20
|
+
this.stage = config.stage;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
/**
|
|
@@ -41,9 +43,8 @@ class CloudFormationDiscovery {
|
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
// Extract from resources (now async to query AWS for details)
|
|
44
|
-
if
|
|
45
|
-
|
|
46
|
-
}
|
|
46
|
+
// Always call this even if resources is empty, as it may query AWS for resources
|
|
47
|
+
await this._extractFromResources(resources || [], discovered);
|
|
47
48
|
|
|
48
49
|
return discovered;
|
|
49
50
|
} catch (error) {
|
|
@@ -108,9 +109,15 @@ class CloudFormationDiscovery {
|
|
|
108
109
|
* @param {Object} discovered - Object to populate with discovered resources
|
|
109
110
|
*/
|
|
110
111
|
async _extractFromResources(resources, discovered) {
|
|
112
|
+
console.log(` DEBUG: Processing ${resources.length} CloudFormation resources...`);
|
|
111
113
|
for (const resource of resources) {
|
|
112
114
|
const { LogicalResourceId, PhysicalResourceId, ResourceType } = resource;
|
|
113
115
|
|
|
116
|
+
// Debug Aurora detection
|
|
117
|
+
if (LogicalResourceId.includes('Aurora')) {
|
|
118
|
+
console.log(` DEBUG: Found Aurora resource: ${LogicalResourceId} (${ResourceType})`);
|
|
119
|
+
}
|
|
120
|
+
|
|
114
121
|
// Security Group - use to get VPC ID
|
|
115
122
|
if (LogicalResourceId === 'FriggLambdaSecurityGroup' && ResourceType === 'AWS::EC2::SecurityGroup') {
|
|
116
123
|
discovered.securityGroupId = PhysicalResourceId;
|
|
@@ -141,21 +148,21 @@ class CloudFormationDiscovery {
|
|
|
141
148
|
if (LogicalResourceId === 'FriggAuroraCluster' && ResourceType === 'AWS::RDS::DBCluster') {
|
|
142
149
|
discovered.auroraClusterId = PhysicalResourceId;
|
|
143
150
|
console.log(` ✓ Found Aurora cluster in stack: ${PhysicalResourceId}`);
|
|
144
|
-
|
|
151
|
+
|
|
145
152
|
// Query RDS to get cluster endpoint
|
|
146
153
|
if (this.provider && !discovered.auroraClusterEndpoint) {
|
|
147
154
|
try {
|
|
148
155
|
console.log(` Querying RDS to get Aurora endpoint...`);
|
|
149
156
|
const { DescribeDBClustersCommand } = require('@aws-sdk/client-rds');
|
|
150
157
|
const { RDSClient } = require('@aws-sdk/client-rds');
|
|
151
|
-
|
|
158
|
+
|
|
152
159
|
const rdsClient = new RDSClient({ region: this.provider.region });
|
|
153
160
|
const clusterDetails = await rdsClient.send(
|
|
154
161
|
new DescribeDBClustersCommand({
|
|
155
162
|
DBClusterIdentifier: PhysicalResourceId
|
|
156
163
|
})
|
|
157
164
|
);
|
|
158
|
-
|
|
165
|
+
|
|
159
166
|
if (clusterDetails.DBClusters && clusterDetails.DBClusters.length > 0) {
|
|
160
167
|
const cluster = clusterDetails.DBClusters[0];
|
|
161
168
|
discovered.auroraClusterEndpoint = cluster.Endpoint;
|
|
@@ -194,6 +201,30 @@ class CloudFormationDiscovery {
|
|
|
194
201
|
}
|
|
195
202
|
}
|
|
196
203
|
|
|
204
|
+
// KMS Key Alias - query to get the actual key ARN
|
|
205
|
+
if (LogicalResourceId === 'FriggKMSKeyAlias' && ResourceType === 'AWS::KMS::Alias') {
|
|
206
|
+
discovered.kmsKeyAlias = PhysicalResourceId;
|
|
207
|
+
console.log(` ✓ Found KMS key alias in stack: ${PhysicalResourceId}`);
|
|
208
|
+
|
|
209
|
+
// Query KMS to get the key ARN that this alias points to
|
|
210
|
+
// Always query even if key is already set, to ensure consistency
|
|
211
|
+
if (this.provider && this.provider.describeKmsKey) {
|
|
212
|
+
try {
|
|
213
|
+
console.log(` Querying KMS to get key ARN from alias...`);
|
|
214
|
+
const keyMetadata = await this.provider.describeKmsKey(PhysicalResourceId);
|
|
215
|
+
|
|
216
|
+
if (keyMetadata) {
|
|
217
|
+
discovered.defaultKmsKeyId = keyMetadata.Arn;
|
|
218
|
+
console.log(` ✓ Extracted KMS key ARN from alias: ${discovered.defaultKmsKeyId}`);
|
|
219
|
+
} else {
|
|
220
|
+
console.warn(` ⚠️ KMS key query returned no metadata`);
|
|
221
|
+
}
|
|
222
|
+
} catch (error) {
|
|
223
|
+
console.warn(` ⚠️ Could not get key ARN from alias: ${error.message}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
197
228
|
// Subnets
|
|
198
229
|
if (LogicalResourceId === 'FriggPrivateSubnet1' && ResourceType === 'AWS::EC2::Subnet') {
|
|
199
230
|
discovered.privateSubnetId1 = PhysicalResourceId;
|
|
@@ -235,6 +266,79 @@ class CloudFormationDiscovery {
|
|
|
235
266
|
discovered.sqsVpcEndpointId = PhysicalResourceId;
|
|
236
267
|
}
|
|
237
268
|
}
|
|
269
|
+
|
|
270
|
+
// If we have a VPC ID but no subnet IDs, query EC2 for Frigg-managed subnets
|
|
271
|
+
if (discovered.defaultVpcId && this.provider &&
|
|
272
|
+
!discovered.privateSubnetId1 && !discovered.publicSubnetId1) {
|
|
273
|
+
try {
|
|
274
|
+
console.log(' Querying EC2 for Frigg-managed subnets...');
|
|
275
|
+
const { DescribeSubnetsCommand } = require('@aws-sdk/client-ec2');
|
|
276
|
+
const subnetResponse = await this.provider.getEC2Client().send(
|
|
277
|
+
new DescribeSubnetsCommand({
|
|
278
|
+
Filters: [
|
|
279
|
+
{ Name: 'vpc-id', Values: [discovered.defaultVpcId] },
|
|
280
|
+
{ Name: 'tag:ManagedBy', Values: ['Frigg'] },
|
|
281
|
+
],
|
|
282
|
+
})
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
if (subnetResponse.Subnets && subnetResponse.Subnets.length > 0) {
|
|
286
|
+
// Extract subnet IDs by logical ID from tags
|
|
287
|
+
const subnets = subnetResponse.Subnets.map(subnet => ({
|
|
288
|
+
subnetId: subnet.SubnetId,
|
|
289
|
+
logicalId: subnet.Tags?.find(t => t.Key === 'aws:cloudformation:logical-id')?.Value,
|
|
290
|
+
isPublic: subnet.MapPublicIpOnLaunch,
|
|
291
|
+
}));
|
|
292
|
+
|
|
293
|
+
// Find private subnets
|
|
294
|
+
const privateSubnets = subnets.filter(s => !s.isPublic).sort((a, b) =>
|
|
295
|
+
a.logicalId?.localeCompare(b.logicalId) || 0
|
|
296
|
+
);
|
|
297
|
+
if (privateSubnets.length >= 1) {
|
|
298
|
+
discovered.privateSubnetId1 = privateSubnets[0].subnetId;
|
|
299
|
+
}
|
|
300
|
+
if (privateSubnets.length >= 2) {
|
|
301
|
+
discovered.privateSubnetId2 = privateSubnets[1].subnetId;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Find public subnets
|
|
305
|
+
const publicSubnets = subnets.filter(s => s.isPublic).sort((a, b) =>
|
|
306
|
+
a.logicalId?.localeCompare(b.logicalId) || 0
|
|
307
|
+
);
|
|
308
|
+
if (publicSubnets.length >= 1) {
|
|
309
|
+
discovered.publicSubnetId1 = publicSubnets[0].subnetId;
|
|
310
|
+
}
|
|
311
|
+
if (publicSubnets.length >= 2) {
|
|
312
|
+
discovered.publicSubnetId2 = publicSubnets[1].subnetId;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
console.log(` ✓ Found ${subnets.length} Frigg-managed subnets via EC2 query`);
|
|
316
|
+
}
|
|
317
|
+
} catch (error) {
|
|
318
|
+
console.warn(` ⚠️ Could not query EC2 for subnets: ${error.message}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Check for KMS key alias via AWS API if not found in stack resources
|
|
323
|
+
// This handles cases where the alias was created outside CloudFormation
|
|
324
|
+
if (!discovered.defaultKmsKeyId && !discovered.kmsKeyAlias &&
|
|
325
|
+
this.provider && this.provider.describeKmsKey && this.serviceName && this.stage) {
|
|
326
|
+
try {
|
|
327
|
+
const aliasName = `alias/${this.serviceName}-${this.stage}-frigg-kms`;
|
|
328
|
+
console.log(` Querying KMS for alias: ${aliasName}...`);
|
|
329
|
+
|
|
330
|
+
const keyMetadata = await this.provider.describeKmsKey(aliasName);
|
|
331
|
+
|
|
332
|
+
if (keyMetadata) {
|
|
333
|
+
discovered.defaultKmsKeyId = keyMetadata.Arn;
|
|
334
|
+
discovered.kmsKeyAlias = aliasName;
|
|
335
|
+
console.log(` ✓ Found KMS key via alias query: ${discovered.defaultKmsKeyId}`);
|
|
336
|
+
}
|
|
337
|
+
} catch (error) {
|
|
338
|
+
// Alias not found - this is expected if no KMS key exists yet
|
|
339
|
+
console.log(` ℹ No KMS key alias found via AWS API`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
238
342
|
}
|
|
239
343
|
}
|
|
240
344
|
|
|
@@ -307,6 +307,235 @@ describe('CloudFormationDiscovery', () => {
|
|
|
307
307
|
|
|
308
308
|
expect(result).toEqual({});
|
|
309
309
|
});
|
|
310
|
+
|
|
311
|
+
it('should query EC2 for subnets when VPC found but no subnet resources in stack', async () => {
|
|
312
|
+
const mockStack = {
|
|
313
|
+
StackName: 'test-stack',
|
|
314
|
+
Outputs: [],
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
const mockResources = [
|
|
318
|
+
{
|
|
319
|
+
LogicalResourceId: 'FriggLambdaSecurityGroup',
|
|
320
|
+
PhysicalResourceId: 'sg-123',
|
|
321
|
+
ResourceType: 'AWS::EC2::SecurityGroup',
|
|
322
|
+
},
|
|
323
|
+
];
|
|
324
|
+
|
|
325
|
+
const mockEC2Client = {
|
|
326
|
+
send: jest.fn(),
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
330
|
+
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
331
|
+
mockProvider.getEC2Client = jest.fn().mockReturnValue(mockEC2Client);
|
|
332
|
+
|
|
333
|
+
// Mock security group query for VPC ID
|
|
334
|
+
mockEC2Client.send.mockResolvedValueOnce({
|
|
335
|
+
SecurityGroups: [{ VpcId: 'vpc-123' }],
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Mock subnet query
|
|
339
|
+
mockEC2Client.send.mockResolvedValueOnce({
|
|
340
|
+
Subnets: [
|
|
341
|
+
{
|
|
342
|
+
SubnetId: 'subnet-private-1',
|
|
343
|
+
MapPublicIpOnLaunch: false,
|
|
344
|
+
Tags: [
|
|
345
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
346
|
+
{ Key: 'aws:cloudformation:logical-id', Value: 'FriggPrivateSubnet1' },
|
|
347
|
+
],
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
SubnetId: 'subnet-private-2',
|
|
351
|
+
MapPublicIpOnLaunch: false,
|
|
352
|
+
Tags: [
|
|
353
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
354
|
+
{ Key: 'aws:cloudformation:logical-id', Value: 'FriggPrivateSubnet2' },
|
|
355
|
+
],
|
|
356
|
+
},
|
|
357
|
+
],
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
361
|
+
|
|
362
|
+
expect(result.privateSubnetId1).toBe('subnet-private-1');
|
|
363
|
+
expect(result.privateSubnetId2).toBe('subnet-private-2');
|
|
364
|
+
expect(mockEC2Client.send).toHaveBeenCalledTimes(2);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('should handle EC2 subnet query errors gracefully', async () => {
|
|
368
|
+
const mockStack = {
|
|
369
|
+
StackName: 'test-stack',
|
|
370
|
+
Outputs: [],
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const mockResources = [
|
|
374
|
+
{
|
|
375
|
+
LogicalResourceId: 'FriggLambdaSecurityGroup',
|
|
376
|
+
PhysicalResourceId: 'sg-123',
|
|
377
|
+
ResourceType: 'AWS::EC2::SecurityGroup',
|
|
378
|
+
},
|
|
379
|
+
];
|
|
380
|
+
|
|
381
|
+
const mockEC2Client = {
|
|
382
|
+
send: jest.fn(),
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
386
|
+
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
387
|
+
mockProvider.getEC2Client = jest.fn().mockReturnValue(mockEC2Client);
|
|
388
|
+
|
|
389
|
+
// Mock security group query for VPC ID
|
|
390
|
+
mockEC2Client.send.mockResolvedValueOnce({
|
|
391
|
+
SecurityGroups: [{ VpcId: 'vpc-123' }],
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// Mock subnet query failure
|
|
395
|
+
mockEC2Client.send.mockRejectedValueOnce(new Error('EC2 API Error'));
|
|
396
|
+
|
|
397
|
+
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
398
|
+
|
|
399
|
+
expect(result.defaultVpcId).toBe('vpc-123');
|
|
400
|
+
expect(result.privateSubnetId1).toBeUndefined();
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('should extract KMS key alias from stack resources and query for key ARN', async () => {
|
|
404
|
+
const mockStack = {
|
|
405
|
+
StackName: 'test-stack',
|
|
406
|
+
Outputs: [],
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const mockResources = [
|
|
410
|
+
{
|
|
411
|
+
LogicalResourceId: 'FriggKMSKeyAlias',
|
|
412
|
+
PhysicalResourceId: 'alias/test-service-dev-frigg-kms',
|
|
413
|
+
ResourceType: 'AWS::KMS::Alias',
|
|
414
|
+
},
|
|
415
|
+
];
|
|
416
|
+
|
|
417
|
+
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
418
|
+
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
419
|
+
mockProvider.describeKmsKey = jest.fn().mockResolvedValue({
|
|
420
|
+
KeyId: 'abc-123',
|
|
421
|
+
Arn: 'arn:aws:kms:us-east-1:123456789:key/abc-123',
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
425
|
+
|
|
426
|
+
expect(result.defaultKmsKeyId).toBe('arn:aws:kms:us-east-1:123456789:key/abc-123');
|
|
427
|
+
expect(result.kmsKeyAlias).toBe('alias/test-service-dev-frigg-kms');
|
|
428
|
+
expect(mockProvider.describeKmsKey).toHaveBeenCalledWith('alias/test-service-dev-frigg-kms');
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('should query AWS API for KMS alias when serviceName and stage are provided', async () => {
|
|
432
|
+
const mockStack = {
|
|
433
|
+
StackName: 'test-stack',
|
|
434
|
+
Outputs: [],
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const mockResources = [];
|
|
438
|
+
|
|
439
|
+
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
440
|
+
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
441
|
+
mockProvider.region = 'us-east-1';
|
|
442
|
+
mockProvider.describeKmsKey = jest.fn().mockResolvedValue({
|
|
443
|
+
KeyId: 'abc-123',
|
|
444
|
+
Arn: 'arn:aws:kms:us-east-1:123456789:key/abc-123',
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// Pass serviceName and stage to discover alias
|
|
448
|
+
cfDiscovery.serviceName = 'test-service';
|
|
449
|
+
cfDiscovery.stage = 'dev';
|
|
450
|
+
|
|
451
|
+
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
452
|
+
|
|
453
|
+
expect(result.defaultKmsKeyId).toBe('arn:aws:kms:us-east-1:123456789:key/abc-123');
|
|
454
|
+
expect(result.kmsKeyAlias).toBe('alias/test-service-dev-frigg-kms');
|
|
455
|
+
expect(mockProvider.describeKmsKey).toHaveBeenCalledWith('alias/test-service-dev-frigg-kms');
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('should handle KMS alias not found gracefully', async () => {
|
|
459
|
+
const mockStack = {
|
|
460
|
+
StackName: 'test-stack',
|
|
461
|
+
Outputs: [],
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
const mockResources = [];
|
|
465
|
+
|
|
466
|
+
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
467
|
+
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
468
|
+
mockProvider.region = 'us-east-1';
|
|
469
|
+
mockProvider.describeKmsKey = jest.fn().mockRejectedValue(
|
|
470
|
+
new Error('Alias/test-service-dev-frigg-kms is not found')
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
cfDiscovery.serviceName = 'test-service';
|
|
474
|
+
cfDiscovery.stage = 'dev';
|
|
475
|
+
|
|
476
|
+
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
477
|
+
|
|
478
|
+
expect(result.defaultKmsKeyId).toBeUndefined();
|
|
479
|
+
expect(result.kmsKeyAlias).toBeUndefined();
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it('should prefer KMS key from stack resources over alias query', async () => {
|
|
483
|
+
const mockStack = {
|
|
484
|
+
StackName: 'test-stack',
|
|
485
|
+
Outputs: [],
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
const mockResources = [
|
|
489
|
+
{
|
|
490
|
+
LogicalResourceId: 'FriggKMSKey',
|
|
491
|
+
PhysicalResourceId: 'arn:aws:kms:us-east-1:123456789:key/xyz-789',
|
|
492
|
+
ResourceType: 'AWS::KMS::Key',
|
|
493
|
+
},
|
|
494
|
+
];
|
|
495
|
+
|
|
496
|
+
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
497
|
+
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
498
|
+
mockProvider.describeKmsKey = jest.fn();
|
|
499
|
+
|
|
500
|
+
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
501
|
+
|
|
502
|
+
// Should use the key from stack resources, not query for alias
|
|
503
|
+
expect(result.defaultKmsKeyId).toBe('arn:aws:kms:us-east-1:123456789:key/xyz-789');
|
|
504
|
+
expect(mockProvider.describeKmsKey).not.toHaveBeenCalled();
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it('should use KMS alias from stack resources even if key is also present', async () => {
|
|
508
|
+
const mockStack = {
|
|
509
|
+
StackName: 'test-stack',
|
|
510
|
+
Outputs: [],
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
const mockResources = [
|
|
514
|
+
{
|
|
515
|
+
LogicalResourceId: 'FriggKMSKey',
|
|
516
|
+
PhysicalResourceId: 'arn:aws:kms:us-east-1:123456789:key/xyz-789',
|
|
517
|
+
ResourceType: 'AWS::KMS::Key',
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
LogicalResourceId: 'FriggKMSKeyAlias',
|
|
521
|
+
PhysicalResourceId: 'alias/test-service-dev-frigg-kms',
|
|
522
|
+
ResourceType: 'AWS::KMS::Alias',
|
|
523
|
+
},
|
|
524
|
+
];
|
|
525
|
+
|
|
526
|
+
mockProvider.describeStack.mockResolvedValue(mockStack);
|
|
527
|
+
mockProvider.listStackResources.mockResolvedValue(mockResources);
|
|
528
|
+
mockProvider.describeKmsKey = jest.fn().mockResolvedValue({
|
|
529
|
+
KeyId: 'xyz-789',
|
|
530
|
+
Arn: 'arn:aws:kms:us-east-1:123456789:key/xyz-789',
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const result = await cfDiscovery.discoverFromStack('test-stack');
|
|
534
|
+
|
|
535
|
+
expect(result.defaultKmsKeyId).toBe('arn:aws:kms:us-east-1:123456789:key/xyz-789');
|
|
536
|
+
expect(result.kmsKeyAlias).toBe('alias/test-service-dev-frigg-kms');
|
|
537
|
+
expect(mockProvider.describeKmsKey).toHaveBeenCalledWith('alias/test-service-dev-frigg-kms');
|
|
538
|
+
});
|
|
310
539
|
});
|
|
311
540
|
});
|
|
312
541
|
|
|
@@ -469,9 +469,29 @@ class AWSProviderAdapter extends CloudProviderAdapter {
|
|
|
469
469
|
return result;
|
|
470
470
|
}
|
|
471
471
|
|
|
472
|
+
/**
|
|
473
|
+
* Describe KMS key by key ID or alias
|
|
474
|
+
*
|
|
475
|
+
* @param {string} keyIdOrAlias - Key ID or alias name
|
|
476
|
+
* @returns {Promise<Object>} Key metadata
|
|
477
|
+
*/
|
|
478
|
+
async describeKmsKey(keyIdOrAlias) {
|
|
479
|
+
const kms = this.getKMSClient();
|
|
480
|
+
|
|
481
|
+
try {
|
|
482
|
+
const response = await kms.send(new DescribeKeyCommand({
|
|
483
|
+
KeyId: keyIdOrAlias,
|
|
484
|
+
}));
|
|
485
|
+
|
|
486
|
+
return response.KeyMetadata;
|
|
487
|
+
} catch (error) {
|
|
488
|
+
throw new Error(`Failed to describe KMS key ${keyIdOrAlias}: ${error.message}`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
472
492
|
/**
|
|
473
493
|
* Describe CloudFormation stack
|
|
474
|
-
*
|
|
494
|
+
*
|
|
475
495
|
* @param {string} stackName - Name of the CloudFormation stack
|
|
476
496
|
* @returns {Promise<Object>} Stack details including outputs
|
|
477
497
|
*/
|
|
@@ -73,9 +73,10 @@ async function gatherDiscoveredResources(appDefinition) {
|
|
|
73
73
|
// Build discovery configuration
|
|
74
74
|
const stage = process.env.SLS_STAGE || 'dev';
|
|
75
75
|
const stackName = `${appDefinition.name || 'create-frigg-app'}-${stage}`;
|
|
76
|
+
const serviceName = appDefinition.name || 'create-frigg-app';
|
|
76
77
|
|
|
77
78
|
// Try CloudFormation-first discovery
|
|
78
|
-
const cfDiscovery = new CloudFormationDiscovery(provider);
|
|
79
|
+
const cfDiscovery = new CloudFormationDiscovery(provider, { serviceName, stage });
|
|
79
80
|
const stackResources = await cfDiscovery.discoverFromStack(stackName);
|
|
80
81
|
|
|
81
82
|
// Validate CF discovery results - only use if contains useful data
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/devtools",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.461.
|
|
4
|
+
"version": "2.0.0--canary.461.77c8d12.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-ec2": "^3.835.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.835.0",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"@babel/eslint-parser": "^7.18.9",
|
|
12
12
|
"@babel/parser": "^7.25.3",
|
|
13
13
|
"@babel/traverse": "^7.25.3",
|
|
14
|
-
"@friggframework/schemas": "2.0.0--canary.461.
|
|
15
|
-
"@friggframework/test": "2.0.0--canary.461.
|
|
14
|
+
"@friggframework/schemas": "2.0.0--canary.461.77c8d12.0",
|
|
15
|
+
"@friggframework/test": "2.0.0--canary.461.77c8d12.0",
|
|
16
16
|
"@hapi/boom": "^10.0.1",
|
|
17
17
|
"@inquirer/prompts": "^5.3.8",
|
|
18
18
|
"axios": "^1.7.2",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"serverless-http": "^2.7.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@friggframework/eslint-config": "2.0.0--canary.461.
|
|
38
|
-
"@friggframework/prettier-config": "2.0.0--canary.461.
|
|
37
|
+
"@friggframework/eslint-config": "2.0.0--canary.461.77c8d12.0",
|
|
38
|
+
"@friggframework/prettier-config": "2.0.0--canary.461.77c8d12.0",
|
|
39
39
|
"aws-sdk-client-mock": "^4.1.0",
|
|
40
40
|
"aws-sdk-client-mock-jest": "^4.1.0",
|
|
41
41
|
"jest": "^30.1.3",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"publishConfig": {
|
|
71
71
|
"access": "public"
|
|
72
72
|
},
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "77c8d12f6a33bb2d66e19b43b6d96ee27d6f5e06"
|
|
74
74
|
}
|