@friggframework/devtools 2.0.0--canary.463.62579dd.0 → 2.0.0--canary.461.ec909cf.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.
@@ -0,0 +1,159 @@
1
+ /**
2
+ * VPC Discovery Service
3
+ *
4
+ * Domain Service - Hexagonal Architecture
5
+ *
6
+ * Discovers VPC and networking resources using the cloud provider adapter.
7
+ * Adds domain-specific validation and transformation logic.
8
+ */
9
+
10
+ class VpcDiscovery {
11
+ /**
12
+ * @param {CloudProviderAdapter} provider - Cloud provider adapter instance
13
+ */
14
+ constructor(provider) {
15
+ this.provider = provider;
16
+ }
17
+
18
+ /**
19
+ * Discover VPC and networking resources
20
+ *
21
+ * @param {Object} config - Discovery configuration
22
+ * @param {string} [config.vpcId] - Specific VPC ID to discover
23
+ * @param {string} [config.serviceName] - Service name for tagging/filtering
24
+ * @param {string} [config.stage] - Deployment stage
25
+ * @returns {Promise<Object>} Discovered VPC resources with friendly property names
26
+ */
27
+ async discover(config) {
28
+ console.log('🔍 Discovering VPC resources...');
29
+
30
+ try {
31
+ const rawResources = await this.provider.discoverVpc(config);
32
+
33
+ // Transform to Frigg-friendly format
34
+ const result = {
35
+ defaultVpcId: rawResources.vpcId,
36
+ vpcCidr: rawResources.vpcCidr,
37
+ subnets: rawResources.subnets,
38
+ securityGroups: rawResources.securityGroups,
39
+ routeTables: rawResources.routeTables,
40
+ natGateways: rawResources.natGateways,
41
+ internetGateways: rawResources.internetGateways,
42
+ };
43
+
44
+ // Extract specific subnet types
45
+ const privateSubnets = rawResources.subnets.filter(
46
+ s => !s.MapPublicIpOnLaunch
47
+ );
48
+ const publicSubnets = rawResources.subnets.filter(
49
+ s => s.MapPublicIpOnLaunch
50
+ );
51
+
52
+ // Set subnet IDs for Frigg infrastructure
53
+ if (privateSubnets.length >= 1) {
54
+ result.privateSubnetId1 = privateSubnets[0].SubnetId;
55
+ }
56
+ if (privateSubnets.length >= 2) {
57
+ result.privateSubnetId2 = privateSubnets[1].SubnetId;
58
+ }
59
+ if (publicSubnets.length >= 1) {
60
+ result.publicSubnetId = publicSubnets[0].SubnetId;
61
+ result.publicSubnetId1 = publicSubnets[0].SubnetId;
62
+ }
63
+ if (publicSubnets.length >= 2) {
64
+ result.publicSubnetId2 = publicSubnets[1].SubnetId;
65
+ }
66
+
67
+ // Find default security group
68
+ const defaultSg = rawResources.securityGroups.find(
69
+ sg => sg.GroupName === 'default'
70
+ );
71
+ if (defaultSg) {
72
+ result.defaultSecurityGroupId = defaultSg.GroupId;
73
+ }
74
+
75
+ // Find default route table
76
+ const defaultRt = rawResources.routeTables.find(
77
+ rt => rt.Associations?.some(a => a.Main)
78
+ );
79
+ if (defaultRt) {
80
+ result.defaultRouteTableId = defaultRt.RouteTableId;
81
+ result.privateRouteTableId = defaultRt.RouteTableId;
82
+ }
83
+
84
+ // Find NAT gateway
85
+ const activeNat = rawResources.natGateways.find(
86
+ nat => nat.State === 'available'
87
+ );
88
+ if (activeNat) {
89
+ result.existingNatGatewayId = activeNat.NatGatewayId;
90
+
91
+ // Check if NAT is in private subnet (configuration error)
92
+ const natSubnet = rawResources.subnets.find(
93
+ s => s.SubnetId === activeNat.SubnetId
94
+ );
95
+ result.natGatewayInPrivateSubnet = natSubnet && !natSubnet.MapPublicIpOnLaunch;
96
+
97
+ // Get elastic IP allocation
98
+ if (activeNat.NatGatewayAddresses && activeNat.NatGatewayAddresses.length > 0) {
99
+ result.existingElasticIpAllocationId =
100
+ activeNat.NatGatewayAddresses[0].AllocationId;
101
+ }
102
+ }
103
+
104
+ // Find Internet Gateway
105
+ if (rawResources.internetGateways.length > 0) {
106
+ result.internetGatewayId = rawResources.internetGateways[0].InternetGatewayId;
107
+ }
108
+
109
+ // Find VPC Endpoints
110
+ if (rawResources.vpcEndpoints) {
111
+ const s3Endpoint = rawResources.vpcEndpoints.find(
112
+ ep => ep.ServiceName && ep.ServiceName.includes('.s3')
113
+ );
114
+ const dynamodbEndpoint = rawResources.vpcEndpoints.find(
115
+ ep => ep.ServiceName && ep.ServiceName.includes('.dynamodb')
116
+ );
117
+
118
+ if (s3Endpoint) {
119
+ result.s3VpcEndpointId = s3Endpoint.VpcEndpointId;
120
+ }
121
+ if (dynamodbEndpoint) {
122
+ result.dynamodbVpcEndpointId = dynamodbEndpoint.VpcEndpointId;
123
+ }
124
+ }
125
+
126
+ console.log(` ✓ Found VPC: ${result.defaultVpcId}`);
127
+ if (result.privateSubnetId1) {
128
+ console.log(` ✓ Found private subnets: ${result.privateSubnetId1}, ${result.privateSubnetId2 || 'N/A'}`);
129
+ }
130
+ if (result.publicSubnetId) {
131
+ console.log(` ✓ Found public subnet: ${result.publicSubnetId}`);
132
+ }
133
+ if (result.existingNatGatewayId) {
134
+ console.log(` ✓ Found NAT Gateway: ${result.existingNatGatewayId}`);
135
+ }
136
+ if (result.s3VpcEndpointId || result.dynamodbVpcEndpointId) {
137
+ console.log(` ✓ Found VPC Endpoints: S3=${result.s3VpcEndpointId ? 'Yes' : 'No'}, DynamoDB=${result.dynamodbVpcEndpointId ? 'Yes' : 'No'}`);
138
+ }
139
+
140
+ return result;
141
+ } catch (error) {
142
+ console.error(' ✗ VPC discovery failed:', error.message);
143
+ return {
144
+ defaultVpcId: null,
145
+ vpcCidr: null,
146
+ privateSubnetId1: null,
147
+ privateSubnetId2: null,
148
+ publicSubnetId: null,
149
+ defaultSecurityGroupId: null,
150
+ defaultRouteTableId: null,
151
+ };
152
+ }
153
+ }
154
+ }
155
+
156
+ module.exports = {
157
+ VpcDiscovery,
158
+ };
159
+
@@ -0,0 +1,445 @@
1
+ /**
2
+ * AWS Provider Adapter
3
+ *
4
+ * Adapter - Hexagonal Architecture
5
+ *
6
+ * Implements CloudProviderAdapter interface for Amazon Web Services.
7
+ * Handles AWS-specific resource discovery using AWS SDK v3.
8
+ *
9
+ * This adapter lazy-loads AWS SDK clients to minimize cold start time
10
+ * and memory usage when not all discovery features are needed.
11
+ */
12
+
13
+ const { CloudProviderAdapter } = require('./cloud-provider-adapter');
14
+
15
+ // Lazy-loaded AWS SDK clients
16
+ let EC2Client, DescribeVpcsCommand, DescribeSubnetsCommand, DescribeSecurityGroupsCommand,
17
+ DescribeRouteTablesCommand, DescribeNatGatewaysCommand, DescribeInternetGatewaysCommand,
18
+ DescribeVpcEndpointsCommand;
19
+ let KMSClient, ListKeysCommand, DescribeKeyCommand, ListAliasesCommand;
20
+ let RDSClient, DescribeDBClustersCommand, DescribeDBInstancesCommand;
21
+ let SSMClient, GetParameterCommand, GetParametersByPathCommand;
22
+ let SecretsManagerClient, ListSecretsCommand, GetSecretValueCommand;
23
+
24
+ /**
25
+ * Lazy load EC2 SDK
26
+ */
27
+ function loadEC2() {
28
+ if (!EC2Client) {
29
+ const ec2Module = require('@aws-sdk/client-ec2');
30
+ EC2Client = ec2Module.EC2Client;
31
+ DescribeVpcsCommand = ec2Module.DescribeVpcsCommand;
32
+ DescribeSubnetsCommand = ec2Module.DescribeSubnetsCommand;
33
+ DescribeSecurityGroupsCommand = ec2Module.DescribeSecurityGroupsCommand;
34
+ DescribeRouteTablesCommand = ec2Module.DescribeRouteTablesCommand;
35
+ DescribeNatGatewaysCommand = ec2Module.DescribeNatGatewaysCommand;
36
+ DescribeInternetGatewaysCommand = ec2Module.DescribeInternetGatewaysCommand;
37
+ DescribeVpcEndpointsCommand = ec2Module.DescribeVpcEndpointsCommand;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Lazy load KMS SDK
43
+ */
44
+ function loadKMS() {
45
+ if (!KMSClient) {
46
+ const kmsModule = require('@aws-sdk/client-kms');
47
+ KMSClient = kmsModule.KMSClient;
48
+ ListKeysCommand = kmsModule.ListKeysCommand;
49
+ DescribeKeyCommand = kmsModule.DescribeKeyCommand;
50
+ ListAliasesCommand = kmsModule.ListAliasesCommand;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Lazy load RDS SDK
56
+ */
57
+ function loadRDS() {
58
+ if (!RDSClient) {
59
+ const rdsModule = require('@aws-sdk/client-rds');
60
+ RDSClient = rdsModule.RDSClient;
61
+ DescribeDBClustersCommand = rdsModule.DescribeDBClustersCommand;
62
+ DescribeDBInstancesCommand = rdsModule.DescribeDBInstancesCommand;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Lazy load SSM SDK
68
+ */
69
+ function loadSSM() {
70
+ if (!SSMClient) {
71
+ const ssmModule = require('@aws-sdk/client-ssm');
72
+ SSMClient = ssmModule.SSMClient;
73
+ GetParameterCommand = ssmModule.GetParameterCommand;
74
+ GetParametersByPathCommand = ssmModule.GetParametersByPathCommand;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Lazy load Secrets Manager SDK
80
+ */
81
+ function loadSecretsManager() {
82
+ if (!SecretsManagerClient) {
83
+ const smModule = require('@aws-sdk/client-secrets-manager');
84
+ SecretsManagerClient = smModule.SecretsManagerClient;
85
+ ListSecretsCommand = smModule.ListSecretsCommand;
86
+ GetSecretValueCommand = smModule.GetSecretValueCommand;
87
+ }
88
+ }
89
+
90
+ class AWSProviderAdapter extends CloudProviderAdapter {
91
+ constructor(region, credentials = {}) {
92
+ super();
93
+ this.region = region || process.env.AWS_REGION || 'us-east-1';
94
+ this.credentials = credentials;
95
+
96
+ // Initialize clients as null - will be lazy loaded
97
+ this.ec2 = null;
98
+ this.kms = null;
99
+ this.rds = null;
100
+ this.ssm = null;
101
+ this.secretsManager = null;
102
+ }
103
+
104
+ /**
105
+ * Get EC2 client (lazy loaded)
106
+ */
107
+ getEC2Client() {
108
+ if (!this.ec2) {
109
+ loadEC2();
110
+ this.ec2 = new EC2Client({
111
+ region: this.region,
112
+ ...this.credentials,
113
+ });
114
+ }
115
+ return this.ec2;
116
+ }
117
+
118
+ /**
119
+ * Get KMS client (lazy loaded)
120
+ */
121
+ getKMSClient() {
122
+ if (!this.kms) {
123
+ loadKMS();
124
+ this.kms = new KMSClient({
125
+ region: this.region,
126
+ ...this.credentials,
127
+ });
128
+ }
129
+ return this.kms;
130
+ }
131
+
132
+ /**
133
+ * Get RDS client (lazy loaded)
134
+ */
135
+ getRDSClient() {
136
+ if (!this.rds) {
137
+ loadRDS();
138
+ this.rds = new RDSClient({
139
+ region: this.region,
140
+ ...this.credentials,
141
+ });
142
+ }
143
+ return this.rds;
144
+ }
145
+
146
+ /**
147
+ * Get SSM client (lazy loaded)
148
+ */
149
+ getSSMClient() {
150
+ if (!this.ssm) {
151
+ loadSSM();
152
+ this.ssm = new SSMClient({
153
+ region: this.region,
154
+ ...this.credentials,
155
+ });
156
+ }
157
+ return this.ssm;
158
+ }
159
+
160
+ /**
161
+ * Get Secrets Manager client (lazy loaded)
162
+ */
163
+ getSecretsManagerClient() {
164
+ if (!this.secretsManager) {
165
+ loadSecretsManager();
166
+ this.secretsManager = new SecretsManagerClient({
167
+ region: this.region,
168
+ ...this.credentials,
169
+ });
170
+ }
171
+ return this.secretsManager;
172
+ }
173
+
174
+ getName() {
175
+ return 'aws';
176
+ }
177
+
178
+ getSupportedRegions() {
179
+ return [
180
+ 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2',
181
+ 'eu-west-1', 'eu-west-2', 'eu-west-3', 'eu-central-1',
182
+ 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'ap-northeast-2',
183
+ 'sa-east-1', 'ca-central-1',
184
+ ];
185
+ }
186
+
187
+ /**
188
+ * Discover VPC resources
189
+ *
190
+ * @param {Object} config - Discovery configuration
191
+ * @returns {Promise<Object>} Discovered VPC resources
192
+ */
193
+ async discoverVpc(config) {
194
+ const ec2 = this.getEC2Client();
195
+ const result = {
196
+ vpcId: null,
197
+ vpcCidr: null,
198
+ subnets: [],
199
+ securityGroups: [],
200
+ routeTables: [],
201
+ natGateways: [],
202
+ internetGateways: [],
203
+ vpcEndpoints: [],
204
+ };
205
+
206
+ try {
207
+ // Discover VPC
208
+ const vpcFilter = config.vpcId
209
+ ? [{ Name: 'vpc-id', Values: [config.vpcId] }]
210
+ : [{ Name: 'isDefault', Values: ['true'] }];
211
+
212
+ const vpcsResponse = await ec2.send(new DescribeVpcsCommand({
213
+ Filters: vpcFilter,
214
+ }));
215
+
216
+ if (vpcsResponse.Vpcs && vpcsResponse.Vpcs.length > 0) {
217
+ const vpc = vpcsResponse.Vpcs[0];
218
+ result.vpcId = vpc.VpcId;
219
+ result.vpcCidr = vpc.CidrBlock;
220
+
221
+ // Discover subnets in VPC
222
+ const subnetsResponse = await ec2.send(new DescribeSubnetsCommand({
223
+ Filters: [{ Name: 'vpc-id', Values: [result.vpcId] }],
224
+ }));
225
+ result.subnets = subnetsResponse.Subnets || [];
226
+
227
+ // Discover security groups
228
+ const sgResponse = await ec2.send(new DescribeSecurityGroupsCommand({
229
+ Filters: [{ Name: 'vpc-id', Values: [result.vpcId] }],
230
+ }));
231
+ result.securityGroups = sgResponse.SecurityGroups || [];
232
+
233
+ // Discover route tables
234
+ const rtResponse = await ec2.send(new DescribeRouteTablesCommand({
235
+ Filters: [{ Name: 'vpc-id', Values: [result.vpcId] }],
236
+ }));
237
+ result.routeTables = rtResponse.RouteTables || [];
238
+
239
+ // Discover NAT gateways
240
+ const natResponse = await ec2.send(new DescribeNatGatewaysCommand({
241
+ Filter: [{ Name: 'vpc-id', Values: [result.vpcId] }],
242
+ }));
243
+ result.natGateways = natResponse.NatGateways || [];
244
+
245
+ // Discover Internet gateways
246
+ const igwResponse = await ec2.send(new DescribeInternetGatewaysCommand({
247
+ Filters: [
248
+ { Name: 'attachment.vpc-id', Values: [result.vpcId] },
249
+ ],
250
+ }));
251
+ result.internetGateways = igwResponse.InternetGateways || [];
252
+
253
+ // Discover VPC Endpoints
254
+ const vpcEndpointsResponse = await ec2.send(new DescribeVpcEndpointsCommand({
255
+ Filters: [
256
+ { Name: 'vpc-id', Values: [result.vpcId] },
257
+ ],
258
+ }));
259
+ result.vpcEndpoints = vpcEndpointsResponse.VpcEndpoints || [];
260
+ }
261
+ } catch (error) {
262
+ console.error('AWS VPC discovery failed:', error);
263
+ throw new Error(`Failed to discover AWS VPC: ${error.message}`);
264
+ }
265
+
266
+ return result;
267
+ }
268
+
269
+ /**
270
+ * Discover KMS keys
271
+ *
272
+ * @param {Object} config - Discovery configuration
273
+ * @returns {Promise<Object>} Discovered KMS keys
274
+ */
275
+ async discoverKmsKeys(config) {
276
+ const kms = this.getKMSClient();
277
+ const result = {
278
+ keys: [],
279
+ aliases: [],
280
+ defaultKey: null,
281
+ };
282
+
283
+ try {
284
+ // List KMS keys
285
+ const keysResponse = await kms.send(new ListKeysCommand({}));
286
+
287
+ if (keysResponse.Keys && keysResponse.Keys.length > 0) {
288
+ // Get details for each key
289
+ for (const key of keysResponse.Keys) {
290
+ try {
291
+ const keyDetails = await kms.send(new DescribeKeyCommand({
292
+ KeyId: key.KeyId,
293
+ }));
294
+
295
+ if (keyDetails.KeyMetadata && keyDetails.KeyMetadata.Enabled) {
296
+ result.keys.push(keyDetails.KeyMetadata);
297
+
298
+ // Set first enabled key as default
299
+ if (!result.defaultKey) {
300
+ result.defaultKey = keyDetails.KeyMetadata;
301
+ }
302
+ }
303
+ } catch (error) {
304
+ // Skip keys we can't access
305
+ console.warn(`Could not describe key ${key.KeyId}:`, error.message);
306
+ }
307
+ }
308
+ }
309
+
310
+ // List KMS aliases
311
+ const aliasesResponse = await kms.send(new ListAliasesCommand({}));
312
+ result.aliases = aliasesResponse.Aliases || [];
313
+
314
+ // If a specific alias is requested, find it
315
+ if (config.keyAlias) {
316
+ const alias = result.aliases.find(a => a.AliasName === config.keyAlias);
317
+ if (alias && alias.TargetKeyId) {
318
+ const key = result.keys.find(k => k.KeyId === alias.TargetKeyId);
319
+ if (key) {
320
+ result.defaultKey = key;
321
+ }
322
+ }
323
+ }
324
+ } catch (error) {
325
+ console.error('AWS KMS discovery failed:', error);
326
+ throw new Error(`Failed to discover AWS KMS keys: ${error.message}`);
327
+ }
328
+
329
+ return result;
330
+ }
331
+
332
+ /**
333
+ * Discover database resources (RDS/Aurora)
334
+ *
335
+ * @param {Object} config - Discovery configuration
336
+ * @returns {Promise<Object>} Discovered database resources
337
+ */
338
+ async discoverDatabase(config) {
339
+ const rds = this.getRDSClient();
340
+ const result = {
341
+ clusters: [],
342
+ instances: [],
343
+ endpoint: null,
344
+ port: null,
345
+ engine: null,
346
+ };
347
+
348
+ try {
349
+ // Discover Aurora clusters
350
+ const clustersResponse = await rds.send(new DescribeDBClustersCommand({}));
351
+
352
+ if (clustersResponse.DBClusters) {
353
+ result.clusters = clustersResponse.DBClusters;
354
+
355
+ // Find cluster matching config
356
+ if (config.databaseId) {
357
+ const cluster = result.clusters.find(c =>
358
+ c.DBClusterIdentifier === config.databaseId
359
+ );
360
+ if (cluster) {
361
+ result.endpoint = cluster.Endpoint;
362
+ result.port = cluster.Port;
363
+ result.engine = cluster.Engine;
364
+ }
365
+ } else if (result.clusters.length > 0) {
366
+ // Use first available cluster
367
+ const cluster = result.clusters[0];
368
+ result.endpoint = cluster.Endpoint;
369
+ result.port = cluster.Port;
370
+ result.engine = cluster.Engine;
371
+ }
372
+ }
373
+
374
+ // Discover RDS instances (if no cluster found)
375
+ if (!result.endpoint) {
376
+ const instancesResponse = await rds.send(new DescribeDBInstancesCommand({}));
377
+ if (instancesResponse.DBInstances) {
378
+ result.instances = instancesResponse.DBInstances;
379
+
380
+ if (config.databaseId) {
381
+ const instance = result.instances.find(i =>
382
+ i.DBInstanceIdentifier === config.databaseId
383
+ );
384
+ if (instance) {
385
+ result.endpoint = instance.Endpoint?.Address;
386
+ result.port = instance.Endpoint?.Port;
387
+ result.engine = instance.Engine;
388
+ }
389
+ } else if (result.instances.length > 0) {
390
+ const instance = result.instances[0];
391
+ result.endpoint = instance.Endpoint?.Address;
392
+ result.port = instance.Endpoint?.Port;
393
+ result.engine = instance.Engine;
394
+ }
395
+ }
396
+ }
397
+ } catch (error) {
398
+ console.error('AWS RDS discovery failed:', error);
399
+ throw new Error(`Failed to discover AWS databases: ${error.message}`);
400
+ }
401
+
402
+ return result;
403
+ }
404
+
405
+ /**
406
+ * Discover SSM parameters
407
+ *
408
+ * @param {Object} config - Discovery configuration
409
+ * @returns {Promise<Object>} Discovered parameters
410
+ */
411
+ async discoverParameters(config) {
412
+ const ssm = this.getSSMClient();
413
+ const result = {
414
+ parameters: [],
415
+ secrets: [],
416
+ };
417
+
418
+ try {
419
+ if (config.parameterPath) {
420
+ const response = await ssm.send(new GetParametersByPathCommand({
421
+ Path: config.parameterPath,
422
+ Recursive: true,
423
+ }));
424
+ result.parameters = response.Parameters || [];
425
+ }
426
+
427
+ // Discover secrets if enabled
428
+ if (config.includeSecrets) {
429
+ const sm = this.getSecretsManagerClient();
430
+ const secretsResponse = await sm.send(new ListSecretsCommand({}));
431
+ result.secrets = secretsResponse.SecretList || [];
432
+ }
433
+ } catch (error) {
434
+ console.error('AWS SSM discovery failed:', error);
435
+ throw new Error(`Failed to discover AWS parameters: ${error.message}`);
436
+ }
437
+
438
+ return result;
439
+ }
440
+ }
441
+
442
+ module.exports = {
443
+ AWSProviderAdapter,
444
+ };
445
+