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:
Matej Janezic 2023-06-02 17:18:25 +02:00
parent e3306c494d
commit 316b37cd05
Signed by: janezicmatej
GPG Key ID: 4298E230ED37B2C0
7 changed files with 132 additions and 87 deletions

82
src/command_builder.rs Normal file
View 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(())
}
}

View File

@ -1,6 +1,12 @@
#![allow(unused)] #![allow(unused)]
use std::{
fs::{File, OpenOptions},
path::PathBuf,
};
pub mod cli; pub mod cli;
pub mod command_builder;
pub mod scripts; pub mod scripts;
// NOTE: stolen from https://docs.rs/debug_print/latest/debug_print/ // NOTE: stolen from https://docs.rs/debug_print/latest/debug_print/
@ -8,3 +14,11 @@ pub mod scripts;
macro_rules! debug_println { macro_rules! debug_println {
($($arg:tt)*) => (if ::std::cfg!(debug_assertions) { ::std::println!($($arg)*); }) ($($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)
}

View File

@ -1,12 +1,13 @@
use crate::scripts::{create_file, safe_create_file};
use super::DockerCommand;
use anyhow::{anyhow, Result};
use std::env; use std::env;
use std::fs::create_dir; use std::fs::create_dir;
use std::io::Write; use std::io::Write;
use std::path::{Path, PathBuf}; 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> { fn get_django_settings_module() -> Result<String> {
let dsm = env::var("DJANGO_SETTINGS_MODULE")?; let dsm = env::var("DJANGO_SETTINGS_MODULE")?;
println!("USING: {dsm}"); println!("USING: {dsm}");
@ -64,7 +65,7 @@ pub fn manage(rest: &[String]) -> Result<()> {
let dsm = get_django_settings_module()?; let dsm = get_django_settings_module()?;
let joined = rest.join(" "); let joined = rest.join(" ");
let command = format!("exec appserver python manage.py {joined} --settings={dsm}"); 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 // shortcuts

View File

@ -1,14 +1,15 @@
use super::DockerCommand;
use anyhow::Result; use anyhow::Result;
use crate::command_builder::CommandBuilder;
pub fn stop_all() -> Result<()> { 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() { if running_containers.is_empty() {
return Ok(()); return Ok(());
} }
DockerCommand::docker() CommandBuilder::docker()
.args(&format!("stop {running_containers}")) .args(&format!("stop {running_containers}"))
.spawn_wait() .exec()
} }

View File

@ -1,13 +1,14 @@
use super::DockerCommand;
use anyhow::Result; use anyhow::Result;
use crate::command_builder::CommandBuilder;
// simple commands // simple commands
pub fn build() -> Result<()> { pub fn build() -> Result<()> {
DockerCommand::docker_compose().args("build").spawn_wait() CommandBuilder::docker_compose().args("build").exec()
} }
pub fn down() -> Result<()> { 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. /// 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` /// `docker compose --env-file ./.env -f docker/local/docker-compose.yaml up start`
pub fn start(containers: Option<&str>) -> Result<()> { pub fn start(containers: Option<&str>) -> Result<()> {
let args = format!("start {}", containers.unwrap_or("")); let args = format!("start {}", containers.unwrap_or(""));
DockerCommand::docker_compose().args("start").spawn_wait() CommandBuilder::docker_compose().args("start").exec()
} }
pub fn stop() -> Result<()> { pub fn stop() -> Result<()> {
DockerCommand::docker_compose().args("stop").spawn_wait() CommandBuilder::docker_compose().args("stop").exec()
} }
pub fn up() -> Result<()> { pub fn up() -> Result<()> {
DockerCommand::docker_compose().args("up -d").spawn_wait() CommandBuilder::docker_compose().args("up -d").exec()
} }
// shortcuts // shortcuts
pub fn rebuild() -> Result<()> { pub fn rebuild() -> Result<()> {
stop()?; stop()?;
build()?; build()?;
start(None) up()
} }
pub fn restart() -> Result<()> { pub fn restart() -> Result<()> {
stop()?; stop()?;
start(None) up()
} }

View File

@ -2,63 +2,3 @@ pub mod django;
pub mod docker; pub mod docker;
pub mod docker_compose; pub mod docker_compose;
pub mod postgres; 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)
}

View File

@ -1,25 +1,31 @@
use super::{docker_compose, DockerCommand}; use anyhow::{anyhow, Context, Result};
use anyhow::{Context, Result};
use std::{ use std::{
fs::File, fs::File,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::Stdio, process::Stdio,
}; };
use super::docker_compose;
use crate::command_builder::CommandBuilder;
fn get_containers() -> Result<[String; 2]> { fn get_containers() -> Result<[String; 2]> {
// get db container // get db container
// FIX: we assume we are running db in service named "postgresbd" // 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") .args("ps -q postgresdb")
.stdout()? .exec_get_stdout()?
.trim() .trim()
.to_string(); .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 // get all containers and filter out db container
let app_containers = DockerCommand::docker_compose() let app_containers = CommandBuilder::docker_compose()
.args("ps -q") .args("ps -q")
.stdout()? .exec_get_stdout()?
.split_whitespace() .split_whitespace()
.filter(|x| x != &db_container) .filter(|x| x != &db_container)
.collect::<Vec<&str>>() .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"), format!("exec {db_container} pg_restore -U db --dbname=db /tmp/dbdump"),
]; ];
for command in commands { for command in commands {
DockerCommand::docker().args(&command).spawn_wait()?; CommandBuilder::docker().args(&command).exec()?;
} }
println!("restarting containers"); println!("restarting containers");
@ -65,9 +71,9 @@ pub fn dump(file: &PathBuf) -> Result<()> {
let stdout = Stdio::from(file); let stdout = Stdio::from(file);
let command = format!("exec {db_container} pg_dump -U db --format=c db"); let command = format!("exec {db_container} pg_dump -U db --format=c db");
DockerCommand::docker() CommandBuilder::docker()
.args(&command) .args(&command)
.write_stdout(stdout)?; .exec_redirect_stdout(stdout)?;
Ok(()) Ok(())
} }