feat: add verbose and dry run
This commit is contained in:
10
README.md
10
README.md
@@ -10,10 +10,14 @@ You will need rust installed. Clone repo and run:
|
||||
cargo install --path .
|
||||
```
|
||||
|
||||
To print the underlying docker commands as they run, build with debug
|
||||
assertions:
|
||||
## seeing what it runs
|
||||
|
||||
`-v` prints each docker command as it runs, and `--dry-run` prints the ones it
|
||||
would run without running them. Both work before or after the subcommand.
|
||||
|
||||
```bash
|
||||
cargo install --path . --debug
|
||||
ahab -v django test
|
||||
ahab --dry-run postgres import ./dump
|
||||
```
|
||||
|
||||
## shell completion
|
||||
|
||||
@@ -8,8 +8,15 @@ use clap_complete::Shell;
|
||||
pub struct Ahab {
|
||||
#[command(subcommand)]
|
||||
pub command: Commands,
|
||||
}
|
||||
|
||||
/// Print each docker command as it runs
|
||||
#[arg(short, long, global = true)]
|
||||
pub verbose: bool,
|
||||
|
||||
/// Print the docker commands that would run, without running them
|
||||
#[arg(long, global = true)]
|
||||
pub dry_run: bool,
|
||||
}
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum Commands {
|
||||
/// Django related subcommands
|
||||
|
||||
@@ -2,10 +2,9 @@ use anyhow::{Context, Result, bail};
|
||||
use std::{
|
||||
fmt::Display,
|
||||
process::{Child, Command, ExitStatus, Stdio},
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
use crate::debug_eprintln;
|
||||
|
||||
pub struct Args(Vec<String>);
|
||||
|
||||
impl From<&str> for Args {
|
||||
@@ -54,8 +53,9 @@ impl CommandBuilder {
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<Command> {
|
||||
debug_eprintln!("running `{self}`");
|
||||
|
||||
if options().verbose {
|
||||
eprintln!("running `{self}`");
|
||||
}
|
||||
let (first, rest) = self.args.split_first().context("empty args")?;
|
||||
let mut command = Command::new(first);
|
||||
command.args(rest);
|
||||
@@ -72,6 +72,11 @@ impl CommandBuilder {
|
||||
}
|
||||
|
||||
pub fn exec(self) -> Result<()> {
|
||||
if options().dry_run {
|
||||
eprintln!("would run `{self}`");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let shown = self.to_string();
|
||||
let status = self.build()?.spawn()?.wait()?;
|
||||
|
||||
@@ -83,6 +88,11 @@ impl CommandBuilder {
|
||||
}
|
||||
|
||||
pub fn exec_redirect_stdout(self, stdio: Stdio) -> Result<()> {
|
||||
if options().dry_run {
|
||||
eprintln!("would run `{self}`");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let shown = self.to_string();
|
||||
let status = self.build()?.stdout(stdio).spawn()?.wait()?;
|
||||
|
||||
@@ -100,3 +110,23 @@ fn check(command: &str, status: ExitStatus) -> Result<()> {
|
||||
None => bail!("`{command}` was killed by a signal"),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub struct Options {
|
||||
pub verbose: bool,
|
||||
pub dry_run: bool,
|
||||
}
|
||||
|
||||
static OPTIONS: OnceLock<Options> = OnceLock::new();
|
||||
|
||||
pub fn set_options(options: Options) {
|
||||
let _ = OPTIONS.set(options);
|
||||
}
|
||||
|
||||
fn options() -> Options {
|
||||
OPTIONS.get().copied().unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn is_dry_run() -> bool {
|
||||
options().dry_run
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ahab::{cli, scripts};
|
||||
use ahab::{cli, command_builder, scripts};
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
@@ -6,6 +6,11 @@ use clap::Parser;
|
||||
fn main() -> Result<()> {
|
||||
let args = cli::Ahab::parse();
|
||||
|
||||
command_builder::set_options(command_builder::Options {
|
||||
verbose: args.verbose,
|
||||
dry_run: args.dry_run,
|
||||
});
|
||||
|
||||
match args.command {
|
||||
cli::Commands::Django { command } => match command {
|
||||
cli::Django::Bash => scripts::django::bash(),
|
||||
|
||||
@@ -10,7 +10,8 @@ use std::{
|
||||
|
||||
use super::docker_compose;
|
||||
use crate::cli::Format;
|
||||
use crate::{command_builder::CommandBuilder, compose::Compose, debug_eprintln};
|
||||
use crate::command_builder;
|
||||
use crate::{command_builder::CommandBuilder, compose::Compose};
|
||||
|
||||
const CUSTOM_MAGIC: &[u8] = b"PGDMP";
|
||||
const TAR_MAGIC: &[u8] = b"toc.dat";
|
||||
@@ -193,7 +194,10 @@ fn piped(db: &Database, script: &str, input: &Path) -> Result<std::process::Comm
|
||||
}
|
||||
|
||||
fn wait_until_ready(db: &Database) -> Result<()> {
|
||||
debug_eprintln!("waiting until pg_isready");
|
||||
if command_builder::is_dry_run() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let deadline = Instant::now() + READY_TIMEOUT;
|
||||
|
||||
loop {
|
||||
@@ -319,6 +323,9 @@ pub fn import(file: &Path) -> Result<()> {
|
||||
}
|
||||
|
||||
wait_until_ready(&db)?;
|
||||
if command_builder::is_dry_run() {
|
||||
eprintln!("would restore with {tool}");
|
||||
} else {
|
||||
match (kind, restore.as_deref()) {
|
||||
// psql's output is read rather than streamed here, to keep the expected
|
||||
// role errors out of the way
|
||||
@@ -341,6 +348,7 @@ pub fn import(file: &Path) -> Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(dump, Dump::Directory) {
|
||||
let _ = in_container(&db).args("rm -rf").arg(&remote).exec();
|
||||
|
||||
Reference in New Issue
Block a user