use crate::tui::{colors, white_confirmation};
use anyhow::{bail, Context};
use colorful::{Color, Colorful};
use simplelog::*;
use spinoff::{spinners, Spinner};
use std::io::{BufRead, BufReader};
use std::path::Path;
use std::process::Output;
use std::process::{Command as ShellCommand, Stdio};
use std::thread;
pub struct Docker {}
impl Docker {
pub fn info() -> Output {
ShellCommand::new("sh")
.arg("-c")
.arg("docker info")
.output()
.expect("failed to execute process")
}
pub fn installed_and_running() -> Result<(), anyhow::Error> {
info!("Checking requirements: [Docker]");
let output = Self::info();
let stdout = String::from_utf8(output.stdout).unwrap();
let stderr = String::from_utf8(output.stderr).unwrap();
if stdout.is_empty() && !stderr.is_empty() {
bail!("- Docker is not installed, please visit docker.com to install")
} else {
if !stdout.is_empty() && !stderr.is_empty() {
let connection_err = stderr.find("Cannot connect to the Docker daemon");
if connection_err.is_some() {
bail!("- Docker is not running, please start it and try again")
}
}
}
Ok(())
}
pub fn build(instance_name: String, verbose: bool) -> Result<(), anyhow::Error> {
let mut sp = if !verbose {
Some(Spinner::new(
spinners::Dots,
"Running Docker Build",
spinoff::Color::White,
))
} else {
None
};
let mut show_message = |message: &str, new_spinner: bool| {
if let Some(mut spinner) = sp.take() {
spinner.stop_with_message(&format!(
"{} {}",
"✓".color(colors::indicator_good()).bold(),
message.color(Color::White).bold()
));
if new_spinner {
sp = Some(Spinner::new(
spinners::Dots,
format!("Building container for {}", instance_name),
spinoff::Color::White,
));
}
} else {
white_confirmation(message);
}
};
let command = format!(
"cd {} && docker build . -t postgres-{}",
instance_name, instance_name
);
run_command(&command, verbose)?;
show_message(
&format!("Docker Build completed for {}", instance_name),
false,
);
Ok(())
}
pub fn docker_compose_up(verbose: bool) -> Result<(), anyhow::Error> {
let mut sp = if !verbose {
Some(Spinner::new(
spinners::Dots,
"Running Docker Compose Up",
spinoff::Color::White,
))
} else {
None
};
let mut show_message = |message: &str, new_spinner: bool| {
if let Some(mut spinner) = sp.take() {
spinner.stop_with_message(&format!(
"{} {}",
"✓".color(colors::indicator_good()).bold(),
message.color(Color::White).bold()
));
if new_spinner {
sp = Some(Spinner::new(
spinners::Dots,
"Running docker-compose up",
spinoff::Color::White,
));
}
} else {
white_confirmation(message);
}
};
let command = "docker-compose up -d --build";
run_command(command, verbose)?;
show_message("Docker Compose Up completed", false);
Ok(())
}
pub fn docker_compose_down(verbose: bool) -> Result<(), anyhow::Error> {
let path = Path::new("docker-compose.yml");
if !path.exists() {
if verbose {
println!(
"{} {}",
"✓".color(colors::indicator_good()).bold(),
"No docker-compose.yml found in the directory"
.color(Color::White)
.bold()
)
}
return Ok(());
}
let mut sp = Spinner::new(
spinners::Dots,
"Running Docker Compose Down",
spinoff::Color::White,
);
let command: String = String::from("docker-compose down");
let output = match ShellCommand::new("sh").arg("-c").arg(&command).output() {
Ok(output) => output,
Err(_) => {
sp.stop_with_message("- Tembo instances failed to stop & remove");
bail!("There was an issue stopping the instances")
}
};
sp.stop_with_message(&format!(
"{} {}",
"✓".color(colors::indicator_good()).bold(),
"Tembo instances stopped & removed"
.color(Color::White)
.bold()
));
let stderr = String::from_utf8(output.stderr).unwrap();
if !output.status.success() {
bail!("There was an issue stopping the instances: {}", stderr)
}
Ok(())
}
}
pub fn run_command(command: &str, verbose: bool) -> Result<(), anyhow::Error> {
let mut child = ShellCommand::new("sh")
.arg("-c")
.arg(command)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.with_context(|| format!("Failed to spawn command '{}'", command))?;
if verbose {
let stdout = BufReader::new(child.stdout.take().expect("Failed to open stdout"));
let stderr = BufReader::new(child.stderr.take().expect("Failed to open stderr"));
let stdout_handle = thread::spawn(move || {
for line in stdout.lines() {
match line {
Ok(line) => println!("{}", line),
Err(e) => eprintln!("Error reading line from stdout: {}", e),
}
}
});
let stderr_handle = thread::spawn(move || {
for line in stderr.lines() {
match line {
Ok(line) => eprintln!("{}", line),
Err(e) => eprintln!("Error reading line from stderr: {}", e),
}
}
});
stdout_handle.join().expect("Stdout thread panicked");
stderr_handle.join().expect("Stderr thread panicked");
}
let status = child.wait().expect("Failed to wait on child");
if !status.success() {
bail!("Command executed with failures");
}
Ok(())
}
#[cfg(test)]
mod tests {
#[test]
#[ignore] fn docker_installed_and_running_test() {
}
}