UNPKG

ecs-pf

Version:

CLI for port-forwarding to RDS via AWS ECS

201 lines (200 loc) 8.84 kB
import { ECSClient } from "@aws-sdk/client-ecs"; import { RDSClient } from "@aws-sdk/client-rds"; import { parsePort, unwrapBrandedString } from "../types.js"; import { askRetry, displayFriendlyError, messages } from "../utils/index.js"; import { handleConnection } from "./connection/rds-connection.js"; import { selectECSTarget } from "./selection/ecs-selection.js"; import { selectLocalPort } from "./selection/port-selection.js"; import { determineRDSPort, selectRDSInstance, } from "./selection/rds-selection.js"; import { selectRegion } from "./selection/region-selection.js"; import { initializeSelectionState } from "./ui/selection-ui.js"; export async function connectToRDSWithSimpleUI(options = { dryRun: false }) { let retryCount = 0; const maxRetries = 3; while (retryCount <= maxRetries) { try { await connectToRDSWithSimpleUIInternal(options); return; } catch (error) { retryCount++; displayFriendlyError(error); if (retryCount <= maxRetries) { messages.warning(`Retry count: ${retryCount}/${maxRetries + 1}`); const shouldRetry = await askRetry(); if (!shouldRetry) { messages.info("Process interrupted"); return; } messages.info("Retrying...\n"); } else { messages.error("Maximum retry count reached. Terminating process."); messages.gray("If the problem persists, please check the above solutions."); throw error; } } } } async function connectToRDSWithSimpleUIInternal(options) { if (options.dryRun && hasAllRequiredParameters(options)) { await handleDirectDryRun(options); return; } if (options.dryRun) { messages.info("Starting AWS ECS RDS connection tool with Simple UI (DRY RUN)..."); } else { messages.info("Starting AWS ECS RDS connection tool with Simple UI..."); } const selections = initializeSelectionState({ region: options.region ? options.region : undefined, cluster: options.cluster ? options.cluster : undefined, task: options.task ? options.task : undefined, rds: options.rds ? options.rds : undefined, rdsPort: options.rdsPort ? String(options.rdsPort) : undefined, localPort: options.localPort ? String(options.localPort) : undefined, dryRun: options.dryRun, }); const selectedRegion = await selectRegion(options, selections); const displaySelections1 = { region: selections.region ? String(selections.region) : undefined, rds: selections.rds ? String(selections.rds) : undefined, rdsPort: selections.rdsPort ? String(selections.rdsPort) : undefined, ecsTarget: selections.ecsTarget, ecsCluster: selections.ecsCluster, localPort: selections.localPort ? String(selections.localPort) : undefined, }; messages.ui.displaySelectionState(displaySelections1); const ecsClient = new ECSClient({ region: selectedRegion }); const rdsClient = new RDSClient({ region: selectedRegion }); const selectedRDS = await selectRDSInstance(rdsClient, options, selections); const rdsPort = determineRDSPort(selectedRDS, { rdsPort: options.rdsPort ? String(options.rdsPort) : undefined, }, selections); const displaySelections2 = { region: selections.region ? String(selections.region) : undefined, rds: selections.rds ? String(selections.rds) : undefined, rdsPort: selections.rdsPort ? String(selections.rdsPort) : undefined, ecsTarget: selections.ecsTarget, ecsCluster: selections.ecsCluster, localPort: selections.localPort ? String(selections.localPort) : undefined, }; messages.ui.displaySelectionState(displaySelections2); const { selectedInference, selectedTask } = await selectECSTarget({ ecsClient, selectedRDS, options: { cluster: options.cluster, task: options.task, }, selections: { region: selectedRegion, ecsTarget: selections.ecsTarget, ecsCluster: selections.ecsCluster, localPort: selections.localPort ? String(selections.localPort) : undefined, rds: selections.rds ? String(selections.rds) : undefined, rdsPort: selections.rdsPort ? String(selections.rdsPort) : undefined, }, }); selections.ecsTarget = unwrapBrandedString(selectedTask); selections.ecsCluster = String(selectedInference.cluster.clusterName); const displaySelections3 = { region: selections.region ? String(selections.region) : undefined, rds: selections.rds ? String(selections.rds) : undefined, rdsPort: selections.rdsPort ? String(selections.rdsPort) : undefined, ecsTarget: selections.ecsTarget, ecsCluster: selections.ecsCluster, localPort: selections.localPort ? String(selections.localPort) : undefined, }; messages.ui.displaySelectionState(displaySelections3); await selectLocalPort({ localPort: options.localPort ? options.localPort : undefined, }, selections); const displaySelections4 = { region: selections.region ? String(selections.region) : undefined, rds: selections.rds ? String(selections.rds) : undefined, rdsPort: selections.rdsPort ? String(selections.rdsPort) : undefined, ecsTarget: selections.ecsTarget, ecsCluster: selections.ecsCluster, localPort: selections.localPort ? String(selections.localPort) : undefined, }; messages.ui.displaySelectionState(displaySelections4); const rdsPortResult = parsePort(rdsPort); if (!rdsPortResult.success) throw new Error(rdsPortResult.error); await handleConnection({ selections, selectedInference, selectedRDS, selectedTask, rdsPort: rdsPortResult.data, options, }); } function hasAllRequiredParameters(options) { return !!(options.region && options.cluster && options.task && options.rds && options.rdsPort && options.localPort); } async function handleDirectDryRun(options) { messages.info("Running dry run with provided parameters..."); const { generateConnectDryRun, displayDryRunResult } = await import("./dry-run.js"); const { parseRegionName, parseClusterName, parseTaskId, parsePort } = await import("../types.js"); const regionResult = parseRegionName(String(options.region)); if (!regionResult.success) throw new Error(regionResult.error); const clusterResult = parseClusterName(String(options.cluster)); if (!clusterResult.success) throw new Error(clusterResult.error); const taskStr = String(options.task); const [_, extractedTaskId] = taskStr.split("_"); const taskId = extractedTaskId ?? taskStr.replace(/^ecs:/, ""); const taskIdResult = parseTaskId(taskId); if (!taskIdResult.success) throw new Error(taskIdResult.error); const rdsPortResult = parsePort(options.rdsPort); if (!rdsPortResult.success) throw new Error(rdsPortResult.error); const localPortResult = parsePort(options.localPort); if (!localPortResult.success) throw new Error(localPortResult.error); const { parseDBInstanceIdentifier, parseDBEndpoint, parseDatabaseEngine } = await import("../types.js"); const rdsIdResult = parseDBInstanceIdentifier(String(options.rds)); if (!rdsIdResult.success) throw new Error(rdsIdResult.error); const endpointResult = parseDBEndpoint(`${String(options.rds)}.region.rds.amazonaws.com`); if (!endpointResult.success) throw new Error(endpointResult.error); const engineResult = parseDatabaseEngine("postgres"); if (!engineResult.success) throw new Error(engineResult.error); const mockRDSInstance = { dbInstanceIdentifier: rdsIdResult.data, endpoint: endpointResult.data, port: rdsPortResult.data, engine: engineResult.data, dbInstanceClass: "unknown", dbInstanceStatus: "available", allocatedStorage: 0, availabilityZone: "unknown", vpcSecurityGroups: [], dbSubnetGroup: undefined, createdTime: undefined, }; const dryRunResult = generateConnectDryRun({ region: regionResult.data, cluster: clusterResult.data, task: taskIdResult.data, rdsInstance: mockRDSInstance, rdsPort: rdsPortResult.data, localPort: localPortResult.data, }); displayDryRunResult(dryRunResult); messages.success("Dry run completed successfully."); }