diff --git a/src/command_builder.rs b/src/command_builder.rs new file mode 100644 index 0000000..af36201 --- /dev/null +++ b/src/command_builder.rs @@ -0,0 +1,82 @@ +use anyhow::{anyhow, Context, Result}; +use std::{ + collections::VecDeque, + ffi::OsStr, + fmt::Display, + fs::{File, OpenOptions}, + path::PathBuf, + process::{Command, Stdio}, +}; + +use crate::debug_println; + +pub struct Args(Vec); + +impl From<&str> for Args { + fn from(value: &str) -> Self { + Self(Vec::from_iter(value.split_whitespace().map(String::from))) + } +} + +impl From<&String> for Args { + fn from(value: &String) -> Self { + Self(Vec::from_iter(value.split_whitespace().map(String::from))) + } +} + +#[derive(Default)] +pub struct CommandBuilder { + args: Vec, +} + +impl Display for CommandBuilder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.args.join(" ")) + } +} + +impl CommandBuilder { + pub fn new(args: &str) -> Self { + Self::default().args("docker") + } + + pub fn docker() -> Self { + Self::default().args("docker") + } + + pub fn docker_compose() -> Self { + Self::default().args("docker compose -f docker/local/docker-compose.yaml") + } + + pub fn args(mut self, args: T) -> Self + where + Args: From, + { + self.args.extend(Args::from(args).0); + self + } + + fn build(self) -> Result { + debug_println!("\nran {self}\n"); + + let (first, rest) = self.args.split_first().context("empty args")?; + let mut command = Command::new(first); + command.args(rest); + + Ok(command) + } + + pub fn exec_get_stdout(mut self) -> Result { + Ok(String::from_utf8(self.build()?.output()?.stdout)?) + } + + pub fn exec(mut self) -> Result<()> { + self.build()?.spawn()?.wait()?; + Ok(()) + } + + pub fn exec_redirect_stdout(mut self, stdio: Stdio) -> Result<()> { + self.build()?.stdout(stdio).spawn()?.wait()?; + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 140ab9b..ba75b2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,12 @@ #![allow(unused)] +use std::{ + fs::{File, OpenOptions}, + path::PathBuf, +}; + pub mod cli; +pub mod command_builder; pub mod scripts; // NOTE: stolen from https://docs.rs/debug_print/latest/debug_print/ @@ -8,3 +14,11 @@ pub mod scripts; macro_rules! debug_println { ($($arg:tt)*) => (if ::std::cfg!(debug_assertions) { ::std::println!($($arg)*); }) } + +fn safe_create_file(path: PathBuf) -> Result { + OpenOptions::new().write(true).create_new(true).open(path) +} + +fn create_file(path: PathBuf) -> Result { + OpenOptions::new().write(true).create(true).open(path) +} diff --git a/src/scripts/django.rs b/src/scripts/django.rs index 5cc01c8..38fe376 100644 --- a/src/scripts/django.rs +++ b/src/scripts/django.rs @@ -1,12 +1,13 @@ -use crate::scripts::{create_file, safe_create_file}; - -use super::DockerCommand; -use anyhow::{anyhow, Result}; use std::env; use std::fs::create_dir; use std::io::Write; use std::path::{Path, PathBuf}; +use anyhow::{anyhow, Result}; + +use crate::command_builder::CommandBuilder; +use crate::{create_file, safe_create_file}; + fn get_django_settings_module() -> Result { let dsm = env::var("DJANGO_SETTINGS_MODULE")?; println!("USING: {dsm}"); @@ -64,7 +65,7 @@ pub fn manage(rest: &[String]) -> Result<()> { let dsm = get_django_settings_module()?; let joined = rest.join(" "); let command = format!("exec appserver python manage.py {joined} --settings={dsm}"); - DockerCommand::docker_compose().args(&command).spawn_wait() + CommandBuilder::docker_compose().args(&command).exec() } // shortcuts diff --git a/src/scripts/docker.rs b/src/scripts/docker.rs index aa8eeb7..6ff49e8 100644 --- a/src/scripts/docker.rs +++ b/src/scripts/docker.rs @@ -1,14 +1,15 @@ -use super::DockerCommand; use anyhow::Result; +use crate::command_builder::CommandBuilder; + pub fn stop_all() -> Result<()> { - let running_containers = DockerCommand::docker().args("ps -q").stdout()?; + let running_containers = CommandBuilder::docker().args("ps -q").exec_get_stdout()?; if running_containers.is_empty() { return Ok(()); } - DockerCommand::docker() + CommandBuilder::docker() .args(&format!("stop {running_containers}")) - .spawn_wait() + .exec() } diff --git a/src/scripts/docker_compose.rs b/src/scripts/docker_compose.rs index 3acd017..9f7e581 100644 --- a/src/scripts/docker_compose.rs +++ b/src/scripts/docker_compose.rs @@ -1,13 +1,14 @@ -use super::DockerCommand; use anyhow::Result; +use crate::command_builder::CommandBuilder; + // simple commands pub fn build() -> Result<()> { - DockerCommand::docker_compose().args("build").spawn_wait() + CommandBuilder::docker_compose().args("build").exec() } pub fn down() -> Result<()> { - DockerCommand::docker_compose().args("down").spawn_wait() + CommandBuilder::docker_compose().args("down").exec() } /// Start containers via `docker compose start`. Optionally pass containers to be started. @@ -18,25 +19,25 @@ pub fn down() -> Result<()> { /// `docker compose --env-file ./.env -f docker/local/docker-compose.yaml up start` pub fn start(containers: Option<&str>) -> Result<()> { let args = format!("start {}", containers.unwrap_or("")); - DockerCommand::docker_compose().args("start").spawn_wait() + CommandBuilder::docker_compose().args("start").exec() } pub fn stop() -> Result<()> { - DockerCommand::docker_compose().args("stop").spawn_wait() + CommandBuilder::docker_compose().args("stop").exec() } pub fn up() -> Result<()> { - DockerCommand::docker_compose().args("up -d").spawn_wait() + CommandBuilder::docker_compose().args("up -d").exec() } // shortcuts pub fn rebuild() -> Result<()> { stop()?; build()?; - start(None) + up() } pub fn restart() -> Result<()> { stop()?; - start(None) + up() } diff --git a/src/scripts/mod.rs b/src/scripts/mod.rs index 151e9b9..d9e926b 100644 --- a/src/scripts/mod.rs +++ b/src/scripts/mod.rs @@ -2,63 +2,3 @@ pub mod django; pub mod docker; pub mod docker_compose; pub mod postgres; - -use std::{ - ffi::OsStr, - fs::{File, OpenOptions}, - path::PathBuf, - process::{Command, Stdio}, -}; - -use anyhow::{Context, Result}; - -struct DockerCommand { - command: Command, -} - -impl DockerCommand { - fn new(program: T) -> Self - where - T: AsRef, - { - DockerCommand { - command: Command::new(program), - } - } - - fn docker() -> Self { - Self::new("docker") - } - - fn docker_compose() -> Self { - Self::new("docker").args("compose -f docker/local/docker-compose.yaml") - } - - fn args(mut self, args: &str) -> Self { - self.command.args(args.split_whitespace()); - self - } - - fn stdout(mut self) -> Result { - Ok(String::from_utf8(self.command.output()?.stdout)?) - } - - fn spawn_wait(mut self) -> Result<()> { - self.command.spawn()?.wait()?; - Ok(()) - } - - fn write_stdout(mut self, stdio: Stdio) -> Result<()> { - self.command.stdout(stdio); - self.spawn_wait()?; - Ok(()) - } -} - -fn safe_create_file(path: PathBuf) -> Result { - OpenOptions::new().write(true).create_new(true).open(path) -} - -fn create_file(path: PathBuf) -> Result { - OpenOptions::new().write(true).create(true).open(path) -} diff --git a/src/scripts/postgres.rs b/src/scripts/postgres.rs index 36443b9..ea7784b 100644 --- a/src/scripts/postgres.rs +++ b/src/scripts/postgres.rs @@ -1,25 +1,31 @@ -use super::{docker_compose, DockerCommand}; - -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use std::{ fs::File, path::{Path, PathBuf}, process::Stdio, }; +use super::docker_compose; +use crate::command_builder::CommandBuilder; + fn get_containers() -> Result<[String; 2]> { // get db container // FIX: we assume we are running db in service named "postgresbd" - let db_container = DockerCommand::docker_compose() + let db_container = CommandBuilder::docker_compose() .args("ps -q postgresdb") - .stdout()? + .exec_get_stdout()? .trim() .to_string(); + let no_result = db_container.is_empty(); + if no_result { + return Err(anyhow!("no container")); + } + // get all containers and filter out db container - let app_containers = DockerCommand::docker_compose() + let app_containers = CommandBuilder::docker_compose() .args("ps -q") - .stdout()? + .exec_get_stdout()? .split_whitespace() .filter(|x| x != &db_container) .collect::>() @@ -46,7 +52,7 @@ pub fn import(file: &Path) -> Result<()> { format!("exec {db_container} pg_restore -U db --dbname=db /tmp/dbdump"), ]; for command in commands { - DockerCommand::docker().args(&command).spawn_wait()?; + CommandBuilder::docker().args(&command).exec()?; } println!("restarting containers"); @@ -65,9 +71,9 @@ pub fn dump(file: &PathBuf) -> Result<()> { let stdout = Stdio::from(file); let command = format!("exec {db_container} pg_dump -U db --format=c db"); - DockerCommand::docker() + CommandBuilder::docker() .args(&command) - .write_stdout(stdout)?; + .exec_redirect_stdout(stdout)?; Ok(()) }