UNPKG

@contiamo/dev

Version:

Dev environment for contiamo

336 lines (301 loc) 9.93 kB
#!/usr/bin/env node const program = require("commander"); const inquirer = require("inquirer"); const path = require("path"); const fs = require("fs"); const os = require("os"); const { spawn, execSync } = require("child_process"); const slash = require("slash"); const YAML = require("yaml"); const Lokka = require("lokka").Lokka; const Transport = require("lokka-transport-http").Transport; /** * Execute a command with nice console output. * * @param {string} command to execute */ const exec = (command, opt) => new Promise((resolve) => { const [c, ...args] = command.split(" "); const task = spawn(c, args, { stdio: "inherit", ...opt }); task.on("close", resolve); }); const dockerComposeFile = slash( path.relative(process.cwd(), path.join(__dirname, "/docker-compose.yml")) ); const restoreScript = slash( path.relative(process.cwd(), path.join(__dirname, "/scripts/restore.sh")) ); const snapshotScript = slash( path.relative(process.cwd(), path.join(__dirname, "/scripts/snapshot.sh")) ); const dockerCompose = YAML.parse(fs.readFileSync(dockerComposeFile, "utf-8")); const services = Object.keys(dockerCompose.services); const contiamoServices = Object.entries(dockerCompose.services).reduce( (mem, [name, s]) => { if (!s.image.startsWith("$")) return mem; const detail = /\$\{(\w+):-eu.gcr.io\/dev-and-test-env\/([\w-]+)/.exec( s.image ); if (mem.map((m) => m.repo).includes(detail[2])) return mem; return [...mem, { repo: detail[2], envKey: detail[1], name }]; }, [] ); // Program const { version } = fs.readFileSync( path.join(__dirname, "./package.json"), "utf-8" ); const printStartMessage = () => { console.log(""); console.log("Tracing ui: http://localhost:16686/search"); console.log("Dev ui: http://localhost:9898/contiamo/profile"); console.log("SMTP ui: http://localhost:8025"); console.log("Spark ui: http://localhost:4040"); console.log("Email: lemon@example.com"); console.log("Password: localdev"); console.log("stunnel: ❌ not configured"); console.log("JMX: port 5001, username pantheon, password localdev"); }; program.version(version); program .command("clean") .description("Stop the environment and remove volumes") .action(() => exec(`docker-compose -f ${dockerComposeFile} down -v`)); program .command("docker-auth") .description("Setup docker authentication with gcloud") .action(() => { // Doesn't work with `exec` ¯\_(ツ)_/¯ console.log("Please execute the following commands:"); console.log("- gcloud auth login"); console.log("- gcloud auth configure-docker"); }); program .command("pull") .description("Pull the latest docker images") .action(() => { exec( `docker-compose -f ${dockerComposeFile} pull ${contiamoServices .map((i) => i.name) .join(" ")}` ); }); program .command("start") .description("Run the local dev environment on http://localhost:9898") .action(() => { exec( `docker-compose -f ${dockerComposeFile} up -d --force-recreate --remove-orphans` ).then(printStartMessage); }); program .command("create-snapshot [snapshotPath]") .description("Create Contiamo dev env snapshot") .action((snapshotPath = "localdev.snapshot") => { exec(`bash ${snapshotScript} ${snapshotPath}`) .then(() => { console.log(""); console.log(`${snapshotPath} was created 🥳`); }) .catch((e) => { console.error(e); }); }); program .command("restore-snapshot [snapshotPath]") .description("Restore Contiamo dev env snapshot") .action((snapshotPath = "localdev.snapshot") => { exec(`bash ${restoreScript} ${snapshotPath}`) .then(printStartMessage) .catch((e) => { console.error(e); }); }); program .command("logs [SERVICE...]") .option( "--tail [n]", "Number of lines to show from the end of the logs for each container." ) .option("-f, --follow", "Follow log output.") .description("Get logs of a service.") .action(async (_, { parent: { rawArgs }, tail, follow }) => { let args = []; if (typeof tail === "string") args.push(`--tail ${tail}`); if (follow) args.push(`-f`); const servicesToLog = rawArgs.filter((i) => services.includes(i)); if (servicesToLog.length === 0) { const { values } = await inquirer.prompt([ { type: "checkbox", name: "values", choices: contiamoServices.map((i) => ({ name: i.name, value: i.name, })), message: "Which services do you want to log?", }, ]); servicesToLog.push(...values); } args.push(...servicesToLog); exec(`docker-compose -f ${dockerComposeFile} logs ${args.join(" ")}`); }); program .command("restart [SERVICE...]") .description("Restart running containers.") .action(async (_, { parent: { rawArgs } }) => { let servicesToRestart = rawArgs.filter((i) => services.includes(i)); if (servicesToRestart.length === 0) { const { values } = await inquirer.prompt([ { type: "checkbox", name: "values", choices: services.map((i) => ({ name: i, value: i, })), message: "Which services do you want to restart?", }, ]); servicesToRestart.push(...values); } exec( `docker-compose -f ${dockerComposeFile} restart ${servicesToRestart.join( " " )}` ); }); program .command("stop") .description("Stop the running docker container") .action(() => { exec(`docker-compose -f ${dockerComposeFile} down`); }); program .command("repro") .description( "Compute the exact image digests that are used in the current setup" ) .action(() => { const configYaml = execSync( `docker-compose -f ${dockerComposeFile} config --resolve-image-digests` ); const config = YAML.parse(configYaml.toString()); const output = { version: config.version, services: Object.entries(config.services).reduce( (mem, [key, value]) => ({ ...mem, [key]: { image: value.image } }), {} ), }; console.log(YAML.stringify(output)); }); program .command("pr-preview [pullRequests...]") .description("Testing PR images (ex: `pr-preview hub:123 contiamo-ui:456`)") .action(async (pullRequests) => { const prPreviewableServices = Object.values(dockerCompose.services).reduce( (mem, s) => { if (!s.image.startsWith("$")) return mem; const detail = /\$\{(\w+):-eu.gcr.io\/dev-and-test-env\/([\w-]+)/.exec( s.image ); if (mem.map((m) => m.repo).includes(detail[2])) return mem; return [...mem, { repo: detail[2], envKey: detail[1] }]; }, [] ); if (pullRequests.length === 0) { const { repositories } = await inquirer.prompt([ { type: "checkbox", name: "repositories", choices: prPreviewableServices.map((i) => ({ name: i.repo, value: i.repo, })), message: "Please select a repository", }, ]); for (const repo of repositories) { // Retrieve the 10 last github PR let accessToken; const githubTokenPath = path.join(os.homedir(), ".restful-react"); if (fs.existsSync(githubTokenPath)) { accessToken = fs.readFileSync(githubTokenPath, "utf-8"); } else { const answers = await inquirer.prompt([ { type: "input", name: "githubToken", message: "Please provide a GitHub token with `repo` rules checked ( https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line )", }, ]); fs.writeFileSync(githubTokenPath, answers.githubToken); accessToken = answers.githubToken; } const client = new Lokka({ transport: new Transport("https://api.github.com/graphql", { headers: { "user-agent": "contiamo-dev", authorization: `bearer ${accessToken}`, }, }), }); const last10prReq = await client .query( `{ repository(name:"${ repo.startsWith("sync-") ? "sync" : repo }", owner:"contiamo") { pullRequests(last: 10){ nodes { number title } } } }` ) .then((res) => res.repository.pullRequests.nodes); const { pr } = await inquirer.prompt({ type: "list", name: "pr", message: `Please select the PR you want to pull for ${repo}`, choices: last10prReq.reverse().map((i) => ({ name: `#${i.number} - ${i.title}`, value: i.number, })), }); pullRequests.push(`${repo}:${pr}`); } } if (pullRequests.length === 0) { console.log("You need to select at least one pull-request"); return; } await Promise.all( pullRequests.map(async (pullRequest) => { const [repo, pr] = pullRequest.split(":"); console.log(`Pull image for ${repo} PR #${pr}`); await exec( `docker pull eu.gcr.io/dev-and-test-env/${repo}-preview:pr-${pr}` ); }) ); const env = process.env; pullRequests.forEach((pullRequest) => { const [repo, pr] = pullRequest.split(":"); env[ prPreviewableServices.find((i) => i.repo === repo).envKey ] = `eu.gcr.io/dev-and-test-env/${repo}-preview:pr-${pr}`; }); exec(`docker-compose -f ${dockerComposeFile} up -d --force-recreate`, { env, }); }); program.parse(process.argv);