diff --git a/src/cli.rs b/src/cli.rs index 3cf85ad..6ba944b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -996,6 +996,31 @@ pub enum BackupCommands { #[arg(long)] description: Option, }, + /// Download a backup archive + Download { + #[command(subcommand)] + command: BackupDownloadCommands, + }, +} + +#[derive(Subcommand)] +pub enum BackupDownloadCommands { + /// Create a backup download request + Create { + /// Site ID + site_id: String, + /// Backup ID + backup_id: String, + }, + /// Check backup download status + Status { + /// Site ID + site_id: String, + /// Backup ID + backup_id: String, + /// Download ID + download_id: String, + }, } #[derive(Subcommand)] diff --git a/src/commands/backup.rs b/src/commands/backup.rs index dd66a0c..f7274bd 100644 --- a/src/commands/backup.rs +++ b/src/commands/backup.rs @@ -178,3 +178,94 @@ pub fn create( Ok(()) } + +pub fn download_create( + client: &ApiClient, + site_id: &str, + backup_id: &str, + format: OutputFormat, +) -> Result<(), ApiError> { + let response: Value = client.post_empty(&format!( + "/api/v1/vector/sites/{}/backups/{}/downloads", + site_id, backup_id + ))?; + + if format == OutputFormat::Json { + print_json(&response); + return Ok(()); + } + + let data = &response["data"]; + print_message(&format!( + "Download requested: {} ({})", + data["id"].as_str().unwrap_or("-"), + data["status"].as_str().unwrap_or("-") + )); + print_message("\nCheck status with:"); + print_message(&format!( + " vector backup download status {} {} {}", + site_id, + backup_id, + data["id"].as_str().unwrap_or("DOWNLOAD_ID") + )); + + Ok(()) +} + +pub fn download_status( + client: &ApiClient, + site_id: &str, + backup_id: &str, + download_id: &str, + format: OutputFormat, +) -> Result<(), ApiError> { + let response: Value = client.get(&format!( + "/api/v1/vector/sites/{}/backups/{}/downloads/{}", + site_id, backup_id, download_id + ))?; + + if format == OutputFormat::Json { + print_json(&response); + return Ok(()); + } + + let data = &response["data"]; + print_key_value(vec![ + ("ID", data["id"].as_str().unwrap_or("-").to_string()), + ("Status", data["status"].as_str().unwrap_or("-").to_string()), + ( + "Size (bytes)", + format_option(&data["size_bytes"].as_u64().map(|v| v.to_string())), + ), + ( + "Duration (ms)", + format_option(&data["duration_ms"].as_u64().map(|v| v.to_string())), + ), + ( + "Error", + format_option(&data["error_message"].as_str().map(String::from)), + ), + ( + "Download URL", + format_option(&data["download_url"].as_str().map(String::from)), + ), + ( + "Download Expires", + format_option(&data["download_expires_at"].as_str().map(String::from)), + ), + ( + "Started At", + format_option(&data["started_at"].as_str().map(String::from)), + ), + ( + "Completed At", + format_option(&data["completed_at"].as_str().map(String::from)), + ), + ( + "Created At", + format_option(&data["created_at"].as_str().map(String::from)), + ), + ]); + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 40c6512..36670e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,8 +11,8 @@ use std::process; use api::{ApiClient, ApiError, EXIT_SUCCESS}; use cli::{ AccountApiKeyCommands, AccountCommands, AccountSecretCommands, AccountSshKeyCommands, - AuthCommands, BackupCommands, Cli, Commands, DbCommands, DbExportCommands, - DbImportSessionCommands, DeployCommands, EnvCommands, EnvDbCommands, + AuthCommands, BackupCommands, BackupDownloadCommands, Cli, Commands, DbCommands, + DbExportCommands, DbImportSessionCommands, DeployCommands, EnvCommands, EnvDbCommands, EnvDbImportSessionCommands, EnvSecretCommands, EventCommands, McpCommands, RestoreCommands, SiteCommands, SiteSshKeyCommands, SslCommands, WafAllowedReferrerCommands, WafBlockedIpCommands, WafBlockedReferrerCommands, WafCommands, WafRateLimitCommands, @@ -634,6 +634,24 @@ fn run_backup(command: BackupCommands, format: OutputFormat) -> Result<(), ApiEr scope, description, } => backup::create(&client, &site_id, &scope, description, format), + BackupCommands::Download { command } => run_backup_download(&client, command, format), + } +} + +fn run_backup_download( + client: &ApiClient, + command: BackupDownloadCommands, + format: OutputFormat, +) -> Result<(), ApiError> { + match command { + BackupDownloadCommands::Create { site_id, backup_id } => { + backup::download_create(client, &site_id, &backup_id, format) + } + BackupDownloadCommands::Status { + site_id, + backup_id, + download_id, + } => backup::download_status(client, &site_id, &backup_id, &download_id, format), } } diff --git a/tests/cli.rs b/tests/cli.rs index 4e5fe83..ad10b5b 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -176,6 +176,50 @@ fn test_backup_help() { assert!(stdout.contains("list")); assert!(stdout.contains("show")); assert!(stdout.contains("create")); + assert!(stdout.contains("download")); +} + +#[test] +fn test_backup_download_help() { + let output = vector_cmd() + .args(["backup", "download", "--help"]) + .output() + .expect("Failed to run"); + assert!(output.status.success()); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!(stdout.contains("create")); + assert!(stdout.contains("status")); +} + +#[test] +fn test_backup_download_create_requires_auth() { + let output = vector_cmd() + .args(["backup", "download", "create", "test-site", "test-backup"]) + .env("VECTOR_CONFIG_DIR", &nonexistent_config_dir()) + .env_remove("VECTOR_API_KEY") + .output() + .expect("Failed to run"); + assert!(!output.status.success()); + assert_eq!(output.status.code(), Some(2)); // EXIT_AUTH_ERROR +} + +#[test] +fn test_backup_download_status_requires_auth() { + let output = vector_cmd() + .args([ + "backup", + "download", + "status", + "test-site", + "test-backup", + "test-download", + ]) + .env("VECTOR_CONFIG_DIR", &nonexistent_config_dir()) + .env_remove("VECTOR_API_KEY") + .output() + .expect("Failed to run"); + assert!(!output.status.success()); + assert_eq!(output.status.code(), Some(2)); // EXIT_AUTH_ERROR } #[test]