@contiamo/dev
Version:
Dev environment for contiamo
336 lines (301 loc) • 9.93 kB
JavaScript
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);