Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,31 @@ pub enum BackupCommands {
#[arg(long)]
description: Option<String>,
},
/// 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)]
Expand Down
91 changes: 91 additions & 0 deletions src/commands/backup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
22 changes: 20 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
}
}

Expand Down
44 changes: 44 additions & 0 deletions tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down