Skip to content

Instantly share code, notes, and snippets.

@andreypopp
Created May 13, 2021 10:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andreypopp/f704a5daacfd01604e7e95452e36c79d to your computer and use it in GitHub Desktop.
Save andreypopp/f704a5daacfd01604e7e95452e36c79d to your computer and use it in GitHub Desktop.
use colored::*;
use exec;
use std::env;
use std::fs;
use std::io::{BufRead, BufReader};
use std::path;
use std::process;
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use std::thread;
use std::vec::Vec;
struct Workspace {
name: String,
path: path::PathBuf,
cwd: path::PathBuf,
}
fn current_workspace() -> Workspace {
let mut vars = env::vars();
let path = vars.find_map(
|(name, value)| {
if name == "w__root" {
Some(value)
} else {
None
}
},
);
let path = match path {
None => {
log_error("$w__root is not set");
process::exit(1)
}
Some(path) => path::PathBuf::from(path),
};
let cwd = fs::canonicalize(env::current_dir().unwrap()).unwrap();
let name = cwd.file_name().unwrap().to_owned().into_string().unwrap();
Workspace { name, path, cwd }
}
impl Workspace {
fn build(&self) {
let mut cmd = Command::new("podman");
cmd.arg("build")
.arg(self.path.clone())
.arg("-t")
.arg(self.name.clone());
run_progress(
&mut cmd,
|line| println!("{}", line.white()),
|line| println!("{}", line.red()),
);
}
fn up(&self) {
let mut volume = String::new();
volume.push_str(self.path.to_str().unwrap());
volume.push_str(":");
volume.push_str(self.path.to_str().unwrap());
let mut cmd = Command::new("podman");
cmd.arg("run")
.arg("--detach")
.arg("--replace")
.arg("--name")
.arg(self.name.clone())
.arg("--volume")
.arg(volume)
.arg(self.name.clone())
.arg("sleep")
.arg("infinity");
run(&mut cmd);
}
fn exec(&self, args: &[String]) -> ! {
let mut cmd = exec::Command::new("podman");
cmd.arg("exec")
.arg("-w")
.arg(self.cwd.as_path())
.arg("-i")
.arg(self.name.clone());
cmd.args(args);
let _ = cmd.exec();
process::exit(1)
}
}
fn run_progress<OnStdout, OnStderr>(cmd: &mut Command, on_stdout: OnStdout, on_stderr: OnStderr)
where
OnStdout: Fn(String) + Send + 'static,
OnStderr: Fn(String) + Send + 'static,
{
cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
let mut c = cmd.spawn().expect("failed to start command");
let out_reader = read_lines(c.stdout.take().unwrap(), on_stdout);
let err_reader = read_lines(c.stderr.take().unwrap(), on_stderr);
out_reader.join().expect("failed to read stdout");
err_reader.join().expect("failed to read stderr");
let exit_status = c.wait().expect("failed to execute command");
if !exit_status.success() {
log_error("command failed:");
process::exit(1)
}
}
/**
* Run command till completion, print stdout/stderr on log_error.
*/
fn run(cmd: &mut Command) {
cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
let mut c = cmd.spawn().expect("failed to start command");
// Keep vector with collected stdout/stderr within a mutex and then refcounted.
let out = Arc::new(Mutex::new(Vec::new()));
// Start reading stdout in a separate thread.
let out_reader = {
let out = out.clone();
read_lines(
c.stdout.take().expect("unable to get process stdout"),
move |line| {
out.lock().unwrap().push(line);
},
)
};
// Start reading stderr in a separate thread.
let err_reader = {
let out = out.clone();
read_lines(c.stderr.take().unwrap(), move |line| {
out.lock().unwrap().push(line);
})
};
// Wait for both readers to finish.
out_reader.join().expect("failed to read stdout");
err_reader.join().expect("failed to read stderr");
let exit_status = c.wait().expect("failed to execute command");
if !exit_status.success() {
log_error("command failed:");
out.lock().unwrap().iter().for_each(|line| {
println!("{}", line.red());
});
process::exit(1)
}
}
fn read_lines<R, F>(oc: R, mut on_line: F) -> thread::JoinHandle<()>
where
R: std::io::Read + Send + 'static,
F: FnMut(String) + Send + 'static,
{
let out_reader = BufReader::new(oc);
return thread::spawn(move || {
out_reader.lines().for_each(|line| {
let line = line.unwrap();
on_line(line);
})
});
}
fn log_info(msg: &str) {
println!("{} {}", " [INFO]".cyan().bold(), msg.cyan().bold());
}
fn log_error(msg: &str) {
println!("{} {}", "[ERROR]".red().bold(), msg.red().bold());
}
fn main() {
let ws = current_workspace();
let args = env::args();
let args: Vec<_> = args.collect();
match args[1].as_str() {
"build" => {
if !ws.path.join("Dockerfile").exists() && !ws.path.join("Containerfile").exists() {
log_error("no Dockefile/Containerfile found at the project root");
process::exit(1);
}
log_info("building...");
ws.build()
}
"exec" => {
ws.exec(&args[2..]);
}
"up" => {
log_info("staring development environment...");
ws.up();
}
_ => {
ws.exec(&args[1..]);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment