UNPKG

@catladder/cli

Version:

Panter cli tool for cloud CI/CD and DevOps

192 lines (176 loc) • 6.11 kB
import type { ComponentContext } from "@catladder/pipeline"; import { createKubernetesCloudsqlBaseValues, getDeployConfigType, isOfDeployType, } from "@catladder/pipeline"; import clipboard from "clipboardy"; import { defineCommand } from "../../core/defineCommand"; import { getEnvVarsResolved, getPipelineContextByChoice, parseChoice, } from "../../config/getProjectConfig"; import { ERROR_NOT_INSTALLED, startCloudSqlProxyInCurrentShell, } from "../../gcloud/cloudSql/startProxy"; import { logError, logLines, logWarning } from "../../utils/log"; import type { IO } from "../../core/types"; import { envAndComponents } from "../../apps/cli/commands/project/utils/autocompletions"; import { hasCloudSql } from "../availability"; type ProxyInfo = { instanceName: string; DB_NAME: string; DB_PASSWORD: string; DB_USER: string; }; const getProxyInfoForKubernetes = async ( io: IO, context: ComponentContext, ): Promise<ProxyInfo> => { if (!isOfDeployType(context.deploy?.config, "kubernetes")) { throw new Error("unsupported"); } const envVars = await getEnvVarsResolved(io, context.env, context.name); const cloudSqlValues = createKubernetesCloudsqlBaseValues(context); const DB_PASSWORD = ( envVars?.DB_PASSWORD || envVars?.POSTGRESQL_PASSWORD )?.toString(); const DB_NAME = cloudSqlValues.cloudsql.fullDbName.toString(); const instanceName = cloudSqlValues.cloudsql.instanceConnectionName; return { instanceName, DB_PASSWORD, DB_NAME, DB_USER: "postgres" }; }; const getProxyInfoForCloudRun = async ( io: IO, context: ComponentContext, ): Promise<ProxyInfo> => { if ( !isOfDeployType(context.deploy?.config, "google-cloudrun") || !context.deploy?.config.cloudSql ) { const deployType = getDeployConfigType(context.deploy?.config); const errorMessage = deployType === "google-cloudrun" ? `DeployConfig is missing the cloudSql property` : `Unsupported DeployConfig type: ${deployType}`; logError(io, errorMessage); throw new Error(errorMessage); } const envVars = await getEnvVarsResolved(io, context.env, context.name); return { instanceName: context.deploy?.config.cloudSql.instanceConnectionName, DB_PASSWORD: envVars?.DB_PASSWORD?.toString(), DB_NAME: context.environment.envVars.DB_NAME.toString(), DB_USER: envVars?.DB_USER?.toString(), }; }; const getProxyInfo = async (io: IO, context: ComponentContext) => { if (isOfDeployType(context.deploy?.config, "kubernetes")) { return getProxyInfoForKubernetes(io, context); } else if (isOfDeployType(context.deploy?.config, "google-cloudrun")) { return getProxyInfoForCloudRun(io, context); } throw new Error(`unsupported environment: ${context.deploy?.config?.type}`); }; const getDbUrl = ( { DB_PASSWORD, DB_NAME, DB_USER }: ProxyInfo, localPort: string, additional: string = "", ) => `DATABASE_URL="postgresql://${DB_USER}:${DB_PASSWORD}@localhost:${localPort}/${DB_NAME}${additional}"`; const getJdbcUrl = ( { DB_PASSWORD, DB_NAME, DB_USER }: ProxyInfo, localPort: string, ) => `DATABASE_JDBC_URL="jdbc:postgresql://localhost:${localPort}/${DB_NAME}?schema=public&user=${DB_USER}&password=${DB_PASSWORD}"`; export const commandCloudSqlProxy = defineCommand({ name: "project cloudsql proxy", description: "proxy to cloud sql db", group: "project", isAvailable: hasCloudSql(), inputs: { envComponent: { type: "string", message: "environment:component", positional: true, choices: async () => envAndComponents(), }, localPort: { type: "number", message: "Local port: ", default: 54320, }, }, execute: async (ctx) => { const envComponent = await ctx.get("envComponent"); const { env, componentName } = parseChoice(envComponent); if (!componentName) { logWarning(ctx, "need componentName"); return; } const context = await getPipelineContextByChoice(env, componentName); if (env === "review") { logWarning( ctx, "connection string does not include mr information on review environments", ); } const localPort = await ctx.get("localPort"); const proxyInfo = await getProxyInfo(ctx, context); const psqlEnvVars = { PGPASSWORD: proxyInfo.DB_PASSWORD, PGUSER: proxyInfo.DB_USER, PGDATABASE: proxyInfo.DB_NAME, PGHOST: "localhost", PGPORT: localPort, }; const lipbqEnvVarDeclarations = Object.entries(psqlEnvVars).map( ([key, value]) => `${key}="${value}"`, ); const dbUrl = getDbUrl(proxyInfo, String(localPort)); const jdbcUrl = getJdbcUrl(proxyInfo, String(localPort)); clipboard.writeSync( [ "", "## CloudSQL proxy connection .env variables", dbUrl, jdbcUrl, "## DATABASE_URL with schema=public parameter (e. g. Prisma requires this):", `# ${getDbUrl(proxyInfo, String(localPort), `?schema=public`)}`, "## Env variables for libpq (e.g. psql client https://www.postgresql.org/docs/current/libpq-envars.html#LIBPQ-ENVARS)", ...lipbqEnvVarDeclarations, "", ].join("\n"), ); logLines( ctx, null, "Connection strings env variables DATABASE_URL and those for the 'psql' client (copied to your clipboard for .env file):", dbUrl, jdbcUrl, ...lipbqEnvVarDeclarations, null, "DATABASE_URL with schema=public parameter (e. g. Prisma requires this):", getDbUrl(proxyInfo, String(localPort), `?schema=public`), null, ); try { await startCloudSqlProxyInCurrentShell({ instanceName: proxyInfo.instanceName, localPort, }); } catch (error) { if (!(error instanceof Error)) throw error; if (error.message !== ERROR_NOT_INSTALLED) throw error; logError( ctx, error.message, null, "Please install the Cloud SQL Auth Proxy from:", `https://cloud.google.com/sql/docs/postgres/connect-auth-proxy#install`, null, ); } }, });