feat: custom command builder
Implemented custom command builder with nicer api suited for my needs. Also returned error if there are no containers in `scripts::postgres::get_containers`.
This commit is contained in:
		
							
								
								
									
										82
									
								
								src/command_builder.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/command_builder.rs
									
									
									
									
									
										Normal file
									
								
							@@ -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<String>);
 | 
			
		||||
 | 
			
		||||
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<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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<T>(mut self, args: T) -> Self
 | 
			
		||||
    where
 | 
			
		||||
        Args: From<T>,
 | 
			
		||||
    {
 | 
			
		||||
        self.args.extend(Args::from(args).0);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn build(self) -> Result<Command> {
 | 
			
		||||
        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<String> {
 | 
			
		||||
        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(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								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<File, std::io::Error> {
 | 
			
		||||
    OpenOptions::new().write(true).create_new(true).open(path)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn create_file(path: PathBuf) -> Result<File, std::io::Error> {
 | 
			
		||||
    OpenOptions::new().write(true).create(true).open(path)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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<String> {
 | 
			
		||||
    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
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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<T>(program: T) -> Self
 | 
			
		||||
    where
 | 
			
		||||
        T: AsRef<OsStr>,
 | 
			
		||||
    {
 | 
			
		||||
        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<String> {
 | 
			
		||||
        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<File, std::io::Error> {
 | 
			
		||||
    OpenOptions::new().write(true).create_new(true).open(path)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn create_file(path: PathBuf) -> Result<File, std::io::Error> {
 | 
			
		||||
    OpenOptions::new().write(true).create(true).open(path)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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::<Vec<&str>>()
 | 
			
		||||
@@ -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(())
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user