UNPKG

@mesh-tech/mesh-cli

Version:

CLI for Mesh platform development utilities

107 lines (106 loc) 4.32 kB
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; }