UNPKG

@lbu/cli

Version:

CLI containing utilities and simple script runner

258 lines (223 loc) 7.08 kB
import { environment, exec, spawn } from "@lbu/stdlib"; const SUB_COMMANDS = ["up", "down", "clean", "reset"]; const containers = { "lbu-postgres-12": { createCommand: `docker create -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e PGDATA=/var/lib/postgresql/data/pgdata -v lbu-postgres-12:/var/lib/postgresql/data/pgdata -p 5432:5432 --name lbu-postgres-12 postgres:12`, }, "lbu-postgres-13": { createCommand: `docker create -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e PGDATA=/var/lib/postgresql/data/pgdata -v lbu-postgres-13:/var/lib/postgresql/data/pgdata -p 5432:5432 --name lbu-postgres-13 postgres:13`, }, "lbu-minio": { createCommand: `docker create -e MINIO_ACCESS_KEY=minio -e MINIO_SECRET_KEY=minio123 -v lbu-minio:/data -p 9000:9000 --name lbu-minio minio/minio server /data`, }, }; /** * @param {Logger} logger * @param {UtilCommand} command * @returns {Promise<{ exitCode?: number }>} */ export async function dockerCommand(logger, command) { const subCommand = command.arguments[0]; if (SUB_COMMANDS.indexOf(subCommand) === -1) { logger.info( `Unknown command: 'lbu docker ${ subCommand ?? "" }'. Please use one of ${SUB_COMMANDS.join(", ")}`, ); return { exitCode: 1 }; } if (!(await isDockerAvailable())) { logger.error( "Make sure to install Docker first. See https://docs.docker.com/install/", ); return { exitCode: 1 }; } const { knownContainers, exitCode, stdout, stderr, } = await getKnownContainers(); if (exitCode !== 0) { logger.error(`Could not list containers.`); logger.error({ exitCode, stderr, stdout }); } const enabledContainers = [ `lbu-postgres-${getPostgresVersion()}`, `lbu-minio`, ]; const disabledContainers = Object.keys(containers).filter( (it) => enabledContainers.indexOf(it) === -1, ); const containerInfo = { knownContainers, enabledContainers, disabledContainers, }; if (subCommand === "up") { return { exitCode: await startContainers(logger, containerInfo) }; } else if (subCommand === "down") { return { exitCode: await stopContainers(logger, containerInfo) }; } else if (subCommand === "clean") { return { exitCode: await cleanContainers(logger, containerInfo) }; } else if (subCommand === "reset") { return { exitCode: await resetDatabase(logger, containerInfo) }; } } /** * @param {Logger} logger * @param {{ enabledContainers: string[], disabledContainers: string[], knownContainers: * string[] }} containerInfo * @returns {Promise<number>} */ async function startContainers(logger, containerInfo) { const stopExitCode = await stopContainers(logger, { knownContainers: containerInfo.knownContainers, enabledContainers: containerInfo.disabledContainers, }); if (stopExitCode !== 0) { return stopExitCode; } for (const name of containerInfo.enabledContainers) { logger.info(`Creating ${name} container`); if (containerInfo.knownContainers.indexOf(name) === -1) { const { exitCode } = await exec(containers[name].createCommand); if (exitCode !== 0) { return exitCode; } } } logger.info(`Starting containers`); const { exitCode } = await spawn(`docker`, [ "start", ...containerInfo.enabledContainers, ]); if (exitCode !== 0) { return exitCode; } logger.info(`Waiting for Postgres ${getPostgresVersion()} to be ready...`); // Race for 30 seconds against the pg_isready command await Promise.race([ exec( `until docker exec lbu-postgres-${getPostgresVersion()} pg_isready ; do sleep 1 ; done`, { shell: true }, ), new Promise((resolve, reject) => { setTimeout(reject, 30000); }), ]); return exitCode; } /** * Stops 'available' containers. * By using the `knownContainers` as a check, we don't error when a container is not yet * created. * * @param {Logger} logger * @param {{ enabledContainers: string[], disabledContainers: string[], knownContainers: * string[] }} containerInfo * @returns {Promise<number>} */ async function stopContainers(logger, containerInfo) { const containers = containerInfo.enabledContainers.filter( (it) => containerInfo.knownContainers.indexOf(it) !== -1, ); if (containers.length === 0) { return 0; } logger.info(`Stopping containers`); const { exitCode } = await spawn(`docker`, ["stop", ...containers]); return exitCode; } /** * Cleanup 'available' known containers. * By using the `knownContainers` as a check, we don't error when a container is not yet * created. * * @param {Logger} logger * @param {{ enabledContainers: string[], disabledContainers: string[], knownContainers: * string[] }} containerInfo * @returns {Promise<number>} */ async function cleanContainers(logger, containerInfo) { const stopExit = await stopContainers(logger, containerInfo); if (stopExit !== 0) { return stopExit; } logger.info(`Removing containers`); const containersAndVolumes = containerInfo.enabledContainers.filter( (it) => containerInfo.knownContainers.indexOf(it) !== -1, ); const { exitCode: rmExit } = await spawn(`docker`, [ "rm", ...containersAndVolumes, ]); if (rmExit !== 0) { return rmExit; } logger.info(`Removing volumes`); const { exitCode: volumeRmExit } = await spawn(`docker`, [ "volume", "rm", ...containersAndVolumes, ]); return volumeRmExit; } /** * @param {Logger} logger * @param {{ enabledContainers: string[], disabledContainers: string[], knownContainers: * string[] }} containerInfo * @returns {Promise<number>} */ async function resetDatabase(logger, containerInfo) { const startExitCode = await startContainers(logger, containerInfo); if (startExitCode !== 0) { return startExitCode; } const name = environment.APP_NAME; logger.info(`Resetting ${name} database`); const { exitCode: postgresExit } = await spawn(`sh`, [ "-c", `echo 'DROP DATABASE IF EXISTS ${name}; CREATE DATABASE ${name}' | docker exec -i lbu-postgres-${getPostgresVersion()} psql --user postgres`, ]); if (postgresExit !== 0) { return postgresExit; } return 0; } /** * Brute force check if docker is available */ async function isDockerAvailable() { try { await exec("docker -v"); return true; } catch { return false; } } /** * Get list of available containers * * @returns {Promise<{ exitCode: number, knownContainers: string[], stdout?: string, * stderr?: string }>} */ async function getKnownContainers() { const { exitCode, stdout, stderr } = await exec( "docker container ls -a --format '{{.Names}}'", ); if (exitCode !== 0) { return { exitCode, knownContainers: [], stdout, stderr }; } const knownContainers = stdout .split("\n") .map((it) => it.trim()) .filter((it) => it.length > 0); return { exitCode, knownContainers, }; } function getPostgresVersion() { return Number(environment.POSTGRES_VERSION ?? "12"); }