@friggframework/devtools 2.0.0-next.44 → 2.0.0-next.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/infrastructure/ARCHITECTURE.md +487 -0
  2. package/infrastructure/HEALTH.md +468 -0
  3. package/infrastructure/README.md +51 -0
  4. package/infrastructure/__tests__/postgres-config.test.js +914 -0
  5. package/infrastructure/__tests__/template-generation.test.js +687 -0
  6. package/infrastructure/create-frigg-infrastructure.js +1 -1
  7. package/infrastructure/docs/POSTGRES-CONFIGURATION.md +630 -0
  8. package/infrastructure/{DEPLOYMENT-INSTRUCTIONS.md → docs/deployment-instructions.md} +3 -3
  9. package/infrastructure/{IAM-POLICY-TEMPLATES.md → docs/iam-policy-templates.md} +9 -10
  10. package/infrastructure/domains/database/aurora-builder.js +809 -0
  11. package/infrastructure/domains/database/aurora-builder.test.js +950 -0
  12. package/infrastructure/domains/database/aurora-discovery.js +87 -0
  13. package/infrastructure/domains/database/aurora-discovery.test.js +188 -0
  14. package/infrastructure/domains/database/aurora-resolver.js +210 -0
  15. package/infrastructure/domains/database/aurora-resolver.test.js +347 -0
  16. package/infrastructure/domains/database/migration-builder.js +633 -0
  17. package/infrastructure/domains/database/migration-builder.test.js +294 -0
  18. package/infrastructure/domains/database/migration-resolver.js +163 -0
  19. package/infrastructure/domains/database/migration-resolver.test.js +337 -0
  20. package/infrastructure/domains/health/application/ports/IPropertyReconciler.js +164 -0
  21. package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
  22. package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
  23. package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
  24. package/infrastructure/domains/health/application/ports/index.js +26 -0
  25. package/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
  26. package/infrastructure/domains/health/application/use-cases/__tests__/mismatch-analyzer-method-name.test.js +167 -0
  27. package/infrastructure/domains/health/application/use-cases/__tests__/repair-via-import-use-case.test.js +1130 -0
  28. package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
  29. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.js +152 -0
  30. package/infrastructure/domains/health/application/use-cases/reconcile-properties-use-case.test.js +343 -0
  31. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.js +535 -0
  32. package/infrastructure/domains/health/application/use-cases/repair-via-import-use-case.test.js +376 -0
  33. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.js +213 -0
  34. package/infrastructure/domains/health/application/use-cases/run-health-check-use-case.test.js +441 -0
  35. package/infrastructure/domains/health/docs/ACME-DEV-DRIFT-ANALYSIS.md +267 -0
  36. package/infrastructure/domains/health/docs/BUILD-VS-DEPLOYED-TEMPLATE-ANALYSIS.md +324 -0
  37. package/infrastructure/domains/health/docs/ORPHAN-DETECTION-ANALYSIS.md +386 -0
  38. package/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md +1419 -0
  39. package/infrastructure/domains/health/docs/TDD-IMPLEMENTATION-SUMMARY.md +391 -0
  40. package/infrastructure/domains/health/docs/TEMPLATE-COMPARISON-IMPLEMENTATION.md +551 -0
  41. package/infrastructure/domains/health/domain/entities/issue.js +299 -0
  42. package/infrastructure/domains/health/domain/entities/issue.test.js +528 -0
  43. package/infrastructure/domains/health/domain/entities/property-mismatch.js +108 -0
  44. package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +275 -0
  45. package/infrastructure/domains/health/domain/entities/resource.js +159 -0
  46. package/infrastructure/domains/health/domain/entities/resource.test.js +432 -0
  47. package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
  48. package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
  49. package/infrastructure/domains/health/domain/services/__tests__/health-score-percentage-based.test.js +380 -0
  50. package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
  51. package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
  52. package/infrastructure/domains/health/domain/services/__tests__/logical-id-mapper.test.js +672 -0
  53. package/infrastructure/domains/health/domain/services/__tests__/template-parser.test.js +496 -0
  54. package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
  55. package/infrastructure/domains/health/domain/services/health-score-calculator.js +248 -0
  56. package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +504 -0
  57. package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
  58. package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
  59. package/infrastructure/domains/health/domain/services/logical-id-mapper.js +345 -0
  60. package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
  61. package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
  62. package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
  63. package/infrastructure/domains/health/domain/services/template-parser.js +245 -0
  64. package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
  65. package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
  66. package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
  67. package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
  68. package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
  69. package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
  70. package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
  71. package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +192 -0
  72. package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +262 -0
  73. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-cfn-tagged.test.js +312 -0
  74. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-multi-stack.test.js +367 -0
  75. package/infrastructure/domains/health/infrastructure/adapters/__tests__/orphan-detection-relationship-analysis.test.js +432 -0
  76. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.js +784 -0
  77. package/infrastructure/domains/health/infrastructure/adapters/aws-property-reconciler.test.js +1133 -0
  78. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.js +565 -0
  79. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-detector.test.js +554 -0
  80. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.js +318 -0
  81. package/infrastructure/domains/health/infrastructure/adapters/aws-resource-importer.test.js +398 -0
  82. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +777 -0
  83. package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
  84. package/infrastructure/domains/integration/integration-builder.js +397 -0
  85. package/infrastructure/domains/integration/integration-builder.test.js +593 -0
  86. package/infrastructure/domains/integration/integration-resolver.js +170 -0
  87. package/infrastructure/domains/integration/integration-resolver.test.js +369 -0
  88. package/infrastructure/domains/integration/websocket-builder.js +69 -0
  89. package/infrastructure/domains/integration/websocket-builder.test.js +195 -0
  90. package/infrastructure/domains/networking/vpc-builder.js +1829 -0
  91. package/infrastructure/domains/networking/vpc-builder.test.js +1262 -0
  92. package/infrastructure/domains/networking/vpc-discovery.js +177 -0
  93. package/infrastructure/domains/networking/vpc-discovery.test.js +350 -0
  94. package/infrastructure/domains/networking/vpc-resolver.js +324 -0
  95. package/infrastructure/domains/networking/vpc-resolver.test.js +501 -0
  96. package/infrastructure/domains/parameters/ssm-builder.js +79 -0
  97. package/infrastructure/domains/parameters/ssm-builder.test.js +189 -0
  98. package/infrastructure/domains/parameters/ssm-discovery.js +84 -0
  99. package/infrastructure/domains/parameters/ssm-discovery.test.js +210 -0
  100. package/infrastructure/{iam-generator.js → domains/security/iam-generator.js} +2 -2
  101. package/infrastructure/domains/security/kms-builder.js +366 -0
  102. package/infrastructure/domains/security/kms-builder.test.js +374 -0
  103. package/infrastructure/domains/security/kms-discovery.js +80 -0
  104. package/infrastructure/domains/security/kms-discovery.test.js +177 -0
  105. package/infrastructure/domains/security/kms-resolver.js +96 -0
  106. package/infrastructure/domains/security/kms-resolver.test.js +216 -0
  107. package/infrastructure/domains/shared/base-builder.js +112 -0
  108. package/infrastructure/domains/shared/base-resolver.js +186 -0
  109. package/infrastructure/domains/shared/base-resolver.test.js +305 -0
  110. package/infrastructure/domains/shared/builder-orchestrator.js +212 -0
  111. package/infrastructure/domains/shared/builder-orchestrator.test.js +213 -0
  112. package/infrastructure/domains/shared/cloudformation-discovery-v2.js +334 -0
  113. package/infrastructure/domains/shared/cloudformation-discovery.js +375 -0
  114. package/infrastructure/domains/shared/cloudformation-discovery.test.js +590 -0
  115. package/infrastructure/domains/shared/environment-builder.js +119 -0
  116. package/infrastructure/domains/shared/environment-builder.test.js +247 -0
  117. package/infrastructure/domains/shared/providers/aws-provider-adapter.js +544 -0
  118. package/infrastructure/domains/shared/providers/aws-provider-adapter.test.js +377 -0
  119. package/infrastructure/domains/shared/providers/azure-provider-adapter.stub.js +93 -0
  120. package/infrastructure/domains/shared/providers/cloud-provider-adapter.js +136 -0
  121. package/infrastructure/domains/shared/providers/gcp-provider-adapter.stub.js +82 -0
  122. package/infrastructure/domains/shared/providers/provider-factory.js +108 -0
  123. package/infrastructure/domains/shared/providers/provider-factory.test.js +170 -0
  124. package/infrastructure/domains/shared/resource-discovery.js +192 -0
  125. package/infrastructure/domains/shared/resource-discovery.test.js +552 -0
  126. package/infrastructure/domains/shared/types/app-definition.js +205 -0
  127. package/infrastructure/domains/shared/types/discovery-result.js +106 -0
  128. package/infrastructure/domains/shared/types/discovery-result.test.js +258 -0
  129. package/infrastructure/domains/shared/types/index.js +46 -0
  130. package/infrastructure/domains/shared/types/resource-ownership.js +108 -0
  131. package/infrastructure/domains/shared/types/resource-ownership.test.js +101 -0
  132. package/infrastructure/domains/shared/utilities/base-definition-factory.js +380 -0
  133. package/infrastructure/domains/shared/utilities/base-definition-factory.js.bak +338 -0
  134. package/infrastructure/domains/shared/utilities/base-definition-factory.test.js +248 -0
  135. package/infrastructure/domains/shared/utilities/handler-path-resolver.js +134 -0
  136. package/infrastructure/domains/shared/utilities/handler-path-resolver.test.js +268 -0
  137. package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +55 -0
  138. package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +138 -0
  139. package/infrastructure/{env-validator.js → domains/shared/validation/env-validator.js} +2 -1
  140. package/infrastructure/domains/shared/validation/env-validator.test.js +173 -0
  141. package/infrastructure/esbuild.config.js +53 -0
  142. package/infrastructure/infrastructure-composer.js +87 -0
  143. package/infrastructure/{serverless-template.test.js → infrastructure-composer.test.js} +115 -24
  144. package/infrastructure/scripts/build-prisma-layer.js +553 -0
  145. package/infrastructure/scripts/build-prisma-layer.test.js +102 -0
  146. package/infrastructure/{build-time-discovery.js → scripts/build-time-discovery.js} +80 -48
  147. package/infrastructure/{build-time-discovery.test.js → scripts/build-time-discovery.test.js} +5 -4
  148. package/layers/prisma/nodejs/package.json +8 -0
  149. package/management-ui/server/utils/cliIntegration.js +1 -1
  150. package/management-ui/server/utils/environment/awsParameterStore.js +29 -18
  151. package/package.json +11 -11
  152. package/frigg-cli/.eslintrc.js +0 -141
  153. package/frigg-cli/__tests__/unit/commands/build.test.js +0 -251
  154. package/frigg-cli/__tests__/unit/commands/db-setup.test.js +0 -548
  155. package/frigg-cli/__tests__/unit/commands/install.test.js +0 -400
  156. package/frigg-cli/__tests__/unit/commands/ui.test.js +0 -346
  157. package/frigg-cli/__tests__/unit/utils/database-validator.test.js +0 -366
  158. package/frigg-cli/__tests__/unit/utils/error-messages.test.js +0 -304
  159. package/frigg-cli/__tests__/unit/utils/prisma-runner.test.js +0 -486
  160. package/frigg-cli/__tests__/utils/mock-factory.js +0 -270
  161. package/frigg-cli/__tests__/utils/prisma-mock.js +0 -194
  162. package/frigg-cli/__tests__/utils/test-fixtures.js +0 -463
  163. package/frigg-cli/__tests__/utils/test-setup.js +0 -287
  164. package/frigg-cli/build-command/index.js +0 -65
  165. package/frigg-cli/db-setup-command/index.js +0 -193
  166. package/frigg-cli/deploy-command/index.js +0 -175
  167. package/frigg-cli/generate-command/__tests__/generate-command.test.js +0 -301
  168. package/frigg-cli/generate-command/azure-generator.js +0 -43
  169. package/frigg-cli/generate-command/gcp-generator.js +0 -47
  170. package/frigg-cli/generate-command/index.js +0 -332
  171. package/frigg-cli/generate-command/terraform-generator.js +0 -555
  172. package/frigg-cli/generate-iam-command.js +0 -118
  173. package/frigg-cli/index.js +0 -75
  174. package/frigg-cli/index.test.js +0 -158
  175. package/frigg-cli/init-command/backend-first-handler.js +0 -756
  176. package/frigg-cli/init-command/index.js +0 -93
  177. package/frigg-cli/init-command/template-handler.js +0 -143
  178. package/frigg-cli/install-command/backend-js.js +0 -33
  179. package/frigg-cli/install-command/commit-changes.js +0 -16
  180. package/frigg-cli/install-command/environment-variables.js +0 -127
  181. package/frigg-cli/install-command/environment-variables.test.js +0 -136
  182. package/frigg-cli/install-command/index.js +0 -54
  183. package/frigg-cli/install-command/install-package.js +0 -13
  184. package/frigg-cli/install-command/integration-file.js +0 -30
  185. package/frigg-cli/install-command/logger.js +0 -12
  186. package/frigg-cli/install-command/template.js +0 -90
  187. package/frigg-cli/install-command/validate-package.js +0 -75
  188. package/frigg-cli/jest.config.js +0 -124
  189. package/frigg-cli/package.json +0 -54
  190. package/frigg-cli/start-command/index.js +0 -149
  191. package/frigg-cli/start-command/start-command.test.js +0 -297
  192. package/frigg-cli/test/init-command.test.js +0 -180
  193. package/frigg-cli/test/npm-registry.test.js +0 -319
  194. package/frigg-cli/ui-command/index.js +0 -154
  195. package/frigg-cli/utils/app-resolver.js +0 -319
  196. package/frigg-cli/utils/backend-path.js +0 -25
  197. package/frigg-cli/utils/database-validator.js +0 -161
  198. package/frigg-cli/utils/error-messages.js +0 -257
  199. package/frigg-cli/utils/npm-registry.js +0 -167
  200. package/frigg-cli/utils/prisma-runner.js +0 -280
  201. package/frigg-cli/utils/process-manager.js +0 -199
  202. package/frigg-cli/utils/repo-detection.js +0 -405
  203. package/infrastructure/aws-discovery.js +0 -1176
  204. package/infrastructure/aws-discovery.test.js +0 -1220
  205. package/infrastructure/serverless-template.js +0 -2074
  206. /package/infrastructure/{WEBSOCKET-CONFIGURATION.md → docs/WEBSOCKET-CONFIGURATION.md} +0 -0
  207. /package/infrastructure/{GENERATE-IAM-DOCS.md → docs/generate-iam-command.md} +0 -0
  208. /package/infrastructure/{iam-generator.test.js → domains/security/iam-generator.test.js} +0 -0
  209. /package/infrastructure/{frigg-deployment-iam-stack.yaml → domains/security/templates/frigg-deployment-iam-stack.yaml} +0 -0
  210. /package/infrastructure/{iam-policy-basic.json → domains/security/templates/iam-policy-basic.json} +0 -0
  211. /package/infrastructure/{iam-policy-full.json → domains/security/templates/iam-policy-full.json} +0 -0
  212. /package/infrastructure/{run-discovery.js → scripts/run-discovery.js} +0 -0
@@ -1,1176 +0,0 @@
1
- let EC2Client,
2
- DescribeVpcsCommand,
3
- DescribeSubnetsCommand,
4
- DescribeSecurityGroupsCommand,
5
- DescribeRouteTablesCommand,
6
- DescribeNatGatewaysCommand,
7
- DescribeAddressesCommand,
8
- DescribeInternetGatewaysCommand;
9
- let KMSClient, ListKeysCommand, DescribeKeyCommand;
10
- let STSClient, GetCallerIdentityCommand;
11
-
12
- function loadEC2() {
13
- if (!EC2Client) {
14
- ({
15
- EC2Client,
16
- DescribeVpcsCommand,
17
- DescribeSubnetsCommand,
18
- DescribeSecurityGroupsCommand,
19
- DescribeRouteTablesCommand,
20
- DescribeNatGatewaysCommand,
21
- DescribeAddressesCommand,
22
- DescribeInternetGatewaysCommand,
23
- } = require('@aws-sdk/client-ec2'));
24
- }
25
- }
26
-
27
- function loadKMS() {
28
- if (!KMSClient) {
29
- ({
30
- KMSClient,
31
- ListKeysCommand,
32
- DescribeKeyCommand,
33
- } = require('@aws-sdk/client-kms'));
34
- }
35
- }
36
-
37
- function loadSTS() {
38
- if (!STSClient) {
39
- ({
40
- STSClient,
41
- GetCallerIdentityCommand,
42
- } = require('@aws-sdk/client-sts'));
43
- }
44
- }
45
-
46
- class AWSDiscovery {
47
- constructor(region = 'us-east-1') {
48
- this.region = region;
49
- loadEC2();
50
- loadKMS();
51
- loadSTS();
52
- this.ec2Client = new EC2Client({ region });
53
- this.kmsClient = new KMSClient({ region });
54
- this.stsClient = new STSClient({ region });
55
- }
56
-
57
- async getAccountId() {
58
- try {
59
- const command = new GetCallerIdentityCommand({});
60
- const response = await this.stsClient.send(command);
61
- return response.Account;
62
- } catch (error) {
63
- console.error('Error getting AWS account ID:', error.message);
64
- throw error;
65
- }
66
- }
67
-
68
- async findDefaultVpc() {
69
- try {
70
- const command = new DescribeVpcsCommand({
71
- Filters: [
72
- {
73
- Name: 'is-default',
74
- Values: ['true'],
75
- },
76
- ],
77
- });
78
-
79
- const response = await this.ec2Client.send(command);
80
-
81
- if (response.Vpcs && response.Vpcs.length > 0) {
82
- return response.Vpcs[0];
83
- }
84
-
85
- const allVpcsCommand = new DescribeVpcsCommand({});
86
- const allVpcsResponse = await this.ec2Client.send(allVpcsCommand);
87
-
88
- if (allVpcsResponse.Vpcs && allVpcsResponse.Vpcs.length > 0) {
89
- console.log('No default VPC found, using first available VPC');
90
- return allVpcsResponse.Vpcs[0];
91
- }
92
-
93
- throw new Error('No VPC found in the account');
94
- } catch (error) {
95
- console.error('Error finding default VPC:', error.message);
96
- throw error;
97
- }
98
- }
99
-
100
- async findPrivateSubnets(vpcId, autoConvert = false) {
101
- try {
102
- const subnets = await this._fetchSubnets(vpcId);
103
-
104
- if (subnets.length === 0) {
105
- throw new Error(`No subnets found in VPC ${vpcId}`);
106
- }
107
-
108
- console.log(`\n🔍 Analyzing ${subnets.length} subnets in VPC ${vpcId}...`);
109
-
110
- const { privateSubnets, publicSubnets } = await this._classifySubnets(
111
- subnets,
112
- { logDetails: true }
113
- );
114
-
115
- this._logSubnetSummary(privateSubnets.length, publicSubnets.length);
116
-
117
- const selection = this._selectSubnetsForLambda({
118
- privateSubnets,
119
- publicSubnets,
120
- autoConvert,
121
- vpcId,
122
- });
123
-
124
- if (selection) {
125
- return selection;
126
- }
127
-
128
- throw new Error(`No subnets found in VPC ${vpcId}`);
129
- } catch (error) {
130
- console.error('Error finding private subnets:', error);
131
- throw error;
132
- }
133
- }
134
-
135
- async isSubnetPublic(subnetId, vpcId) {
136
- const isPrivate = await this.isSubnetPrivate(subnetId, vpcId);
137
- return !isPrivate;
138
- }
139
-
140
- async isSubnetPrivate(subnetId, vpcId) {
141
- try {
142
- const targetVpcId = vpcId || (await this._getSubnetVpcId(subnetId));
143
-
144
- const routeTables = await this.findRouteTables(targetVpcId);
145
- const routeTable = this._findRouteTableForSubnet(routeTables, subnetId);
146
-
147
- if (!routeTable) {
148
- console.warn(`No route table found for subnet ${subnetId}`);
149
- return true;
150
- }
151
-
152
- const gatewayId = this._findIgwRoute(routeTable);
153
- if (gatewayId) {
154
- console.log(
155
- `✅ Subnet ${subnetId} is PUBLIC (has route to IGW ${gatewayId})`
156
- );
157
- return false;
158
- }
159
-
160
- console.log(
161
- `🔒 Subnet ${subnetId} is PRIVATE (no IGW route found)`
162
- );
163
- return true;
164
- } catch (error) {
165
- console.warn(
166
- `Could not determine if subnet ${subnetId} is private:`,
167
- error
168
- );
169
- return true;
170
- }
171
- }
172
-
173
- async findDefaultSecurityGroup(vpcId) {
174
- try {
175
- const friggGroup = await this._findSecurityGroupByName(
176
- vpcId,
177
- 'frigg-lambda-sg'
178
- );
179
- if (friggGroup) {
180
- return friggGroup;
181
- }
182
-
183
- const defaultGroup = await this._findSecurityGroupByName(vpcId, 'default');
184
- if (defaultGroup) {
185
- return defaultGroup;
186
- }
187
-
188
- throw new Error(`No security group found for VPC ${vpcId}`);
189
- } catch (error) {
190
- console.error('Error finding default security group:', error);
191
- throw error;
192
- }
193
- }
194
-
195
- async findPublicSubnets(vpcId) {
196
- try {
197
- const subnets = await this._fetchSubnets(vpcId);
198
-
199
- if (subnets.length === 0) {
200
- throw new Error(`No subnets found in VPC ${vpcId}`);
201
- }
202
-
203
- const { publicSubnets } = await this._classifySubnets(subnets);
204
-
205
- if (publicSubnets.length === 0) {
206
- console.warn(
207
- `WARNING: No public subnets found in VPC ${vpcId}`
208
- );
209
- console.warn(
210
- 'A public subnet with Internet Gateway route is required for NAT Gateway placement'
211
- );
212
- console.warn(
213
- 'Please create a public subnet or use VPC endpoints instead'
214
- );
215
- return null;
216
- }
217
-
218
- console.log(
219
- `Found ${publicSubnets.length} public subnets, using ${publicSubnets[0].SubnetId} for NAT Gateway`
220
- );
221
- return publicSubnets[0];
222
- } catch (error) {
223
- console.error('Error finding public subnets:', error);
224
- throw error;
225
- }
226
- }
227
-
228
- async findPrivateRouteTable(vpcId) {
229
- try {
230
- const routeTables = await this.findRouteTables(vpcId);
231
-
232
- if (routeTables.length === 0) {
233
- throw new Error(`No route tables found for VPC ${vpcId}`);
234
- }
235
-
236
- const privateTable = routeTables.find(
237
- (rt) => !this._findIgwRoute(rt)
238
- );
239
-
240
- return privateTable || routeTables[0];
241
- } catch (error) {
242
- console.error('Error finding private route table:', error);
243
- throw error;
244
- }
245
- }
246
-
247
- async findExistingNatGateway(vpcId) {
248
- try {
249
- const command = new DescribeNatGatewaysCommand({
250
- Filter: [
251
- {
252
- Name: 'vpc-id',
253
- Values: [vpcId],
254
- },
255
- {
256
- Name: 'state',
257
- Values: ['available'],
258
- },
259
- ],
260
- });
261
-
262
- const response = await this.ec2Client.send(command);
263
-
264
- const natGateways = (response.NatGateways || []).filter((nat) => {
265
- if (nat.State !== 'available') {
266
- console.warn(
267
- `Skipping NAT Gateway ${nat.NatGatewayId} with state: ${nat.State}`
268
- );
269
- return false;
270
- }
271
- return true;
272
- });
273
-
274
- if (natGateways.length === 0) {
275
- console.warn('No truly available NAT Gateways found in VPC');
276
- return null;
277
- }
278
-
279
- const sortedNatGateways = natGateways.sort((a, b) => {
280
- const aIsFrigg = this._isFriggManaged(a.Tags);
281
- const bIsFrigg = this._isFriggManaged(b.Tags);
282
-
283
- if (aIsFrigg && !bIsFrigg) return -1;
284
- if (!aIsFrigg && bIsFrigg) return 1;
285
- return 0;
286
- });
287
-
288
- for (const natGateway of sortedNatGateways) {
289
- const subnetId = natGateway.SubnetId;
290
- const isPrivate = await this.isSubnetPrivate(
291
- subnetId,
292
- natGateway.VpcId
293
- );
294
- const isFriggNat = this._isFriggManaged(natGateway.Tags);
295
-
296
- if (isPrivate) {
297
- console.warn(
298
- `WARNING: NAT Gateway ${natGateway.NatGatewayId} is in subnet ${subnetId} which appears to be private`
299
- );
300
-
301
- if (isFriggNat) {
302
- console.warn(
303
- 'This is a Frigg-managed NAT Gateway that may have been misconfigured by route table changes'
304
- );
305
- console.warn(
306
- 'Consider enabling selfHeal: true to fix this automatically'
307
- );
308
- natGateway._isInPrivateSubnet = true;
309
- return natGateway;
310
- }
311
-
312
- console.warn(
313
- 'NAT Gateways MUST be placed in public subnets with Internet Gateway routes'
314
- );
315
- console.warn('Skipping this misconfigured NAT Gateway...');
316
- continue;
317
- }
318
-
319
- if (isFriggNat) {
320
- console.log(
321
- `Found existing Frigg-managed NAT Gateway: ${natGateway.NatGatewayId} (State: ${natGateway.State})`
322
- );
323
- natGateway._isInPrivateSubnet = false;
324
- return natGateway;
325
- }
326
-
327
- console.log(
328
- `Found existing NAT Gateway in public subnet: ${natGateway.NatGatewayId} (State: ${natGateway.State})`
329
- );
330
- natGateway._isInPrivateSubnet = false;
331
- return natGateway;
332
- }
333
-
334
- console.error(
335
- `ERROR: Found ${(response.NatGateways || []).length} NAT Gateway(s) but all non-Frigg ones are in private subnets!`
336
- );
337
- console.error(
338
- 'These NAT Gateways will not provide internet connectivity without route table fixes'
339
- );
340
- console.error(
341
- 'Enable selfHeal: true to fix automatically or create a new NAT Gateway'
342
- );
343
- return null;
344
- } catch (error) {
345
- console.warn('Error finding existing NAT Gateway:', error.message);
346
- return null;
347
- }
348
- }
349
-
350
- async findAvailableElasticIP() {
351
- try {
352
- const command = new DescribeAddressesCommand({});
353
- const response = await this.ec2Client.send(command);
354
-
355
- if (response.Addresses && response.Addresses.length > 0) {
356
- const availableEIP = response.Addresses.find(
357
- (eip) =>
358
- !eip.AssociationId &&
359
- !eip.InstanceId &&
360
- !eip.NetworkInterfaceId
361
- );
362
-
363
- if (availableEIP) {
364
- console.log(
365
- `Found available Elastic IP: ${availableEIP.AllocationId}`
366
- );
367
- return availableEIP;
368
- }
369
-
370
- const friggEIP = response.Addresses.find((eip) =>
371
- this._isFriggManaged(eip.Tags)
372
- );
373
-
374
- if (friggEIP) {
375
- console.log(
376
- `Found Frigg-tagged Elastic IP: ${friggEIP.AllocationId}`
377
- );
378
- return friggEIP;
379
- }
380
- }
381
-
382
- return null;
383
- } catch (error) {
384
- console.warn('Error finding available Elastic IP:', error.message);
385
- return null;
386
- }
387
- }
388
-
389
- async findDefaultKmsKey() {
390
- console.log('KMS Discovery Starting...');
391
- try {
392
- console.log(`[KMS Discovery] Running in region: ${this.region}`);
393
- try {
394
- const accountId = await this.getAccountId();
395
- console.log(`[KMS Discovery] AWS Account ID: ${accountId}`);
396
- } catch (error) {
397
- console.warn(
398
- '[KMS Discovery] Could not retrieve account ID:',
399
- error.message
400
- );
401
- }
402
-
403
- const command = new ListKeysCommand({});
404
- const response = await this.kmsClient.send(command);
405
-
406
- if (!response.Keys || response.Keys.length === 0) {
407
- console.log('[KMS Discovery] No KMS keys found in account');
408
- return null;
409
- }
410
-
411
- console.log(
412
- `[KMS Discovery] Found ${response.Keys.length} total keys in account`
413
- );
414
- let keysExamined = 0;
415
- let customerManagedKeys = 0;
416
- let enabledKeys = 0;
417
- let pendingDeletionKeys = 0;
418
-
419
- for (const key of response.Keys) {
420
- try {
421
- const describeCommand = new DescribeKeyCommand({
422
- KeyId: key.KeyId,
423
- });
424
- const keyDetails = await this.kmsClient.send(
425
- describeCommand
426
- );
427
- keysExamined++;
428
-
429
- const metadata = keyDetails.KeyMetadata;
430
- if (!metadata) {
431
- continue;
432
- }
433
-
434
- console.log(`[KMS Discovery] Key ${key.KeyId}:`, {
435
- KeyManager: metadata.KeyManager,
436
- KeyState: metadata.KeyState,
437
- Enabled: metadata.Enabled,
438
- DeletionDate:
439
- metadata.DeletionDate || 'Not scheduled for deletion',
440
- Arn: metadata.Arn,
441
- });
442
-
443
- if (metadata.KeyManager === 'CUSTOMER') {
444
- customerManagedKeys++;
445
-
446
- if (metadata.KeyState === 'Enabled') {
447
- enabledKeys++;
448
- } else if (metadata.KeyState === 'PendingDeletion') {
449
- pendingDeletionKeys++;
450
- console.warn(
451
- `[KMS Discovery] Skipping key ${key.KeyId} - State: PendingDeletion, DeletionDate: ${metadata.DeletionDate}`
452
- );
453
- }
454
-
455
- if (
456
- metadata.KeyState === 'Enabled' &&
457
- !metadata.DeletionDate
458
- ) {
459
- console.log(
460
- `[KMS Discovery] Found eligible customer managed KMS key: ${metadata.Arn}`
461
- );
462
- return metadata.Arn;
463
- } else if (
464
- metadata.KeyState === 'Enabled' &&
465
- metadata.DeletionDate
466
- ) {
467
- console.error(
468
- `[KMS Discovery] WARNING: Key ${key.KeyId} has KeyState='Enabled' but DeletionDate is set: ${metadata.DeletionDate}`
469
- );
470
- }
471
- }
472
- } catch (error) {
473
- console.warn(
474
- `[KMS Discovery] Could not describe key ${key.KeyId}:`,
475
- error.message
476
- );
477
- continue;
478
- }
479
- }
480
-
481
- console.log('[KMS Discovery] Summary:', {
482
- totalKeys: response.Keys.length,
483
- keysExamined,
484
- customerManagedKeys,
485
- enabledKeys,
486
- pendingDeletionKeys,
487
- });
488
-
489
- if (customerManagedKeys === 0) {
490
- console.log(
491
- '[KMS Discovery] No customer managed KMS keys found in account'
492
- );
493
- } else if (enabledKeys === 0) {
494
- console.warn(
495
- '[KMS Discovery] Found customer managed keys but none are in Enabled state'
496
- );
497
- } else {
498
- console.warn(
499
- '[KMS Discovery] Found enabled customer managed keys but none met all criteria'
500
- );
501
- }
502
-
503
- return null;
504
- } catch (error) {
505
- console.error(
506
- '[KMS Discovery] Error finding default KMS key:',
507
- error
508
- );
509
- return null;
510
- }
511
- }
512
-
513
- async detectMisconfiguredResources(vpcId) {
514
- try {
515
- const misconfigurations = {
516
- natGatewaysInPrivateSubnets: [],
517
- orphanedElasticIps: [],
518
- misconfiguredRouteTables: [],
519
- privateSubnetsWithoutNatRoute: [],
520
- };
521
-
522
- const natCommand = new DescribeNatGatewaysCommand({
523
- Filter: [
524
- { Name: 'vpc-id', Values: [vpcId] },
525
- { Name: 'state', Values: ['available'] },
526
- ],
527
- });
528
- const natResponse = await this.ec2Client.send(natCommand);
529
-
530
- for (const nat of natResponse.NatGateways || []) {
531
- const isPrivate = await this.isSubnetPrivate(nat.SubnetId, vpcId);
532
- if (isPrivate) {
533
- misconfigurations.natGatewaysInPrivateSubnets.push({
534
- natGatewayId: nat.NatGatewayId,
535
- subnetId: nat.SubnetId,
536
- tags: nat.Tags,
537
- });
538
- }
539
- }
540
-
541
- const eipCommand = new DescribeAddressesCommand({});
542
- const eipResponse = await this.ec2Client.send(eipCommand);
543
-
544
- for (const eip of eipResponse.Addresses || []) {
545
- if (
546
- !eip.InstanceId &&
547
- !eip.NetworkInterfaceId &&
548
- !eip.AssociationId &&
549
- this._isFriggManaged(eip.Tags)
550
- ) {
551
- misconfigurations.orphanedElasticIps.push({
552
- allocationId: eip.AllocationId,
553
- publicIp: eip.PublicIp,
554
- tags: eip.Tags,
555
- });
556
- }
557
- }
558
-
559
- const subnets = await this.findPrivateSubnets(vpcId);
560
- const routeTables = await this.findRouteTables(vpcId);
561
-
562
- for (const subnet of subnets) {
563
- const hasNatRoute = routeTables.some((rt) => {
564
- const isAssociated = (rt.Associations || []).some(
565
- (assoc) => assoc.SubnetId === subnet.SubnetId
566
- );
567
- if (!isAssociated) {
568
- return false;
569
- }
570
- return (rt.Routes || []).some(
571
- (route) =>
572
- route.NatGatewayId &&
573
- route.DestinationCidrBlock === '0.0.0.0/0'
574
- );
575
- });
576
-
577
- if (!hasNatRoute) {
578
- misconfigurations.privateSubnetsWithoutNatRoute.push({
579
- subnetId: subnet.SubnetId,
580
- availabilityZone: subnet.AvailabilityZone,
581
- });
582
- }
583
- }
584
-
585
- return misconfigurations;
586
- } catch (error) {
587
- console.error('Error detecting misconfigurations:', error);
588
- return {
589
- natGatewaysInPrivateSubnets: [],
590
- orphanedElasticIps: [],
591
- misconfiguredRouteTables: [],
592
- privateSubnetsWithoutNatRoute: [],
593
- };
594
- }
595
- }
596
-
597
- getHealingRecommendations(misconfigurations) {
598
- const recommendations = [];
599
-
600
- if (misconfigurations.natGatewaysInPrivateSubnets.length > 0) {
601
- recommendations.push({
602
- severity: 'critical',
603
- issue: 'NAT Gateway in private subnet',
604
- recommendation:
605
- 'Recreate NAT Gateway in public subnet or fix route tables',
606
- affectedResources:
607
- misconfigurations.natGatewaysInPrivateSubnets.map(
608
- (n) => n.natGatewayId
609
- ),
610
- });
611
- }
612
-
613
- if (misconfigurations.orphanedElasticIps.length > 0) {
614
- recommendations.push({
615
- severity: 'warning',
616
- issue: 'Orphaned Elastic IPs',
617
- recommendation: 'Release unused Elastic IPs to avoid charges',
618
- affectedResources: misconfigurations.orphanedElasticIps.map(
619
- (e) => e.allocationId
620
- ),
621
- });
622
- }
623
-
624
- if (misconfigurations.privateSubnetsWithoutNatRoute.length > 0) {
625
- recommendations.push({
626
- severity: 'critical',
627
- issue: 'Private subnets without NAT route',
628
- recommendation:
629
- 'Add NAT Gateway route to private subnet route tables',
630
- affectedResources:
631
- misconfigurations.privateSubnetsWithoutNatRoute.map(
632
- (s) => s.subnetId
633
- ),
634
- });
635
- }
636
-
637
- recommendations.sort((a, b) => {
638
- const severityOrder = { critical: 0, warning: 1, info: 2 };
639
- return severityOrder[a.severity] - severityOrder[b.severity];
640
- });
641
-
642
- return recommendations;
643
- }
644
-
645
- async discoverResources(options = {}) {
646
- try {
647
- console.log(
648
- '\n🚀 Discovering AWS resources for Frigg deployment...'
649
- );
650
- console.log('═'.repeat(60));
651
-
652
- const vpc = await this.findDefaultVpc();
653
- console.log(`\n✅ Found VPC: ${vpc.VpcId}`);
654
-
655
- const autoConvert = options.selfHeal || false;
656
-
657
- const privateSubnets = await this.findPrivateSubnets(
658
- vpc.VpcId,
659
- autoConvert
660
- );
661
- console.log(
662
- `\n✅ Selected subnets for Lambda: ${privateSubnets
663
- .map((s) => s.SubnetId)
664
- .join(', ')}`
665
- );
666
-
667
- const publicSubnet = await this.findPublicSubnets(vpc.VpcId);
668
- if (publicSubnet) {
669
- console.log(
670
- `\n✅ Found public subnet for NAT Gateway: ${publicSubnet.SubnetId}`
671
- );
672
- } else {
673
- console.log(
674
- `\n⚠️ No public subnet found - NAT Gateway creation may fail`
675
- );
676
- }
677
-
678
- const securityGroup = await this.findDefaultSecurityGroup(
679
- vpc.VpcId
680
- );
681
- console.log(`\n✅ Found security group: ${securityGroup.GroupId}`);
682
-
683
- const routeTable = await this.findPrivateRouteTable(vpc.VpcId);
684
- console.log(`✅ Found route table: ${routeTable.RouteTableId}`);
685
-
686
- const kmsKeyArn = await this.findDefaultKmsKey();
687
- if (kmsKeyArn) {
688
- console.log(`✅ Found KMS key: ${kmsKeyArn}`);
689
- } else {
690
- console.log('ℹ️ No KMS key found');
691
- }
692
-
693
- const existingNatGateway = await this.findExistingNatGateway(
694
- vpc.VpcId
695
- );
696
- let natGatewayId = null;
697
- let elasticIpAllocationId = null;
698
- let natGatewayInPrivateSubnet = false;
699
-
700
- if (existingNatGateway) {
701
- natGatewayId = existingNatGateway.NatGatewayId;
702
- natGatewayInPrivateSubnet =
703
- existingNatGateway._isInPrivateSubnet || false;
704
-
705
- if (
706
- existingNatGateway.NatGatewayAddresses &&
707
- existingNatGateway.NatGatewayAddresses.length > 0
708
- ) {
709
- elasticIpAllocationId =
710
- existingNatGateway.NatGatewayAddresses[0].AllocationId;
711
- }
712
- } else {
713
- const availableEIP = await this.findAvailableElasticIP();
714
- if (availableEIP) {
715
- elasticIpAllocationId = availableEIP.AllocationId;
716
- }
717
- }
718
-
719
- const subnet1IsActuallyPrivate = privateSubnets[0]
720
- ? await this.isSubnetPrivate(
721
- privateSubnets[0].SubnetId,
722
- privateSubnets[0].VpcId || vpc.VpcId
723
- )
724
- : false;
725
- const subnet2IsActuallyPrivate = privateSubnets[1]
726
- ? await this.isSubnetPrivate(
727
- privateSubnets[1].SubnetId,
728
- privateSubnets[1].VpcId || vpc.VpcId
729
- )
730
- : subnet1IsActuallyPrivate;
731
-
732
- const subnetStatus = {
733
- requiresConversion:
734
- !subnet1IsActuallyPrivate || !subnet2IsActuallyPrivate,
735
- subnet1NeedsConversion: !subnet1IsActuallyPrivate,
736
- subnet2NeedsConversion: !subnet2IsActuallyPrivate,
737
- };
738
-
739
- if (subnetStatus.requiresConversion) {
740
- console.log(`\n⚠️ SUBNET CONFIGURATION WARNING:`);
741
- if (subnetStatus.subnet1NeedsConversion && privateSubnets[0]) {
742
- console.log(
743
- ` - Subnet ${privateSubnets[0].SubnetId} is currently PUBLIC but will be used for Lambda`
744
- );
745
- }
746
- if (subnetStatus.subnet2NeedsConversion && privateSubnets[1]) {
747
- console.log(
748
- ` - Subnet ${privateSubnets[1].SubnetId} is currently PUBLIC but will be used for Lambda`
749
- );
750
- }
751
- console.log(
752
- ` 💡 Enable selfHeal: true to automatically fix this`
753
- );
754
- }
755
-
756
- console.log(`\n${'═'.repeat(60)}`);
757
- console.log('📋 Discovery Summary:');
758
- console.log(` VPC: ${vpc.VpcId}`);
759
- console.log(
760
- ` Lambda Subnets: ${privateSubnets
761
- .map((s) => s.SubnetId)
762
- .join(', ')}`
763
- );
764
- console.log(
765
- ` NAT Subnet: ${
766
- publicSubnet?.SubnetId || 'None (needs creation)'
767
- }`
768
- );
769
- console.log(
770
- ` NAT Gateway: ${natGatewayId || 'None (will be created)'}`
771
- );
772
- console.log(
773
- ` Elastic IP: ${
774
- elasticIpAllocationId || 'None (will be allocated)'
775
- }`
776
- );
777
- if (subnetStatus.requiresConversion) {
778
- console.log(` ⚠️ Subnet Conversion Required: Yes`);
779
- }
780
- console.log(`${'═'.repeat(60)}\n`);
781
-
782
- return {
783
- defaultVpcId: vpc.VpcId,
784
- vpcCidr: vpc.CidrBlock,
785
- defaultSecurityGroupId: securityGroup.GroupId,
786
- privateSubnetId1: privateSubnets[0]?.SubnetId,
787
- privateSubnetId2:
788
- privateSubnets[1]?.SubnetId || privateSubnets[0]?.SubnetId,
789
- publicSubnetId: publicSubnet?.SubnetId || null,
790
- privateRouteTableId: routeTable.RouteTableId,
791
- defaultKmsKeyId: kmsKeyArn,
792
- existingNatGatewayId: natGatewayId,
793
- existingElasticIpAllocationId: elasticIpAllocationId,
794
- natGatewayInPrivateSubnet: natGatewayInPrivateSubnet,
795
- subnetConversionRequired: subnetStatus.requiresConversion,
796
- privateSubnetsWithWrongRoutes: (() => {
797
- const wrongRoutes = [];
798
- if (
799
- subnetStatus.subnet1NeedsConversion &&
800
- privateSubnets[0]
801
- ) {
802
- wrongRoutes.push(privateSubnets[0].SubnetId);
803
- }
804
- if (
805
- subnetStatus.subnet2NeedsConversion &&
806
- privateSubnets[1]
807
- ) {
808
- wrongRoutes.push(privateSubnets[1].SubnetId);
809
- }
810
- return wrongRoutes;
811
- })(),
812
- };
813
- } catch (error) {
814
- console.error('Error discovering AWS resources:', error);
815
- throw error;
816
- }
817
- }
818
-
819
- async findInternetGateway(vpcId) {
820
- try {
821
- const command = new DescribeInternetGatewaysCommand({
822
- Filters: [
823
- {
824
- Name: 'attachment.vpc-id',
825
- Values: [vpcId],
826
- },
827
- {
828
- Name: 'attachment.state',
829
- Values: ['available'],
830
- },
831
- ],
832
- });
833
-
834
- const response = await this.ec2Client.send(command);
835
-
836
- if (
837
- response.InternetGateways &&
838
- response.InternetGateways.length > 0
839
- ) {
840
- console.log(
841
- `Found existing Internet Gateway: ${response.InternetGateways[0].InternetGatewayId}`
842
- );
843
- return response.InternetGateways[0];
844
- }
845
-
846
- return null;
847
- } catch (error) {
848
- console.warn('Error finding Internet Gateway:', error.message);
849
- return null;
850
- }
851
- }
852
-
853
- async findFriggManagedResources(serviceName, stage) {
854
- const results = {
855
- natGateways: [],
856
- elasticIps: [],
857
- routeTables: [],
858
- subnets: [],
859
- securityGroups: [],
860
- };
861
-
862
- try {
863
- const filters = [
864
- {
865
- Name: 'tag:ManagedBy',
866
- Values: ['Frigg'],
867
- },
868
- ];
869
-
870
- if (serviceName) {
871
- filters.push({
872
- Name: 'tag:Service',
873
- Values: [serviceName],
874
- });
875
- }
876
-
877
- if (stage) {
878
- filters.push({
879
- Name: 'tag:Stage',
880
- Values: [stage],
881
- });
882
- }
883
-
884
- const fetchWithFallback = async (Command, input, field, label) => {
885
- try {
886
- const response = await this.ec2Client.send(
887
- new Command(input)
888
- );
889
- return response[field] || [];
890
- } catch (err) {
891
- console.warn(
892
- `Error finding Frigg ${label}:`,
893
- err.message
894
- );
895
- return [];
896
- }
897
- };
898
-
899
- results.natGateways = await fetchWithFallback(
900
- DescribeNatGatewaysCommand,
901
- {
902
- Filter: [
903
- ...filters,
904
- {
905
- Name: 'state',
906
- Values: ['available'],
907
- },
908
- ],
909
- },
910
- 'NatGateways',
911
- 'NAT Gateways'
912
- );
913
-
914
- results.elasticIps = await fetchWithFallback(
915
- DescribeAddressesCommand,
916
- { Filters: filters },
917
- 'Addresses',
918
- 'Elastic IPs'
919
- );
920
-
921
- results.routeTables = await fetchWithFallback(
922
- DescribeRouteTablesCommand,
923
- { Filters: filters },
924
- 'RouteTables',
925
- 'Route Tables'
926
- );
927
-
928
- results.subnets = await fetchWithFallback(
929
- DescribeSubnetsCommand,
930
- { Filters: filters },
931
- 'Subnets',
932
- 'Subnets'
933
- );
934
-
935
- results.securityGroups = await fetchWithFallback(
936
- DescribeSecurityGroupsCommand,
937
- { Filters: filters },
938
- 'SecurityGroups',
939
- 'Security Groups'
940
- );
941
-
942
- console.log('Found Frigg-managed resources:', {
943
- natGateways: results.natGateways.length,
944
- elasticIps: results.elasticIps.length,
945
- routeTables: results.routeTables.length,
946
- subnets: results.subnets.length,
947
- securityGroups: results.securityGroups.length,
948
- });
949
-
950
- return results;
951
- } catch (error) {
952
- console.error('Error finding Frigg-managed resources:', error);
953
- return results;
954
- }
955
- }
956
-
957
- async findRouteTables(vpcId) {
958
- const command = new DescribeRouteTablesCommand({
959
- Filters: [
960
- {
961
- Name: 'vpc-id',
962
- Values: [vpcId],
963
- },
964
- ],
965
- });
966
- const response = await this.ec2Client.send(command);
967
- return response.RouteTables || [];
968
- }
969
-
970
- async _fetchSubnets(vpcId) {
971
- const command = new DescribeSubnetsCommand({
972
- Filters: [
973
- {
974
- Name: 'vpc-id',
975
- Values: [vpcId],
976
- },
977
- ],
978
- });
979
- const response = await this.ec2Client.send(command);
980
- return response.Subnets || [];
981
- }
982
-
983
- async _getSubnetVpcId(subnetId) {
984
- const command = new DescribeSubnetsCommand({
985
- SubnetIds: [subnetId],
986
- });
987
- const response = await this.ec2Client.send(command);
988
-
989
- if (!response.Subnets || response.Subnets.length === 0) {
990
- throw new Error(`Subnet ${subnetId} not found`);
991
- }
992
-
993
- return response.Subnets[0].VpcId;
994
- }
995
-
996
- async _classifySubnets(subnets, { logDetails = false } = {}) {
997
- const privateSubnets = [];
998
- const publicSubnets = [];
999
-
1000
- for (const subnet of subnets) {
1001
- const isPrivate = await this.isSubnetPrivate(
1002
- subnet.SubnetId,
1003
- subnet.VpcId
1004
- );
1005
- if (isPrivate) {
1006
- privateSubnets.push(subnet);
1007
- if (logDetails) {
1008
- console.log(
1009
- ` 🔒 Private subnet: ${subnet.SubnetId} (AZ: ${subnet.AvailabilityZone})`
1010
- );
1011
- }
1012
- } else {
1013
- publicSubnets.push(subnet);
1014
- if (logDetails) {
1015
- console.log(
1016
- ` 🌐 Public subnet: ${subnet.SubnetId} (AZ: ${subnet.AvailabilityZone})`
1017
- );
1018
- }
1019
- }
1020
- }
1021
-
1022
- return { privateSubnets, publicSubnets };
1023
- }
1024
-
1025
- _logSubnetSummary(privateCount, publicCount) {
1026
- console.log(`\n📊 Subnet Analysis Results:`);
1027
- console.log(` - Private subnets: ${privateCount}`);
1028
- console.log(` - Public subnets: ${publicCount}`);
1029
- }
1030
-
1031
- _selectSubnetsForLambda({ privateSubnets, publicSubnets, autoConvert, vpcId }) {
1032
- if (privateSubnets.length >= 2) {
1033
- console.log(
1034
- `✅ Found ${privateSubnets.length} private subnets for Lambda deployment`
1035
- );
1036
- return privateSubnets.slice(0, 2);
1037
- }
1038
-
1039
- if (privateSubnets.length === 1) {
1040
- console.warn(
1041
- `⚠️ Only 1 private subnet found. Need at least 2 for high availability.`
1042
- );
1043
- if (publicSubnets.length > 0 && autoConvert) {
1044
- console.log(
1045
- `🔄 Will convert 1 public subnet to private for high availability...`
1046
- );
1047
- }
1048
- return [...privateSubnets, ...publicSubnets].slice(0, 2);
1049
- }
1050
-
1051
- if (privateSubnets.length === 0 && publicSubnets.length > 0) {
1052
- console.error(
1053
- `❌ CRITICAL: No private subnets found, but ${publicSubnets.length} public subnets exist`
1054
- );
1055
- console.error(
1056
- `❌ Lambda functions should NOT be deployed in public subnets!`
1057
- );
1058
-
1059
- if (autoConvert && publicSubnets.length >= 3) {
1060
- console.log(
1061
- `\n🔧 AUTO-CONVERSION: Will configure subnets for proper isolation...`
1062
- );
1063
- console.log(
1064
- ` - Keeping ${publicSubnets[0].SubnetId} as public (for NAT Gateway)`
1065
- );
1066
- console.log(
1067
- ` - Converting ${publicSubnets[1].SubnetId} to private (for Lambda)`
1068
- );
1069
- if (publicSubnets[2]) {
1070
- console.log(
1071
- ` - Converting ${publicSubnets[2].SubnetId} to private (for Lambda)`
1072
- );
1073
- }
1074
- return publicSubnets.slice(1, 3);
1075
- }
1076
-
1077
- if (autoConvert && publicSubnets.length >= 2) {
1078
- console.log(
1079
- `\n🔧 AUTO-CONVERSION: Only ${publicSubnets.length} subnets available`
1080
- );
1081
- console.log(
1082
- ` - Will need to create new subnets or reconfigure existing ones`
1083
- );
1084
- return publicSubnets.slice(0, 2);
1085
- }
1086
-
1087
- console.error(`\n⚠️ CONFIGURATION ERROR:`);
1088
- console.error(
1089
- ` Found ${publicSubnets.length} public subnets but no private subnets.`
1090
- );
1091
- console.error(
1092
- ` Lambda functions require private subnets for security.`
1093
- );
1094
- console.error(`\n Options:`);
1095
- console.error(
1096
- ` 1. Enable selfHeal: true in vpc configuration`
1097
- );
1098
- console.error(` 2. Create private subnets manually`);
1099
- console.error(
1100
- ` 3. Set subnets.management: 'create' to create new private subnets`
1101
- );
1102
-
1103
- throw new Error(
1104
- `No private subnets found in VPC ${vpcId}. ` +
1105
- `Found ${publicSubnets.length} public subnets. ` +
1106
- `Lambda requires private subnets. Enable selfHeal or create private subnets.`
1107
- );
1108
- }
1109
-
1110
- return null;
1111
- }
1112
-
1113
- _findRouteTableForSubnet(routeTables, subnetId) {
1114
- for (const rt of routeTables) {
1115
- for (const assoc of rt.Associations || []) {
1116
- if (assoc.SubnetId === subnetId) {
1117
- return rt;
1118
- }
1119
- }
1120
- }
1121
-
1122
- for (const rt of routeTables) {
1123
- for (const assoc of rt.Associations || []) {
1124
- if (assoc.Main === true) {
1125
- return rt;
1126
- }
1127
- }
1128
- }
1129
-
1130
- return null;
1131
- }
1132
-
1133
- _findIgwRoute(routeTable) {
1134
- for (const route of routeTable.Routes || []) {
1135
- if (route.GatewayId && route.GatewayId.startsWith('igw-')) {
1136
- return route.GatewayId;
1137
- }
1138
- }
1139
- return null;
1140
- }
1141
-
1142
- _isFriggManaged(tags) {
1143
- if (!tags) {
1144
- return false;
1145
- }
1146
-
1147
- return tags.some(
1148
- (tag) =>
1149
- (tag.Key === 'ManagedBy' && tag.Value === 'Frigg') ||
1150
- (tag.Key === 'Name' &&
1151
- typeof tag.Value === 'string' &&
1152
- tag.Value.includes('frigg'))
1153
- );
1154
- }
1155
-
1156
- async _findSecurityGroupByName(vpcId, groupName) {
1157
- const command = new DescribeSecurityGroupsCommand({
1158
- Filters: [
1159
- {
1160
- Name: 'vpc-id',
1161
- Values: [vpcId],
1162
- },
1163
- {
1164
- Name: 'group-name',
1165
- Values: [groupName],
1166
- },
1167
- ],
1168
- });
1169
-
1170
- const response = await this.ec2Client.send(command);
1171
- const groups = response.SecurityGroups || [];
1172
- return groups[0] || null;
1173
- }
1174
- }
1175
-
1176
- module.exports = { AWSDiscovery };