Skip to content

Commit df2c2cc

Browse files
authored
Add backup/restore commands (#6)
* Add backup command * Add restore command * Appease linter
1 parent c771d7d commit df2c2cc

6 files changed

Lines changed: 646 additions & 6 deletions

File tree

src/cli.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ pub enum Commands {
7070
#[command(subcommand)]
7171
command: WebhookCommands,
7272
},
73+
/// Manage backups
74+
Backup {
75+
#[command(subcommand)]
76+
command: BackupCommands,
77+
},
78+
/// Manage restores
79+
Restore {
80+
#[command(subcommand)]
81+
command: RestoreCommands,
82+
},
7383
/// List available PHP versions
7484
PhpVersions,
7585
/// Configure MCP integration for Claude
@@ -955,6 +965,68 @@ pub enum EventCommands {
955965
},
956966
}
957967

968+
#[derive(Subcommand)]
969+
pub enum BackupCommands {
970+
/// List backups for a site
971+
List {
972+
/// Site ID
973+
site_id: String,
974+
/// Page number
975+
#[arg(long, default_value = "1")]
976+
page: u32,
977+
/// Items per page
978+
#[arg(long, default_value = "15")]
979+
per_page: u32,
980+
},
981+
/// Show backup details
982+
Show {
983+
/// Site ID
984+
site_id: String,
985+
/// Backup ID
986+
backup_id: String,
987+
},
988+
/// Create a manual backup
989+
Create {
990+
/// Site ID
991+
site_id: String,
992+
/// Backup description
993+
#[arg(long)]
994+
description: Option<String>,
995+
},
996+
}
997+
998+
#[derive(Subcommand)]
999+
pub enum RestoreCommands {
1000+
/// List restores for a site
1001+
List {
1002+
/// Site ID
1003+
site_id: String,
1004+
/// Page number
1005+
#[arg(long, default_value = "1")]
1006+
page: u32,
1007+
/// Items per page
1008+
#[arg(long, default_value = "15")]
1009+
per_page: u32,
1010+
},
1011+
/// Show restore details
1012+
Show {
1013+
/// Site ID
1014+
site_id: String,
1015+
/// Restore ID
1016+
restore_id: String,
1017+
},
1018+
/// Create a restore from a backup
1019+
Create {
1020+
/// Site ID
1021+
site_id: String,
1022+
/// Backup ID to restore from
1023+
backup_id: String,
1024+
/// Restore scope (full, database, files)
1025+
#[arg(long, default_value = "full")]
1026+
scope: String,
1027+
},
1028+
}
1029+
9581030
#[derive(Subcommand)]
9591031
pub enum WebhookCommands {
9601032
/// List webhooks

src/commands/backup.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
use serde::Serialize;
2+
use serde_json::Value;
3+
4+
use crate::api::{ApiClient, ApiError};
5+
use crate::output::{
6+
OutputFormat, extract_pagination, format_option, print_json, print_key_value, print_message,
7+
print_pagination, print_table,
8+
};
9+
10+
#[derive(Debug, Serialize)]
11+
struct PaginationQuery {
12+
page: u32,
13+
per_page: u32,
14+
}
15+
16+
#[derive(Debug, Serialize)]
17+
struct CreateBackupRequest {
18+
r#type: String,
19+
#[serde(skip_serializing_if = "Option::is_none")]
20+
description: Option<String>,
21+
}
22+
23+
pub fn list(
24+
client: &ApiClient,
25+
site_id: &str,
26+
page: u32,
27+
per_page: u32,
28+
format: OutputFormat,
29+
) -> Result<(), ApiError> {
30+
let query = PaginationQuery { page, per_page };
31+
let response: Value =
32+
client.get_with_query(&format!("/api/v1/vector/sites/{}/backups", site_id), &query)?;
33+
34+
if format == OutputFormat::Json {
35+
print_json(&response);
36+
return Ok(());
37+
}
38+
39+
let backups = response["data"]
40+
.as_array()
41+
.ok_or_else(|| ApiError::Other("Invalid response format".to_string()))?;
42+
43+
if backups.is_empty() {
44+
print_message("No backups found.");
45+
return Ok(());
46+
}
47+
48+
let rows: Vec<Vec<String>> = backups
49+
.iter()
50+
.map(|b| {
51+
vec![
52+
b["id"].as_str().unwrap_or("-").to_string(),
53+
b["type"].as_str().unwrap_or("-").to_string(),
54+
b["status"].as_str().unwrap_or("-").to_string(),
55+
format_option(&b["description"].as_str().map(String::from)),
56+
format_option(&b["created_at"].as_str().map(String::from)),
57+
]
58+
})
59+
.collect();
60+
61+
print_table(vec!["ID", "Type", "Status", "Description", "Created"], rows);
62+
63+
if let Some((current, last, total)) = extract_pagination(&response) {
64+
print_pagination(current, last, total);
65+
}
66+
67+
Ok(())
68+
}
69+
70+
pub fn show(
71+
client: &ApiClient,
72+
site_id: &str,
73+
backup_id: &str,
74+
format: OutputFormat,
75+
) -> Result<(), ApiError> {
76+
let response: Value = client.get(&format!(
77+
"/api/v1/vector/sites/{}/backups/{}",
78+
site_id, backup_id
79+
))?;
80+
81+
if format == OutputFormat::Json {
82+
print_json(&response);
83+
return Ok(());
84+
}
85+
86+
let backup = &response["data"];
87+
88+
print_key_value(vec![
89+
("ID", backup["id"].as_str().unwrap_or("-").to_string()),
90+
("Type", backup["type"].as_str().unwrap_or("-").to_string()),
91+
(
92+
"Status",
93+
backup["status"].as_str().unwrap_or("-").to_string(),
94+
),
95+
(
96+
"Description",
97+
format_option(&backup["description"].as_str().map(String::from)),
98+
),
99+
(
100+
"Snapshot ID",
101+
format_option(&backup["snapshot_id"].as_str().map(String::from)),
102+
),
103+
(
104+
"Started At",
105+
format_option(&backup["started_at"].as_str().map(String::from)),
106+
),
107+
(
108+
"Completed At",
109+
format_option(&backup["completed_at"].as_str().map(String::from)),
110+
),
111+
(
112+
"Created At",
113+
format_option(&backup["created_at"].as_str().map(String::from)),
114+
),
115+
(
116+
"Updated At",
117+
format_option(&backup["updated_at"].as_str().map(String::from)),
118+
),
119+
]);
120+
121+
Ok(())
122+
}
123+
124+
pub fn create(
125+
client: &ApiClient,
126+
site_id: &str,
127+
description: Option<String>,
128+
format: OutputFormat,
129+
) -> Result<(), ApiError> {
130+
let body = CreateBackupRequest {
131+
r#type: "manual".to_string(),
132+
description,
133+
};
134+
135+
let response: Value =
136+
client.post(&format!("/api/v1/vector/sites/{}/backups", site_id), &body)?;
137+
138+
if format == OutputFormat::Json {
139+
print_json(&response);
140+
return Ok(());
141+
}
142+
143+
let backup = &response["data"];
144+
print_message(&format!(
145+
"Backup created: {} ({})",
146+
backup["id"].as_str().unwrap_or("-"),
147+
backup["status"].as_str().unwrap_or("-")
148+
));
149+
150+
print_key_value(vec![
151+
("ID", backup["id"].as_str().unwrap_or("-").to_string()),
152+
("Type", backup["type"].as_str().unwrap_or("-").to_string()),
153+
(
154+
"Status",
155+
backup["status"].as_str().unwrap_or("-").to_string(),
156+
),
157+
(
158+
"Description",
159+
format_option(&backup["description"].as_str().map(String::from)),
160+
),
161+
(
162+
"Created At",
163+
format_option(&backup["created_at"].as_str().map(String::from)),
164+
),
165+
]);
166+
167+
Ok(())
168+
}

src/commands/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
pub mod account;
22
pub mod auth;
3+
pub mod backup;
34
pub mod db;
45
pub mod deploy;
56
pub mod env;
67
pub mod event;
78
pub mod mcp;
9+
pub mod restore;
810
pub mod site;
911
pub mod ssl;
1012
pub mod waf;

0 commit comments

Comments
 (0)