use anyhow::{Context, Result};
use clap::Parser;
use vantage_aws::AwsAccount;
use vantage_aws::dynamodb::DynamoDB;
use vantage_aws::models::dynamodb::tables_table;
use vantage_dataset::traits::ReadableValueSet;
use vantage_table::table::Table;
use vantage_table::traits::table_like::TableLike;
use vantage_types::EmptyEntity;
#[derive(Parser)]
#[command(
name = "aws-dynamo",
about = "List DynamoDB tables and dump their contents"
)]
struct Cli {
#[arg(long)]
region: Option<String>,
#[arg(long, default_value = "id")]
id_field: String,
#[arg(long, default_value_t = 20)]
sample: usize,
#[arg(long)]
filter: Option<String>,
#[arg(long)]
no_scan: bool,
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
if let Some(region) = &cli.region {
unsafe { std::env::set_var("AWS_REGION", region) };
}
let aws = AwsAccount::from_default().context(
"Set AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_REGION, or configure ~/.aws/credentials [default]",
)?;
let names: Vec<String> = tables_table(aws.clone())
.list_values()
.await
.context("ListTables failed")?
.into_keys()
.collect();
let region_label = std::env::var("AWS_REGION").unwrap_or_else(|_| "<unset>".into());
println!("region: {}", region_label);
println!("tables ({}):", names.len());
let db = DynamoDB::new(aws);
for name in &names {
let count_table: Table<DynamoDB, EmptyEntity> = Table::new(name.as_str(), db.clone());
let count = match count_table.get_count().await {
Ok(n) => format!("{} items", n),
Err(e) => format!("count failed: {}", e),
};
println!(" - {} ({})", name, count);
}
println!();
if cli.no_scan {
return Ok(());
}
for name in &names {
if let Some(needle) = &cli.filter
&& !name.contains(needle)
{
continue;
}
scan_table(&db, name, &cli.id_field, cli.sample).await;
println!();
}
Ok(())
}
async fn scan_table(db: &DynamoDB, name: &str, id_field: &str, sample: usize) {
let table: Table<DynamoDB, EmptyEntity> = Table::new(name, db.clone()).with_id_column(id_field);
println!("=== {} (id={}) ===", name, id_field);
match table.list_values().await {
Ok(items) if items.is_empty() => println!("(empty)"),
Ok(items) => {
let total = items.len();
for (i, (id, rec)) in items.iter().enumerate() {
if i >= sample {
println!("... ({} more)", total - sample);
break;
}
println!("- {}={}", id_field, id);
for (k, v) in rec.iter() {
if k == id_field {
continue;
}
let json = serde_json::Value::from(v.clone());
println!(" {}: {}", k, json);
}
}
}
Err(e) => println!("scan failed: {}", e),
}
}