@friggframework/devtools 2.0.0-next.3 → 2.0.0-next.30
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/.eslintrc.js +141 -0
- package/frigg-cli/__tests__/jest.config.js +102 -0
- package/frigg-cli/__tests__/unit/commands/build.test.js +483 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +418 -0
- package/frigg-cli/__tests__/unit/commands/ui.test.js +592 -0
- package/frigg-cli/__tests__/utils/command-tester.js +170 -0
- package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
- package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
- package/frigg-cli/__tests__/utils/test-setup.js +286 -0
- package/frigg-cli/build-command/index.js +54 -0
- package/frigg-cli/deploy-command/index.js +36 -0
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +312 -0
- package/frigg-cli/generate-command/azure-generator.js +43 -0
- package/frigg-cli/generate-command/gcp-generator.js +47 -0
- package/frigg-cli/generate-command/index.js +332 -0
- package/frigg-cli/generate-command/terraform-generator.js +555 -0
- package/frigg-cli/generate-iam-command.js +115 -0
- package/frigg-cli/index.js +47 -1
- package/frigg-cli/index.test.js +1 -4
- package/frigg-cli/init-command/backend-first-handler.js +756 -0
- package/frigg-cli/init-command/index.js +93 -0
- package/frigg-cli/init-command/template-handler.js +143 -0
- package/frigg-cli/install-command/index.js +1 -4
- package/frigg-cli/package.json +51 -0
- package/frigg-cli/start-command/index.js +24 -4
- package/frigg-cli/test/init-command.test.js +180 -0
- package/frigg-cli/test/npm-registry.test.js +319 -0
- package/frigg-cli/ui-command/index.js +154 -0
- package/frigg-cli/utils/app-resolver.js +319 -0
- package/frigg-cli/utils/backend-path.js +16 -17
- package/frigg-cli/utils/npm-registry.js +167 -0
- package/frigg-cli/utils/process-manager.js +199 -0
- package/frigg-cli/utils/repo-detection.js +405 -0
- package/infrastructure/AWS-DISCOVERY-TROUBLESHOOTING.md +245 -0
- package/infrastructure/AWS-IAM-CREDENTIAL-NEEDS.md +596 -0
- package/infrastructure/DEPLOYMENT-INSTRUCTIONS.md +268 -0
- package/infrastructure/GENERATE-IAM-DOCS.md +253 -0
- package/infrastructure/IAM-POLICY-TEMPLATES.md +176 -0
- package/infrastructure/README-TESTING.md +332 -0
- package/infrastructure/README.md +421 -0
- package/infrastructure/WEBSOCKET-CONFIGURATION.md +105 -0
- package/infrastructure/__tests__/fixtures/mock-aws-resources.js +391 -0
- package/infrastructure/__tests__/helpers/test-utils.js +277 -0
- package/infrastructure/aws-discovery.js +568 -0
- package/infrastructure/aws-discovery.test.js +373 -0
- package/infrastructure/build-time-discovery.js +206 -0
- package/infrastructure/build-time-discovery.test.js +375 -0
- package/infrastructure/create-frigg-infrastructure.js +3 -5
- package/infrastructure/frigg-deployment-iam-stack.yaml +379 -0
- package/infrastructure/iam-generator.js +687 -0
- package/infrastructure/iam-generator.test.js +169 -0
- package/infrastructure/iam-policy-basic.json +212 -0
- package/infrastructure/iam-policy-full.json +282 -0
- package/infrastructure/integration.test.js +383 -0
- package/infrastructure/run-discovery.js +110 -0
- package/infrastructure/serverless-template.js +923 -113
- package/infrastructure/serverless-template.test.js +541 -0
- package/management-ui/.eslintrc.js +22 -0
- package/management-ui/README.md +203 -0
- package/management-ui/components.json +21 -0
- package/management-ui/docs/phase2-integration-guide.md +320 -0
- package/management-ui/index.html +13 -0
- package/management-ui/package-lock.json +16517 -0
- package/management-ui/package.json +76 -0
- package/management-ui/packages/devtools/frigg-cli/ui-command/index.js +302 -0
- package/management-ui/postcss.config.js +6 -0
- package/management-ui/server/api/backend.js +256 -0
- package/management-ui/server/api/cli.js +315 -0
- package/management-ui/server/api/codegen.js +663 -0
- package/management-ui/server/api/connections.js +857 -0
- package/management-ui/server/api/discovery.js +185 -0
- package/management-ui/server/api/environment/index.js +1 -0
- package/management-ui/server/api/environment/router.js +378 -0
- package/management-ui/server/api/environment.js +328 -0
- package/management-ui/server/api/integrations.js +876 -0
- package/management-ui/server/api/logs.js +248 -0
- package/management-ui/server/api/monitoring.js +282 -0
- package/management-ui/server/api/open-ide.js +31 -0
- package/management-ui/server/api/project.js +1029 -0
- package/management-ui/server/api/users/sessions.js +371 -0
- package/management-ui/server/api/users/simulation.js +254 -0
- package/management-ui/server/api/users.js +362 -0
- package/management-ui/server/api-contract.md +275 -0
- package/management-ui/server/index.js +873 -0
- package/management-ui/server/middleware/errorHandler.js +93 -0
- package/management-ui/server/middleware/security.js +32 -0
- package/management-ui/server/processManager.js +296 -0
- package/management-ui/server/server.js +346 -0
- package/management-ui/server/services/aws-monitor.js +413 -0
- package/management-ui/server/services/npm-registry.js +347 -0
- package/management-ui/server/services/template-engine.js +538 -0
- package/management-ui/server/utils/cliIntegration.js +220 -0
- package/management-ui/server/utils/environment/auditLogger.js +471 -0
- package/management-ui/server/utils/environment/awsParameterStore.js +264 -0
- package/management-ui/server/utils/environment/encryption.js +278 -0
- package/management-ui/server/utils/environment/envFileManager.js +286 -0
- package/management-ui/server/utils/import-commonjs.js +28 -0
- package/management-ui/server/utils/response.js +83 -0
- package/management-ui/server/websocket/handler.js +325 -0
- package/management-ui/src/App.jsx +109 -0
- package/management-ui/src/assets/FriggLogo.svg +1 -0
- package/management-ui/src/components/AppRouter.jsx +65 -0
- package/management-ui/src/components/Button.jsx +70 -0
- package/management-ui/src/components/Card.jsx +97 -0
- package/management-ui/src/components/EnvironmentCompare.jsx +400 -0
- package/management-ui/src/components/EnvironmentEditor.jsx +372 -0
- package/management-ui/src/components/EnvironmentImportExport.jsx +469 -0
- package/management-ui/src/components/EnvironmentSchema.jsx +491 -0
- package/management-ui/src/components/EnvironmentSecurity.jsx +463 -0
- package/management-ui/src/components/ErrorBoundary.jsx +73 -0
- package/management-ui/src/components/IntegrationCard.jsx +481 -0
- package/management-ui/src/components/IntegrationCardEnhanced.jsx +770 -0
- package/management-ui/src/components/IntegrationExplorer.jsx +379 -0
- package/management-ui/src/components/IntegrationStatus.jsx +336 -0
- package/management-ui/src/components/Layout.jsx +716 -0
- package/management-ui/src/components/LoadingSpinner.jsx +113 -0
- package/management-ui/src/components/RepositoryPicker.jsx +248 -0
- package/management-ui/src/components/SessionMonitor.jsx +350 -0
- package/management-ui/src/components/StatusBadge.jsx +208 -0
- package/management-ui/src/components/UserContextSwitcher.jsx +212 -0
- package/management-ui/src/components/UserSimulation.jsx +327 -0
- package/management-ui/src/components/Welcome.jsx +434 -0
- package/management-ui/src/components/codegen/APIEndpointGenerator.jsx +637 -0
- package/management-ui/src/components/codegen/APIModuleSelector.jsx +227 -0
- package/management-ui/src/components/codegen/CodeGenerationWizard.jsx +247 -0
- package/management-ui/src/components/codegen/CodePreviewEditor.jsx +316 -0
- package/management-ui/src/components/codegen/DynamicModuleForm.jsx +271 -0
- package/management-ui/src/components/codegen/FormBuilder.jsx +737 -0
- package/management-ui/src/components/codegen/IntegrationGenerator.jsx +855 -0
- package/management-ui/src/components/codegen/ProjectScaffoldWizard.jsx +797 -0
- package/management-ui/src/components/codegen/SchemaBuilder.jsx +303 -0
- package/management-ui/src/components/codegen/TemplateSelector.jsx +586 -0
- package/management-ui/src/components/codegen/index.js +10 -0
- package/management-ui/src/components/connections/ConnectionConfigForm.jsx +362 -0
- package/management-ui/src/components/connections/ConnectionHealthMonitor.jsx +182 -0
- package/management-ui/src/components/connections/ConnectionTester.jsx +200 -0
- package/management-ui/src/components/connections/EntityRelationshipMapper.jsx +292 -0
- package/management-ui/src/components/connections/OAuthFlow.jsx +204 -0
- package/management-ui/src/components/connections/index.js +5 -0
- package/management-ui/src/components/index.js +21 -0
- package/management-ui/src/components/monitoring/APIGatewayMetrics.jsx +222 -0
- package/management-ui/src/components/monitoring/LambdaMetrics.jsx +169 -0
- package/management-ui/src/components/monitoring/MetricsChart.jsx +197 -0
- package/management-ui/src/components/monitoring/MonitoringDashboard.jsx +393 -0
- package/management-ui/src/components/monitoring/SQSMetrics.jsx +246 -0
- package/management-ui/src/components/monitoring/index.js +6 -0
- package/management-ui/src/components/monitoring/monitoring.css +218 -0
- package/management-ui/src/components/theme-provider.jsx +52 -0
- package/management-ui/src/components/theme-toggle.jsx +39 -0
- package/management-ui/src/components/ui/badge.tsx +36 -0
- package/management-ui/src/components/ui/button.test.jsx +56 -0
- package/management-ui/src/components/ui/button.tsx +57 -0
- package/management-ui/src/components/ui/card.tsx +76 -0
- package/management-ui/src/components/ui/dropdown-menu.tsx +199 -0
- package/management-ui/src/components/ui/select.tsx +157 -0
- package/management-ui/src/components/ui/skeleton.jsx +15 -0
- package/management-ui/src/hooks/useFrigg.jsx +601 -0
- package/management-ui/src/hooks/useSocket.jsx +58 -0
- package/management-ui/src/index.css +193 -0
- package/management-ui/src/lib/utils.ts +6 -0
- package/management-ui/src/main.jsx +10 -0
- package/management-ui/src/pages/CodeGeneration.jsx +14 -0
- package/management-ui/src/pages/Connections.jsx +252 -0
- package/management-ui/src/pages/ConnectionsEnhanced.jsx +633 -0
- package/management-ui/src/pages/Dashboard.jsx +311 -0
- package/management-ui/src/pages/Environment.jsx +314 -0
- package/management-ui/src/pages/IntegrationConfigure.jsx +669 -0
- package/management-ui/src/pages/IntegrationDiscovery.jsx +567 -0
- package/management-ui/src/pages/IntegrationTest.jsx +742 -0
- package/management-ui/src/pages/Integrations.jsx +253 -0
- package/management-ui/src/pages/Monitoring.jsx +17 -0
- package/management-ui/src/pages/Simulation.jsx +155 -0
- package/management-ui/src/pages/Users.jsx +492 -0
- package/management-ui/src/services/api.js +41 -0
- package/management-ui/src/services/apiModuleService.js +193 -0
- package/management-ui/src/services/websocket-handlers.js +120 -0
- package/management-ui/src/test/api/project.test.js +273 -0
- package/management-ui/src/test/components/Welcome.test.jsx +378 -0
- package/management-ui/src/test/mocks/server.js +178 -0
- package/management-ui/src/test/setup.js +61 -0
- package/management-ui/src/test/utils/test-utils.jsx +134 -0
- package/management-ui/src/utils/repository.js +98 -0
- package/management-ui/src/utils/repository.test.js +118 -0
- package/management-ui/src/workflows/phase2-integration-workflows.js +884 -0
- package/management-ui/tailwind.config.js +63 -0
- package/management-ui/tsconfig.json +37 -0
- package/management-ui/tsconfig.node.json +10 -0
- package/management-ui/vite.config.js +26 -0
- package/management-ui/vitest.config.js +38 -0
- package/package.json +16 -9
- package/infrastructure/app-handler-helpers.js +0 -57
- package/infrastructure/backend-utils.js +0 -90
- package/infrastructure/routers/auth.js +0 -26
- package/infrastructure/routers/integration-defined-routers.js +0 -37
- package/infrastructure/routers/middleware/loadUser.js +0 -15
- package/infrastructure/routers/middleware/requireLoggedInUser.js +0 -12
- package/infrastructure/routers/user.js +0 -41
- package/infrastructure/routers/websocket.js +0 -55
- package/infrastructure/workers/integration-defined-workers.js +0 -24
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
let EC2Client, DescribeVpcsCommand, DescribeSubnetsCommand, DescribeSecurityGroupsCommand, DescribeRouteTablesCommand, DescribeNatGatewaysCommand, DescribeAddressesCommand;
|
|
2
|
+
let KMSClient, ListKeysCommand, DescribeKeyCommand;
|
|
3
|
+
let STSClient, GetCallerIdentityCommand;
|
|
4
|
+
|
|
5
|
+
function loadEC2() {
|
|
6
|
+
if (!EC2Client) {
|
|
7
|
+
({ EC2Client, DescribeVpcsCommand, DescribeSubnetsCommand, DescribeSecurityGroupsCommand, DescribeRouteTablesCommand, DescribeNatGatewaysCommand, DescribeAddressesCommand } = require('@aws-sdk/client-ec2'));
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function loadKMS() {
|
|
12
|
+
if (!KMSClient) {
|
|
13
|
+
({ KMSClient, ListKeysCommand, DescribeKeyCommand } = require('@aws-sdk/client-kms'));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function loadSTS() {
|
|
18
|
+
if (!STSClient) {
|
|
19
|
+
({ STSClient, GetCallerIdentityCommand } = require('@aws-sdk/client-sts'));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* AWS Resource Discovery utilities for Frigg applications
|
|
25
|
+
* These functions use AWS credentials to discover default resources during build time
|
|
26
|
+
*/
|
|
27
|
+
class AWSDiscovery {
|
|
28
|
+
/**
|
|
29
|
+
* Creates an instance of AWSDiscovery
|
|
30
|
+
* @param {string} [region='us-east-1'] - AWS region to use for discovery
|
|
31
|
+
*/
|
|
32
|
+
constructor(region = 'us-east-1') {
|
|
33
|
+
this.region = region;
|
|
34
|
+
loadEC2();
|
|
35
|
+
loadKMS();
|
|
36
|
+
loadSTS();
|
|
37
|
+
this.ec2Client = new EC2Client({ region });
|
|
38
|
+
this.kmsClient = new KMSClient({ region });
|
|
39
|
+
this.stsClient = new STSClient({ region });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get AWS account ID
|
|
44
|
+
* @returns {Promise<string>} The AWS account ID
|
|
45
|
+
* @throws {Error} If unable to retrieve account ID
|
|
46
|
+
*/
|
|
47
|
+
async getAccountId() {
|
|
48
|
+
try {
|
|
49
|
+
const command = new GetCallerIdentityCommand({});
|
|
50
|
+
const response = await this.stsClient.send(command);
|
|
51
|
+
return response.Account;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('Error getting AWS account ID:', error.message);
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Find the default VPC for the account
|
|
60
|
+
* @returns {Promise<Object>} VPC object containing VpcId and other properties
|
|
61
|
+
* @throws {Error} If no VPC is found in the account
|
|
62
|
+
*/
|
|
63
|
+
async findDefaultVpc() {
|
|
64
|
+
try {
|
|
65
|
+
const command = new DescribeVpcsCommand({
|
|
66
|
+
Filters: [
|
|
67
|
+
{
|
|
68
|
+
Name: 'is-default',
|
|
69
|
+
Values: ['true']
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const response = await this.ec2Client.send(command);
|
|
75
|
+
|
|
76
|
+
if (response.Vpcs && response.Vpcs.length > 0) {
|
|
77
|
+
return response.Vpcs[0];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// If no default VPC, get the first available VPC
|
|
81
|
+
const allVpcsCommand = new DescribeVpcsCommand({});
|
|
82
|
+
const allVpcsResponse = await this.ec2Client.send(allVpcsCommand);
|
|
83
|
+
|
|
84
|
+
if (allVpcsResponse.Vpcs && allVpcsResponse.Vpcs.length > 0) {
|
|
85
|
+
console.log('No default VPC found, using first available VPC');
|
|
86
|
+
return allVpcsResponse.Vpcs[0];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
throw new Error('No VPC found in the account');
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error('Error finding default VPC:', error.message);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Find private subnets for the given VPC
|
|
98
|
+
* @param {string} vpcId - The VPC ID to search within
|
|
99
|
+
* @returns {Promise<Array>} Array of subnet objects (at least 2 for high availability)
|
|
100
|
+
* @throws {Error} If no subnets are found in the VPC
|
|
101
|
+
*/
|
|
102
|
+
async findPrivateSubnets(vpcId) {
|
|
103
|
+
try {
|
|
104
|
+
const command = new DescribeSubnetsCommand({
|
|
105
|
+
Filters: [
|
|
106
|
+
{
|
|
107
|
+
Name: 'vpc-id',
|
|
108
|
+
Values: [vpcId]
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const response = await this.ec2Client.send(command);
|
|
114
|
+
|
|
115
|
+
if (!response.Subnets || response.Subnets.length === 0) {
|
|
116
|
+
throw new Error(`No subnets found in VPC ${vpcId}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Prefer private subnets (no direct route to IGW)
|
|
120
|
+
const privateSubnets = [];
|
|
121
|
+
const publicSubnets = [];
|
|
122
|
+
|
|
123
|
+
for (const subnet of response.Subnets) {
|
|
124
|
+
// Check route tables to determine if subnet is private
|
|
125
|
+
const isPrivate = await this.isSubnetPrivate(subnet.SubnetId);
|
|
126
|
+
if (isPrivate) {
|
|
127
|
+
privateSubnets.push(subnet);
|
|
128
|
+
} else {
|
|
129
|
+
publicSubnets.push(subnet);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Return at least 2 subnets for high availability
|
|
134
|
+
const selectedSubnets = privateSubnets.length >= 2 ?
|
|
135
|
+
privateSubnets.slice(0, 2) :
|
|
136
|
+
response.Subnets.slice(0, 2);
|
|
137
|
+
|
|
138
|
+
return selectedSubnets;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error('Error finding private subnets:', error);
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Check if a subnet is private (no direct route to Internet Gateway)
|
|
147
|
+
* @param {string} subnetId - The subnet ID to check
|
|
148
|
+
* @returns {Promise<boolean>} True if subnet is private, false if public
|
|
149
|
+
*/
|
|
150
|
+
async isSubnetPrivate(subnetId) {
|
|
151
|
+
try {
|
|
152
|
+
// First, get the subnet details to find its VPC
|
|
153
|
+
const subnetCommand = new DescribeSubnetsCommand({
|
|
154
|
+
SubnetIds: [subnetId]
|
|
155
|
+
});
|
|
156
|
+
const subnetResponse = await this.ec2Client.send(subnetCommand);
|
|
157
|
+
|
|
158
|
+
if (!subnetResponse.Subnets || subnetResponse.Subnets.length === 0) {
|
|
159
|
+
throw new Error(`Subnet ${subnetId} not found`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const subnet = subnetResponse.Subnets[0];
|
|
163
|
+
const vpcId = subnet.VpcId;
|
|
164
|
+
|
|
165
|
+
// Get all route tables for this VPC
|
|
166
|
+
const routeTablesCommand = new DescribeRouteTablesCommand({
|
|
167
|
+
Filters: [
|
|
168
|
+
{
|
|
169
|
+
Name: 'vpc-id',
|
|
170
|
+
Values: [vpcId]
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const routeTablesResponse = await this.ec2Client.send(routeTablesCommand);
|
|
176
|
+
|
|
177
|
+
// Find the route table for this subnet
|
|
178
|
+
let routeTable = null;
|
|
179
|
+
|
|
180
|
+
// First check for explicit association
|
|
181
|
+
for (const rt of routeTablesResponse.RouteTables || []) {
|
|
182
|
+
for (const assoc of rt.Associations || []) {
|
|
183
|
+
if (assoc.SubnetId === subnetId) {
|
|
184
|
+
routeTable = rt;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (routeTable) break;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// If no explicit association, use the main route table
|
|
192
|
+
if (!routeTable) {
|
|
193
|
+
for (const rt of routeTablesResponse.RouteTables || []) {
|
|
194
|
+
for (const assoc of rt.Associations || []) {
|
|
195
|
+
if (assoc.Main === true) {
|
|
196
|
+
routeTable = rt;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (routeTable) break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (!routeTable) {
|
|
205
|
+
console.warn(`No route table found for subnet ${subnetId}`);
|
|
206
|
+
return true; // Default to private for safety
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Check if route table has a route to an Internet Gateway
|
|
210
|
+
for (const route of routeTable.Routes || []) {
|
|
211
|
+
if (route.GatewayId && route.GatewayId.startsWith('igw-')) {
|
|
212
|
+
return false; // It's a public subnet
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return true; // No IGW route found, it's private
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.warn(`Could not determine if subnet ${subnetId} is private:`, error);
|
|
219
|
+
return true; // Default to private for safety
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Find or create a default security group for Lambda functions
|
|
225
|
+
* @param {string} vpcId - The VPC ID to search within
|
|
226
|
+
* @returns {Promise<Object>} Security group object containing GroupId and other properties
|
|
227
|
+
* @throws {Error} If no security group is found for the VPC
|
|
228
|
+
*/
|
|
229
|
+
async findDefaultSecurityGroup(vpcId) {
|
|
230
|
+
try {
|
|
231
|
+
// First try to find existing Frigg security group
|
|
232
|
+
const friggSgCommand = new DescribeSecurityGroupsCommand({
|
|
233
|
+
Filters: [
|
|
234
|
+
{
|
|
235
|
+
Name: 'vpc-id',
|
|
236
|
+
Values: [vpcId]
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
Name: 'group-name',
|
|
240
|
+
Values: ['frigg-lambda-sg']
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const friggResponse = await this.ec2Client.send(friggSgCommand);
|
|
246
|
+
if (friggResponse.SecurityGroups && friggResponse.SecurityGroups.length > 0) {
|
|
247
|
+
return friggResponse.SecurityGroups[0];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Fall back to default security group
|
|
251
|
+
const defaultSgCommand = new DescribeSecurityGroupsCommand({
|
|
252
|
+
Filters: [
|
|
253
|
+
{
|
|
254
|
+
Name: 'vpc-id',
|
|
255
|
+
Values: [vpcId]
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
Name: 'group-name',
|
|
259
|
+
Values: ['default']
|
|
260
|
+
}
|
|
261
|
+
]
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const defaultResponse = await this.ec2Client.send(defaultSgCommand);
|
|
265
|
+
if (defaultResponse.SecurityGroups && defaultResponse.SecurityGroups.length > 0) {
|
|
266
|
+
return defaultResponse.SecurityGroups[0];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
throw new Error(`No security group found for VPC ${vpcId}`);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error('Error finding default security group:', error);
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Find public subnets for NAT Gateway placement
|
|
278
|
+
* @param {string} vpcId - The VPC ID to search within
|
|
279
|
+
* @returns {Promise<Object>} First public subnet object for NAT Gateway placement
|
|
280
|
+
* @throws {Error} If no public subnets are found in the VPC
|
|
281
|
+
*/
|
|
282
|
+
async findPublicSubnets(vpcId) {
|
|
283
|
+
try {
|
|
284
|
+
const command = new DescribeSubnetsCommand({
|
|
285
|
+
Filters: [
|
|
286
|
+
{
|
|
287
|
+
Name: 'vpc-id',
|
|
288
|
+
Values: [vpcId]
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const response = await this.ec2Client.send(command);
|
|
294
|
+
|
|
295
|
+
if (!response.Subnets || response.Subnets.length === 0) {
|
|
296
|
+
throw new Error(`No subnets found in VPC ${vpcId}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Find public subnets (have direct route to IGW)
|
|
300
|
+
const publicSubnets = [];
|
|
301
|
+
|
|
302
|
+
for (const subnet of response.Subnets) {
|
|
303
|
+
// Check route tables to determine if subnet is public
|
|
304
|
+
const isPrivate = await this.isSubnetPrivate(subnet.SubnetId);
|
|
305
|
+
if (!isPrivate) {
|
|
306
|
+
publicSubnets.push(subnet);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (publicSubnets.length === 0) {
|
|
311
|
+
throw new Error(`No public subnets found in VPC ${vpcId} for NAT Gateway placement`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Return first public subnet for NAT Gateway
|
|
315
|
+
return publicSubnets[0];
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error('Error finding public subnets:', error);
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Find private route table for VPC endpoints
|
|
324
|
+
* @param {string} vpcId - The VPC ID to search within
|
|
325
|
+
* @returns {Promise<Object>} Route table object containing RouteTableId and other properties
|
|
326
|
+
* @throws {Error} If no route tables are found for the VPC
|
|
327
|
+
*/
|
|
328
|
+
async findPrivateRouteTable(vpcId) {
|
|
329
|
+
try {
|
|
330
|
+
const command = new DescribeRouteTablesCommand({
|
|
331
|
+
Filters: [
|
|
332
|
+
{
|
|
333
|
+
Name: 'vpc-id',
|
|
334
|
+
Values: [vpcId]
|
|
335
|
+
}
|
|
336
|
+
]
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const response = await this.ec2Client.send(command);
|
|
340
|
+
|
|
341
|
+
if (!response.RouteTables || response.RouteTables.length === 0) {
|
|
342
|
+
throw new Error(`No route tables found for VPC ${vpcId}`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Find a route table that doesn't have direct IGW route (private)
|
|
346
|
+
for (const routeTable of response.RouteTables) {
|
|
347
|
+
let hasIgwRoute = false;
|
|
348
|
+
for (const route of routeTable.Routes || []) {
|
|
349
|
+
if (route.GatewayId && route.GatewayId.startsWith('igw-')) {
|
|
350
|
+
hasIgwRoute = true;
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (!hasIgwRoute) {
|
|
355
|
+
return routeTable;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// If no private route table found, return the first one
|
|
360
|
+
return response.RouteTables[0];
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.error('Error finding private route table:', error);
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Find existing NAT Gateways in the VPC
|
|
369
|
+
* @param {string} vpcId - The VPC ID to search within
|
|
370
|
+
* @returns {Promise<Object|null>} NAT Gateway object or null if none found
|
|
371
|
+
*/
|
|
372
|
+
async findExistingNatGateway(vpcId) {
|
|
373
|
+
try {
|
|
374
|
+
const command = new DescribeNatGatewaysCommand({
|
|
375
|
+
Filter: [
|
|
376
|
+
{
|
|
377
|
+
Name: 'vpc-id',
|
|
378
|
+
Values: [vpcId]
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
Name: 'state',
|
|
382
|
+
Values: ['available']
|
|
383
|
+
}
|
|
384
|
+
]
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
const response = await this.ec2Client.send(command);
|
|
388
|
+
|
|
389
|
+
if (response.NatGateways && response.NatGateways.length > 0) {
|
|
390
|
+
// Find a NAT Gateway tagged for Frigg first
|
|
391
|
+
const friggNatGateway = response.NatGateways.find(nat =>
|
|
392
|
+
nat.Tags && nat.Tags.some(tag =>
|
|
393
|
+
tag.Key === 'Name' && tag.Value.includes('frigg')
|
|
394
|
+
)
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
if (friggNatGateway) {
|
|
398
|
+
console.log(`Found existing Frigg NAT Gateway: ${friggNatGateway.NatGatewayId}`);
|
|
399
|
+
return friggNatGateway;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Return first available NAT Gateway if no Frigg-specific one found
|
|
403
|
+
console.log(`Found existing NAT Gateway: ${response.NatGateways[0].NatGatewayId}`);
|
|
404
|
+
return response.NatGateways[0];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return null;
|
|
408
|
+
} catch (error) {
|
|
409
|
+
console.warn('Error finding existing NAT Gateway:', error.message);
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Find available Elastic IPs
|
|
416
|
+
* @returns {Promise<Object|null>} Available EIP object or null if none found
|
|
417
|
+
*/
|
|
418
|
+
async findAvailableElasticIP() {
|
|
419
|
+
try {
|
|
420
|
+
const command = new DescribeAddressesCommand({});
|
|
421
|
+
const response = await this.ec2Client.send(command);
|
|
422
|
+
|
|
423
|
+
if (response.Addresses && response.Addresses.length > 0) {
|
|
424
|
+
// Find an unassociated EIP first
|
|
425
|
+
const availableEIP = response.Addresses.find(eip =>
|
|
426
|
+
!eip.AssociationId && !eip.InstanceId && !eip.NetworkInterfaceId
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
if (availableEIP) {
|
|
430
|
+
console.log(`Found available Elastic IP: ${availableEIP.AllocationId}`);
|
|
431
|
+
return availableEIP;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Check for EIPs tagged for Frigg
|
|
435
|
+
const friggEIP = response.Addresses.find(eip =>
|
|
436
|
+
eip.Tags && eip.Tags.some(tag =>
|
|
437
|
+
tag.Key === 'Name' && tag.Value.includes('frigg')
|
|
438
|
+
)
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
if (friggEIP) {
|
|
442
|
+
console.log(`Found Frigg-tagged Elastic IP: ${friggEIP.AllocationId}`);
|
|
443
|
+
return friggEIP;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return null;
|
|
448
|
+
} catch (error) {
|
|
449
|
+
console.warn('Error finding available Elastic IP:', error.message);
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Find the default KMS key for the account
|
|
456
|
+
* @returns {Promise<string>} KMS key ARN or wildcard pattern as fallback
|
|
457
|
+
*/
|
|
458
|
+
async findDefaultKmsKey() {
|
|
459
|
+
try {
|
|
460
|
+
// First try to find a key with alias/aws/lambda
|
|
461
|
+
const command = new ListKeysCommand({});
|
|
462
|
+
const response = await this.kmsClient.send(command);
|
|
463
|
+
|
|
464
|
+
if (!response.Keys || response.Keys.length === 0) {
|
|
465
|
+
// Return AWS managed key ARN pattern as fallback
|
|
466
|
+
const accountId = await this.getAccountId();
|
|
467
|
+
return `arn:aws:kms:${this.region}:${accountId}:key/*`;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Look for customer managed keys first
|
|
471
|
+
for (const key of response.Keys) {
|
|
472
|
+
try {
|
|
473
|
+
const describeCommand = new DescribeKeyCommand({ KeyId: key.KeyId });
|
|
474
|
+
const keyDetails = await this.kmsClient.send(describeCommand);
|
|
475
|
+
|
|
476
|
+
if (keyDetails.KeyMetadata &&
|
|
477
|
+
keyDetails.KeyMetadata.KeyManager === 'CUSTOMER' &&
|
|
478
|
+
keyDetails.KeyMetadata.KeyState === 'Enabled') {
|
|
479
|
+
return keyDetails.KeyMetadata.Arn;
|
|
480
|
+
}
|
|
481
|
+
} catch (error) {
|
|
482
|
+
// Continue to next key if we can't describe this one
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Fallback to wildcard pattern for AWS managed keys
|
|
488
|
+
const accountId = await this.getAccountId();
|
|
489
|
+
return `arn:aws:kms:${this.region}:${accountId}:key/*`;
|
|
490
|
+
} catch (error) {
|
|
491
|
+
console.error('Error finding default KMS key:', error);
|
|
492
|
+
// Return wildcard pattern as ultimate fallback
|
|
493
|
+
return '*';
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Discover all AWS resources needed for Frigg deployment
|
|
499
|
+
* @returns {Promise<Object>} Object containing discovered resource IDs:
|
|
500
|
+
* @returns {string} return.defaultVpcId - The default VPC ID
|
|
501
|
+
* @returns {string} return.defaultSecurityGroupId - The default security group ID
|
|
502
|
+
* @returns {string} return.privateSubnetId1 - First private subnet ID
|
|
503
|
+
* @returns {string} return.privateSubnetId2 - Second private subnet ID
|
|
504
|
+
* @returns {string} return.publicSubnetId - Public subnet ID for NAT Gateway
|
|
505
|
+
* @returns {string} return.privateRouteTableId - Private route table ID
|
|
506
|
+
* @returns {string} return.defaultKmsKeyId - Default KMS key ARN
|
|
507
|
+
* @throws {Error} If resource discovery fails
|
|
508
|
+
*/
|
|
509
|
+
async discoverResources() {
|
|
510
|
+
try {
|
|
511
|
+
console.log('Discovering AWS resources for Frigg deployment...');
|
|
512
|
+
|
|
513
|
+
const vpc = await this.findDefaultVpc();
|
|
514
|
+
console.log(`Found VPC: ${vpc.VpcId}`);
|
|
515
|
+
|
|
516
|
+
const privateSubnets = await this.findPrivateSubnets(vpc.VpcId);
|
|
517
|
+
console.log(`Found ${privateSubnets.length} private subnets: ${privateSubnets.map(s => s.SubnetId).join(', ')}`);
|
|
518
|
+
|
|
519
|
+
const publicSubnet = await this.findPublicSubnets(vpc.VpcId);
|
|
520
|
+
console.log(`Found public subnet for NAT Gateway: ${publicSubnet.SubnetId}`);
|
|
521
|
+
|
|
522
|
+
const securityGroup = await this.findDefaultSecurityGroup(vpc.VpcId);
|
|
523
|
+
console.log(`Found security group: ${securityGroup.GroupId}`);
|
|
524
|
+
|
|
525
|
+
const routeTable = await this.findPrivateRouteTable(vpc.VpcId);
|
|
526
|
+
console.log(`Found route table: ${routeTable.RouteTableId}`);
|
|
527
|
+
|
|
528
|
+
const kmsKeyArn = await this.findDefaultKmsKey();
|
|
529
|
+
console.log(`Found KMS key: ${kmsKeyArn}`);
|
|
530
|
+
|
|
531
|
+
// Try to find existing NAT Gateway
|
|
532
|
+
const existingNatGateway = await this.findExistingNatGateway(vpc.VpcId);
|
|
533
|
+
let natGatewayId = null;
|
|
534
|
+
let elasticIpAllocationId = null;
|
|
535
|
+
|
|
536
|
+
if (existingNatGateway) {
|
|
537
|
+
natGatewayId = existingNatGateway.NatGatewayId;
|
|
538
|
+
// Get the EIP allocation ID from the NAT Gateway
|
|
539
|
+
if (existingNatGateway.NatGatewayAddresses && existingNatGateway.NatGatewayAddresses.length > 0) {
|
|
540
|
+
elasticIpAllocationId = existingNatGateway.NatGatewayAddresses[0].AllocationId;
|
|
541
|
+
}
|
|
542
|
+
} else {
|
|
543
|
+
// If no NAT Gateway exists, check for available EIP
|
|
544
|
+
const availableEIP = await this.findAvailableElasticIP();
|
|
545
|
+
if (availableEIP) {
|
|
546
|
+
elasticIpAllocationId = availableEIP.AllocationId;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return {
|
|
551
|
+
defaultVpcId: vpc.VpcId,
|
|
552
|
+
defaultSecurityGroupId: securityGroup.GroupId,
|
|
553
|
+
privateSubnetId1: privateSubnets[0]?.SubnetId,
|
|
554
|
+
privateSubnetId2: privateSubnets[1]?.SubnetId || privateSubnets[0]?.SubnetId,
|
|
555
|
+
publicSubnetId: publicSubnet.SubnetId,
|
|
556
|
+
privateRouteTableId: routeTable.RouteTableId,
|
|
557
|
+
defaultKmsKeyId: kmsKeyArn,
|
|
558
|
+
existingNatGatewayId: natGatewayId,
|
|
559
|
+
existingElasticIpAllocationId: elasticIpAllocationId
|
|
560
|
+
};
|
|
561
|
+
} catch (error) {
|
|
562
|
+
console.error('Error discovering AWS resources:', error);
|
|
563
|
+
throw error;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
module.exports = { AWSDiscovery };
|