bottomless-cli 0.1.14

Command-line interface for bottomless replication for libSQL
use anyhow::Result;
use clap::{Parser, Subcommand};

mod replicator_extras;
use replicator_extras::Replicator;

#[derive(Debug, Parser)]
#[command(name = "bottomless-cli")]
#[command(about = "Bottomless CLI", long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
    #[clap(long, short)]
    endpoint: Option<String>,
    #[clap(long, short)]
    bucket: Option<String>,
    #[clap(long, short)]
    database: Option<String>,
}

#[derive(Debug, Subcommand)]
enum Commands {
    #[clap(about = "List available generations")]
    Ls {
        #[clap(long, short, long_help = "List details about single generation")]
        generation: Option<uuid::Uuid>,
        #[clap(
            long,
            short,
            conflicts_with = "generation",
            long_help = "List only <limit> newest generations"
        )]
        limit: Option<u64>,
        #[clap(
            long,
            conflicts_with = "generation",
            long_help = "List only generations older than given date"
        )]
        older_than: Option<chrono::NaiveDate>,
        #[clap(
            long,
            conflicts_with = "generation",
            long_help = "List only generations newer than given date"
        )]
        newer_than: Option<chrono::NaiveDate>,
        #[clap(
            long,
            short,
            long_help = "Print detailed information on each generation"
        )]
        verbose: bool,
    },
    #[clap(about = "Restore the database")]
    Restore {
        #[clap(
            long,
            short,
            long_help = "Generation to restore from.\nSkip this parameter to restore from the newest generation."
        )]
        generation: Option<uuid::Uuid>,
    },
    #[clap(about = "Remove given generation from remote storage")]
    Rm {
        #[clap(long, short)]
        generation: Option<uuid::Uuid>,
        #[clap(
            long,
            conflicts_with = "generation",
            long_help = "Remove generations older than given date"
        )]
        older_than: Option<chrono::NaiveDate>,
        #[clap(long, short)]
        verbose: bool,
    },
}

async fn run() -> Result<()> {
    tracing_subscriber::fmt::init();
    let options = Cli::parse();

    if let Some(ep) = options.endpoint {
        std::env::set_var("LIBSQL_BOTTOMLESS_ENDPOINT", ep)
    }

    if let Some(bucket) = options.bucket {
        std::env::set_var("LIBSQL_BOTTOMLESS_BUCKET", bucket)
    }

    let mut client = Replicator::new().await?;

    let database = match options.database {
        Some(db) => db,
        None => {
            match client.detect_db().await {
                Some(db) => db,
                None => {
                    println!("Could not autodetect the database. Please pass it explicitly with -d option");
                    return Ok(());
                }
            }
        }
    };
    tracing::info!("Database: {}", database);

    client.register_db(database);

    match options.command {
        Commands::Ls {
            generation,
            limit,
            older_than,
            newer_than,
            verbose,
        } => match generation {
            Some(gen) => client.list_generation(gen).await?,
            None => {
                client
                    .list_generations(limit, older_than, newer_than, verbose)
                    .await?
            }
        },
        Commands::Restore { generation } => {
            match generation {
                Some(gen) => client.restore_from(gen).await?,
                None => client.restore().await?,
            };
        }
        Commands::Rm {
            generation,
            older_than,
            verbose,
        } => match (generation, older_than) {
            (None, Some(older_than)) => client.remove_many(older_than, verbose).await?,
            (Some(generation), None) => client.remove(generation, verbose).await?,
            (Some(_), Some(_)) => unreachable!(),
            (None, None) => println!(
                "rm command cannot be run without parameters; see -h or --help for details"
            ),
        },
    };
    Ok(())
}

#[tokio::main]
async fn main() {
    if let Err(e) = run().await {
        eprintln!("Error: {}", e);
        std::process::exit(1)
    }
}