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 .
|
cargo install --path .
|
||||||
```
|
```
|
||||||
|
|
||||||
To print the underlying docker commands as they run, build with debug
|
## seeing what it runs
|
||||||
assertions:
|
|
||||||
|
`-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
|
```bash
|
||||||
cargo install --path . --debug
|
ahab -v django test
|
||||||
|
ahab --dry-run postgres import ./dump
|
||||||
```
|
```
|
||||||
|
|
||||||
## shell completion
|
## shell completion
|
||||||
|
|||||||
@@ -8,8 +8,15 @@ use clap_complete::Shell;
|
|||||||
pub struct Ahab {
|
pub struct Ahab {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: Commands,
|
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)]
|
#[derive(Debug, Subcommand)]
|
||||||
pub enum Commands {
|
pub enum Commands {
|
||||||
/// Django related subcommands
|
/// Django related subcommands
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ use anyhow::{Context, Result, bail};
|
|||||||
use std::{
|
use std::{
|
||||||
fmt::Display,
|
fmt::Display,
|
||||||
process::{Child, Command, ExitStatus, Stdio},
|
process::{Child, Command, ExitStatus, Stdio},
|
||||||
|
sync::OnceLock,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::debug_eprintln;
|
|
||||||
|
|
||||||
pub struct Args(Vec<String>);
|
pub struct Args(Vec<String>);
|
||||||
|
|
||||||
impl From<&str> for Args {
|
impl From<&str> for Args {
|
||||||
@@ -54,8 +53,9 @@ impl CommandBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> Result<Command> {
|
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 (first, rest) = self.args.split_first().context("empty args")?;
|
||||||
let mut command = Command::new(first);
|
let mut command = Command::new(first);
|
||||||
command.args(rest);
|
command.args(rest);
|
||||||
@@ -72,6 +72,11 @@ impl CommandBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec(self) -> Result<()> {
|
pub fn exec(self) -> Result<()> {
|
||||||
|
if options().dry_run {
|
||||||
|
eprintln!("would run `{self}`");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let shown = self.to_string();
|
let shown = self.to_string();
|
||||||
let status = self.build()?.spawn()?.wait()?;
|
let status = self.build()?.spawn()?.wait()?;
|
||||||
|
|
||||||
@@ -83,6 +88,11 @@ impl CommandBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec_redirect_stdout(self, stdio: Stdio) -> Result<()> {
|
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 shown = self.to_string();
|
||||||
let status = self.build()?.stdout(stdio).spawn()?.wait()?;
|
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"),
|
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 anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
@@ -6,6 +6,11 @@ use clap::Parser;
|
|||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let args = cli::Ahab::parse();
|
let args = cli::Ahab::parse();
|
||||||
|
|
||||||
|
command_builder::set_options(command_builder::Options {
|
||||||
|
verbose: args.verbose,
|
||||||
|
dry_run: args.dry_run,
|
||||||
|
});
|
||||||
|
|
||||||
match args.command {
|
match args.command {
|
||||||
cli::Commands::Django { command } => match command {
|
cli::Commands::Django { command } => match command {
|
||||||
cli::Django::Bash => scripts::django::bash(),
|
cli::Django::Bash => scripts::django::bash(),
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ use std::{
|
|||||||
|
|
||||||
use super::docker_compose;
|
use super::docker_compose;
|
||||||
use crate::cli::Format;
|
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 CUSTOM_MAGIC: &[u8] = b"PGDMP";
|
||||||
const TAR_MAGIC: &[u8] = b"toc.dat";
|
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<()> {
|
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;
|
let deadline = Instant::now() + READY_TIMEOUT;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@@ -319,25 +323,29 @@ pub fn import(file: &Path) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wait_until_ready(&db)?;
|
wait_until_ready(&db)?;
|
||||||
match (kind, restore.as_deref()) {
|
if command_builder::is_dry_run() {
|
||||||
// psql's output is read rather than streamed here, to keep the expected
|
eprintln!("would restore with {tool}");
|
||||||
// role errors out of the way
|
} else {
|
||||||
(Kind::Cluster, Some(script)) => restore_cluster(&db, script, file)?,
|
match (kind, restore.as_deref()) {
|
||||||
(_, restore) => {
|
// psql's output is read rather than streamed here, to keep the expected
|
||||||
let status = match restore {
|
// role errors out of the way
|
||||||
Some(script) => piped(&db, script, file)?.spawn()?.wait()?,
|
(Kind::Cluster, Some(script)) => restore_cluster(&db, script, file)?,
|
||||||
None => in_container(&db)
|
(_, restore) => {
|
||||||
.args("pg_restore -U")
|
let status = match restore {
|
||||||
.arg(&db.user)
|
Some(script) => piped(&db, script, file)?.spawn()?.wait()?,
|
||||||
.arg(format!("--dbname={}", db.name))
|
None => in_container(&db)
|
||||||
.arg(&remote)
|
.args("pg_restore -U")
|
||||||
.build()?
|
.arg(&db.user)
|
||||||
.spawn()?
|
.arg(format!("--dbname={}", db.name))
|
||||||
.wait()?,
|
.arg(&remote)
|
||||||
};
|
.build()?
|
||||||
|
.spawn()?
|
||||||
|
.wait()?,
|
||||||
|
};
|
||||||
|
|
||||||
if !status.success() {
|
if !status.success() {
|
||||||
bail!("{tool} failed, the database is left empty");
|
bail!("{tool} failed, the database is left empty");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user