ecs-pf
Version:
CLI for port-forwarding to RDS via AWS ECS
201 lines (200 loc) • 8.84 kB
JavaScript
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.");
}