@mesh-tech/mesh-cli
Version:
CLI for Mesh platform development utilities
107 lines (106 loc) • 4.32 kB
JavaScript
import { SecretsManagerClient, GetSecretValueCommand, } from "@aws-sdk/client-secrets-manager";
import { logInfo, logSuccess } from "./log.js";
export async function getDatabaseUrl(tenant, stage, options) {
const secretsManager = new SecretsManagerClient({});
const secretName = options?.app
? `mesh/${tenant}/${stage}/${options.app}/db-credentials`
: `mesh/${tenant}/${stage}/db-credentials`;
logInfo(`Looking up credentials at ${secretName}...`);
try {
const response = await secretsManager.send(new GetSecretValueCommand({ SecretId: secretName }));
if (response.SecretString) {
const secret = JSON.parse(response.SecretString);
if (secret.DATABASE_URL) {
const parsed = new URL(secret.DATABASE_URL);
logSuccess(`Got credentials for user: ${parsed.username}`);
return secret.DATABASE_URL;
}
if (secret.username && secret.password) {
logSuccess(`Got credentials for user: ${secret.username}`);
const database = secret.dbname ?? secret.database ?? "postgres";
return `postgresql://${secret.username}:${encodeURIComponent(secret.password)}@${secret.host ?? "localhost"}:${secret.port ?? 5432}/${database}`;
}
throw new Error(`Secret ${secretName} has unexpected format (needs DATABASE_URL or username/password)`);
}
}
catch (error) {
if (error.message?.includes("unexpected format")) {
throw error;
}
}
throw new Error(`Could not find credentials at ${secretName}`);
}
export async function getDbCredentials(tenant, stage, _platformEnv, rdsHost, rdsPort, options) {
const databaseUrl = await getDatabaseUrl(tenant, stage, options);
const parsed = new URL(databaseUrl);
return {
username: parsed.username,
password: decodeURIComponent(parsed.password),
host: rdsHost,
port: rdsPort,
database: parsed.pathname.slice(1),
};
}
export function rewriteDatabaseUrl(url, options) {
try {
const parsed = new URL(url);
if (options.endpoint) {
if (options.endpoint.includes(":")) {
const colonIndex = options.endpoint.lastIndexOf(":");
parsed.hostname = options.endpoint.slice(0, colonIndex);
parsed.port = options.endpoint.slice(colonIndex + 1);
}
else {
parsed.hostname = options.endpoint;
parsed.port = parsed.port || "5432";
}
}
if (options.sslMode) {
parsed.searchParams.set("sslmode", options.sslMode);
if (options.sslMode === "require" || options.sslMode === "no-verify") {
parsed.searchParams.set("sslaccept", "accept_invalid_certs");
}
}
return parsed.toString();
}
catch {
return url;
}
}
export function buildDatabaseUrl(creds, options) {
const host = options?.endpoint?.split(":")[0] ?? creds.host;
const port = options?.endpoint?.split(":")[1] ?? String(creds.port);
let url = `postgresql://${creds.username}:${encodeURIComponent(creds.password)}@${host}:${port}/${creds.database}`;
if (options?.sslMode) {
url += `?sslmode=${options.sslMode}`;
if (options.sslMode === "require" || options.sslMode === "no-verify") {
url += "&sslaccept=accept_invalid_certs";
}
}
return url;
}
export async function readSstOutputs() {
const fs = await import("fs");
if (!fs.existsSync(".sst/outputs.json")) {
return null;
}
try {
const content = fs.readFileSync(".sst/outputs.json", "utf-8");
const outputs = JSON.parse(content);
if (outputs.databaseUrl) {
logInfo("Found SST outputs with database config");
const secretArnMatch = outputs.databaseUrl.match(/secretArn=([^&]+)/);
const secretArn = secretArnMatch
? decodeURIComponent(secretArnMatch[1])
: undefined;
return {
databaseUrl: outputs.databaseUrl,
databaseName: outputs.databaseName,
secretArn,
};
}
}
catch {
}
return null;
}