]>
Commit | Line | Data |
---|---|---|
290810c2 | 1 | use std::{env, fs::remove_dir_all, path::Path}; |
06474893 FG |
2 | |
3 | use anyhow::{bail, Error}; | |
9ecde319 FG |
4 | use serde_json::Value; |
5 | ||
6 | use proxmox_router::cli::{ | |
7 | default_table_format_options, format_and_print_result_full, get_output_format, CliCommand, | |
8 | CliCommandMap, ColumnConfig, CommandLineInterface, OUTPUT_FORMAT, | |
9 | }; | |
10 | use proxmox_schema::{api, param_bail, ApiType, ArraySchema, ReturnType}; | |
11 | ||
8b267808 | 12 | use proxmox_offline_mirror::{ |
9ecde319 | 13 | config::{MediaConfig, MediaConfigUpdater, MirrorConfig, MirrorConfigUpdater}, |
d035ecb5 | 14 | mirror, |
b42cad3b | 15 | types::{MEDIA_ID_SCHEMA, MIRROR_ID_SCHEMA}, |
9ecde319 FG |
16 | }; |
17 | ||
290810c2 FG |
18 | pub fn get_config_path() -> String { |
19 | env::var("PROXMOX_OFFLINE_MIRROR_CONFIG") | |
54c83977 | 20 | .unwrap_or_else(|_| "/etc/proxmox-offline-mirror.cfg".to_string()) |
290810c2 | 21 | } |
9ecde319 FG |
22 | |
23 | pub const LIST_MIRRORS_RETURN_TYPE: ReturnType = ReturnType { | |
24 | optional: false, | |
25 | schema: &ArraySchema::new("Returns the list of mirrors.", &MirrorConfig::API_SCHEMA).schema(), | |
26 | }; | |
27 | ||
28 | pub const SHOW_MIRROR_RETURN_TYPE: ReturnType = ReturnType { | |
29 | schema: &MirrorConfig::API_SCHEMA, | |
30 | optional: true, | |
31 | }; | |
32 | ||
33 | pub const LIST_MEDIA_RETURN_TYPE: ReturnType = ReturnType { | |
34 | optional: false, | |
35 | schema: &ArraySchema::new("Returns the list of mirrors.", &MediaConfig::API_SCHEMA).schema(), | |
36 | }; | |
37 | ||
38 | pub const SHOW_MEDIUM_RETURN_TYPE: ReturnType = ReturnType { | |
39 | schema: &MediaConfig::API_SCHEMA, | |
40 | optional: true, | |
41 | }; | |
42 | ||
43 | #[api( | |
44 | input: { | |
45 | properties: { | |
46 | config: { | |
47 | type: String, | |
48 | optional: true, | |
49 | description: "Path to mirroring config file.", | |
50 | }, | |
51 | "output-format": { | |
52 | schema: OUTPUT_FORMAT, | |
53 | optional: true, | |
54 | }, | |
55 | } | |
56 | }, | |
57 | )] | |
58 | /// List configured mirrors | |
59 | async fn list_mirror(config: Option<String>, param: Value) -> Result<Value, Error> { | |
54c83977 | 60 | let config = config.unwrap_or_else(get_config_path); |
9ecde319 | 61 | |
8b267808 | 62 | let (config, _digest) = proxmox_offline_mirror::config::config(&config)?; |
9ecde319 FG |
63 | let config: Vec<MirrorConfig> = config.convert_to_typed_array("mirror")?; |
64 | ||
65 | let output_format = get_output_format(¶m); | |
66 | let options = default_table_format_options() | |
67 | .column(ColumnConfig::new("id").header("ID")) | |
68 | .column(ColumnConfig::new("repository")) | |
c598cb15 | 69 | .column(ColumnConfig::new("base-dir")) |
9ecde319 FG |
70 | .column(ColumnConfig::new("verify")) |
71 | .column(ColumnConfig::new("sync")); | |
72 | ||
73 | format_and_print_result_full( | |
74 | &mut serde_json::json!(config), | |
75 | &LIST_MIRRORS_RETURN_TYPE, | |
76 | &output_format, | |
77 | &options, | |
78 | ); | |
79 | ||
80 | Ok(Value::Null) | |
81 | } | |
82 | ||
83 | #[api( | |
84 | input: { | |
85 | properties: { | |
86 | config: { | |
87 | type: String, | |
88 | optional: true, | |
89 | description: "Path to mirroring config file.", | |
90 | }, | |
91 | id: { | |
92 | schema: MIRROR_ID_SCHEMA, | |
93 | }, | |
94 | "output-format": { | |
95 | schema: OUTPUT_FORMAT, | |
96 | optional: true, | |
97 | }, | |
98 | } | |
99 | }, | |
100 | )] | |
101 | /// Show full mirror config | |
102 | async fn show_mirror(config: Option<String>, id: String, param: Value) -> Result<Value, Error> { | |
54c83977 | 103 | let config = config.unwrap_or_else(get_config_path); |
9ecde319 | 104 | |
8b267808 | 105 | let (config, _digest) = proxmox_offline_mirror::config::config(&config)?; |
9ecde319 FG |
106 | let mut config = config.lookup_json("mirror", &id)?; |
107 | ||
108 | let output_format = get_output_format(¶m); | |
109 | format_and_print_result_full( | |
110 | &mut config, | |
111 | &SHOW_MIRROR_RETURN_TYPE, | |
112 | &output_format, | |
113 | &default_table_format_options(), | |
114 | ); | |
115 | Ok(Value::Null) | |
116 | } | |
117 | ||
118 | #[api( | |
119 | protected: true, | |
120 | input: { | |
121 | properties: { | |
122 | config: { | |
123 | type: String, | |
124 | optional: true, | |
125 | description: "Path to mirroring config file.", | |
126 | }, | |
127 | data: { | |
128 | type: MirrorConfig, | |
129 | flatten: true, | |
130 | }, | |
131 | "output-format": { | |
132 | schema: OUTPUT_FORMAT, | |
133 | optional: true, | |
134 | }, | |
135 | }, | |
136 | }, | |
137 | )] | |
138 | /// Create new mirror config entry. | |
139 | async fn add_mirror( | |
140 | config: Option<String>, | |
141 | data: MirrorConfig, | |
142 | _param: Value, | |
143 | ) -> Result<Value, Error> { | |
54c83977 | 144 | let config = config.unwrap_or_else(get_config_path); |
9ecde319 | 145 | |
8b267808 | 146 | let _lock = proxmox_offline_mirror::config::lock_config(&config)?; |
9ecde319 | 147 | |
8b267808 | 148 | let (mut section_config, _digest) = proxmox_offline_mirror::config::config(&config)?; |
9ecde319 FG |
149 | |
150 | if section_config.sections.get(&data.id).is_some() { | |
151 | param_bail!("name", "mirror config entry '{}' already exists.", data.id); | |
152 | } | |
153 | ||
d035ecb5 FG |
154 | mirror::init(&data)?; |
155 | ||
9ecde319 | 156 | section_config.set_data(&data.id, "mirror", &data)?; |
8b267808 | 157 | proxmox_offline_mirror::config::save_config(&config, §ion_config)?; |
9ecde319 FG |
158 | |
159 | Ok(Value::Null) | |
160 | } | |
161 | ||
162 | #[api( | |
163 | input: { | |
164 | properties: { | |
165 | config: { | |
166 | type: String, | |
167 | optional: true, | |
168 | description: "Path to mirroring config file.", | |
169 | }, | |
170 | id: { | |
171 | schema: MIRROR_ID_SCHEMA, | |
172 | }, | |
173 | "remove-data": { | |
174 | type: bool, | |
175 | description: "Remove mirror data as well.", | |
176 | }, | |
177 | "output-format": { | |
178 | schema: OUTPUT_FORMAT, | |
179 | optional: true, | |
180 | }, | |
181 | } | |
182 | }, | |
183 | )] | |
184 | /// Remove mirror config entry. | |
185 | async fn remove_mirror( | |
186 | config: Option<String>, | |
187 | id: String, | |
188 | remove_data: bool, | |
189 | _param: Value, | |
190 | ) -> Result<Value, Error> { | |
54c83977 | 191 | let config_file = config.unwrap_or_else(get_config_path); |
9ecde319 | 192 | |
8b267808 | 193 | let _lock = proxmox_offline_mirror::config::lock_config(&config_file)?; |
9ecde319 FG |
194 | |
195 | // TODO (optionally?) remove media entries? | |
8b267808 | 196 | let (mut section_config, _digest) = proxmox_offline_mirror::config::config(&config_file)?; |
9ecde319 FG |
197 | match section_config.lookup::<MirrorConfig>("mirror", &id) { |
198 | Ok(config) => { | |
199 | if remove_data { | |
d035ecb5 | 200 | mirror::destroy(&config)?; |
9ecde319 FG |
201 | } |
202 | ||
203 | section_config.sections.remove(&id); | |
204 | } | |
205 | _ => { | |
206 | param_bail!("id", "mirror config entry '{}' does not exist!", id); | |
207 | } | |
208 | } | |
209 | ||
8b267808 | 210 | proxmox_offline_mirror::config::save_config(&config_file, §ion_config)?; |
9ecde319 FG |
211 | |
212 | Ok(Value::Null) | |
213 | } | |
214 | ||
215 | #[api( | |
216 | input: { | |
217 | properties: { | |
218 | config: { | |
219 | type: String, | |
220 | optional: true, | |
221 | description: "Path to mirroring config file.", | |
222 | }, | |
223 | id: { | |
224 | schema: MIRROR_ID_SCHEMA, | |
225 | }, | |
226 | update: { | |
227 | type: MirrorConfigUpdater, | |
228 | flatten: true, | |
229 | }, | |
230 | }, | |
231 | }, | |
232 | )] | |
233 | /// Update mirror config entry. | |
234 | pub fn update_mirror( | |
235 | update: MirrorConfigUpdater, | |
236 | config: Option<String>, | |
237 | id: String, | |
238 | ) -> Result<(), Error> { | |
54c83977 | 239 | let config_file = config.unwrap_or_else(get_config_path); |
9ecde319 | 240 | |
8b267808 | 241 | let _lock = proxmox_offline_mirror::config::lock_config(&config_file)?; |
9ecde319 | 242 | |
8b267808 | 243 | let (mut config, _digest) = proxmox_offline_mirror::config::config(&config_file)?; |
9ecde319 FG |
244 | |
245 | let mut data: MirrorConfig = config.lookup("mirror", &id)?; | |
246 | ||
247 | if let Some(key_path) = update.key_path { | |
248 | data.key_path = key_path | |
249 | } | |
250 | if let Some(repository) = update.repository { | |
251 | data.repository = repository | |
252 | } | |
c598cb15 FG |
253 | if let Some(base_dir) = update.base_dir { |
254 | data.base_dir = base_dir | |
9ecde319 FG |
255 | } |
256 | if let Some(architectures) = update.architectures { | |
257 | data.architectures = architectures | |
258 | } | |
259 | if let Some(sync) = update.sync { | |
260 | data.sync = sync | |
261 | } | |
262 | if let Some(verify) = update.verify { | |
263 | data.verify = verify | |
264 | } | |
96a80415 FG |
265 | if let Some(ignore_errors) = update.ignore_errors { |
266 | data.ignore_errors = ignore_errors | |
267 | } | |
9ecde319 FG |
268 | |
269 | config.set_data(&id, "mirror", &data)?; | |
8b267808 | 270 | proxmox_offline_mirror::config::save_config(&config_file, &config)?; |
9ecde319 FG |
271 | |
272 | Ok(()) | |
273 | } | |
274 | ||
275 | #[api( | |
276 | input: { | |
277 | properties: { | |
278 | config: { | |
279 | type: String, | |
280 | optional: true, | |
281 | description: "Path to mirroring config file.", | |
282 | }, | |
283 | "output-format": { | |
284 | schema: OUTPUT_FORMAT, | |
285 | optional: true, | |
286 | }, | |
287 | } | |
288 | }, | |
289 | )] | |
290 | /// List configured media. | |
291 | async fn list_media(config: Option<String>, param: Value) -> Result<Value, Error> { | |
54c83977 | 292 | let config = config.unwrap_or_else(get_config_path); |
9ecde319 | 293 | |
8b267808 | 294 | let (config, _digest) = proxmox_offline_mirror::config::config(&config)?; |
9ecde319 FG |
295 | let config: Vec<MediaConfig> = config.convert_to_typed_array("medium")?; |
296 | ||
297 | let output_format = get_output_format(¶m); | |
298 | let options = default_table_format_options() | |
299 | .column(ColumnConfig::new("id").header("ID")) | |
300 | .column(ColumnConfig::new("mountpoint")) | |
301 | .column(ColumnConfig::new("mirrors")) | |
302 | .column(ColumnConfig::new("verify")) | |
303 | .column(ColumnConfig::new("sync")); | |
304 | ||
305 | format_and_print_result_full( | |
306 | &mut serde_json::json!(config), | |
307 | &LIST_MEDIA_RETURN_TYPE, | |
308 | &output_format, | |
309 | &options, | |
310 | ); | |
311 | ||
312 | Ok(Value::Null) | |
313 | } | |
314 | ||
315 | #[api( | |
316 | input: { | |
317 | properties: { | |
318 | config: { | |
319 | type: String, | |
320 | optional: true, | |
321 | description: "Path to mirroring config file.", | |
322 | }, | |
323 | id: { | |
b42cad3b | 324 | schema: MEDIA_ID_SCHEMA, |
9ecde319 FG |
325 | }, |
326 | "output-format": { | |
327 | schema: OUTPUT_FORMAT, | |
328 | optional: true, | |
329 | }, | |
330 | } | |
331 | }, | |
332 | )] | |
2d13dcfc | 333 | /// Show full medium config entry. |
9ecde319 | 334 | async fn show_medium(config: Option<String>, id: String, param: Value) -> Result<Value, Error> { |
54c83977 | 335 | let config = config.unwrap_or_else(get_config_path); |
9ecde319 | 336 | |
8b267808 | 337 | let (config, _digest) = proxmox_offline_mirror::config::config(&config)?; |
9ecde319 FG |
338 | let mut config = config.lookup_json("medium", &id)?; |
339 | ||
340 | let output_format = get_output_format(¶m); | |
341 | format_and_print_result_full( | |
342 | &mut config, | |
343 | &SHOW_MEDIUM_RETURN_TYPE, | |
344 | &output_format, | |
345 | &default_table_format_options(), | |
346 | ); | |
347 | Ok(Value::Null) | |
348 | } | |
349 | ||
350 | #[api( | |
351 | protected: true, | |
352 | input: { | |
353 | properties: { | |
354 | config: { | |
355 | type: String, | |
356 | optional: true, | |
357 | description: "Path to mirroring config file.", | |
358 | }, | |
359 | data: { | |
360 | type: MediaConfig, | |
361 | flatten: true, | |
362 | }, | |
363 | "output-format": { | |
364 | schema: OUTPUT_FORMAT, | |
365 | optional: true, | |
366 | }, | |
367 | }, | |
368 | }, | |
369 | )] | |
370 | /// Create new medium config entry. | |
371 | async fn add_medium( | |
372 | config: Option<String>, | |
373 | data: MediaConfig, | |
374 | _param: Value, | |
375 | ) -> Result<Value, Error> { | |
54c83977 | 376 | let config = config.unwrap_or_else(get_config_path); |
9ecde319 | 377 | |
8b267808 | 378 | let _lock = proxmox_offline_mirror::config::lock_config(&config)?; |
9ecde319 | 379 | |
8b267808 | 380 | let (mut section_config, _digest) = proxmox_offline_mirror::config::config(&config)?; |
9ecde319 FG |
381 | |
382 | if section_config.sections.get(&data.id).is_some() { | |
383 | param_bail!("name", "config section '{}' already exists.", data.id); | |
384 | } | |
385 | ||
386 | // TODO check mountpoint and mirrors exist? | |
387 | ||
388 | section_config.set_data(&data.id, "medium", &data)?; | |
8b267808 | 389 | proxmox_offline_mirror::config::save_config(&config, §ion_config)?; |
9ecde319 FG |
390 | |
391 | Ok(Value::Null) | |
392 | } | |
393 | ||
394 | #[api( | |
395 | input: { | |
396 | properties: { | |
397 | config: { | |
398 | type: String, | |
399 | optional: true, | |
400 | description: "Path to mirroring config file.", | |
401 | }, | |
402 | id: { | |
b42cad3b | 403 | schema: MEDIA_ID_SCHEMA, |
9ecde319 FG |
404 | }, |
405 | "remove-data": { | |
406 | type: bool, | |
06474893 | 407 | description: "Remove ALL DATA on medium as well.", |
9ecde319 FG |
408 | }, |
409 | "output-format": { | |
410 | schema: OUTPUT_FORMAT, | |
411 | optional: true, | |
412 | }, | |
413 | } | |
414 | }, | |
415 | )] | |
416 | /// Remove medium config entry. | |
417 | async fn remove_medium( | |
418 | config: Option<String>, | |
419 | id: String, | |
420 | remove_data: bool, | |
421 | _param: Value, | |
422 | ) -> Result<Value, Error> { | |
54c83977 | 423 | let config_file = config.unwrap_or_else(get_config_path); |
9ecde319 | 424 | |
8b267808 | 425 | let _lock = proxmox_offline_mirror::config::lock_config(&config_file)?; |
9ecde319 | 426 | |
8b267808 | 427 | let (mut section_config, _digest) = proxmox_offline_mirror::config::config(&config_file)?; |
9ecde319 | 428 | match section_config.lookup::<MediaConfig>("medium", &id) { |
06474893 | 429 | Ok(medium) => { |
9ecde319 | 430 | if remove_data { |
06474893 FG |
431 | let medium_base = Path::new(&medium.mountpoint); |
432 | if !medium_base.exists() { | |
433 | bail!("Medium mountpoint doesn't exist."); | |
434 | } | |
435 | remove_dir_all(medium_base)?; | |
9ecde319 FG |
436 | } |
437 | ||
438 | section_config.sections.remove(&id); | |
439 | } | |
440 | _ => { | |
441 | param_bail!("id", "config section '{}' does not exist!", id); | |
442 | } | |
443 | } | |
444 | ||
8b267808 | 445 | proxmox_offline_mirror::config::save_config(&config_file, §ion_config)?; |
9ecde319 FG |
446 | |
447 | Ok(Value::Null) | |
448 | } | |
449 | ||
450 | #[api( | |
451 | input: { | |
452 | properties: { | |
453 | config: { | |
454 | type: String, | |
455 | optional: true, | |
456 | description: "Path to mirroring config file.", | |
457 | }, | |
458 | id: { | |
b42cad3b | 459 | schema: MEDIA_ID_SCHEMA, |
9ecde319 FG |
460 | }, |
461 | update: { | |
462 | type: MediaConfigUpdater, | |
463 | flatten: true, | |
464 | }, | |
465 | }, | |
466 | }, | |
467 | )] | |
468 | /// Update medium config entry. | |
469 | pub fn update_medium( | |
470 | update: MediaConfigUpdater, | |
471 | config: Option<String>, | |
472 | id: String, | |
473 | ) -> Result<(), Error> { | |
54c83977 | 474 | let config_file = config.unwrap_or_else(get_config_path); |
9ecde319 | 475 | |
8b267808 | 476 | let _lock = proxmox_offline_mirror::config::lock_config(&config_file)?; |
9ecde319 | 477 | |
8b267808 | 478 | let (mut config, _digest) = proxmox_offline_mirror::config::config(&config_file)?; |
9ecde319 FG |
479 | |
480 | let mut data: MediaConfig = config.lookup("medium", &id)?; | |
481 | ||
482 | if let Some(mountpoint) = update.mountpoint { | |
483 | data.mountpoint = mountpoint | |
484 | } | |
485 | if let Some(mirrors) = update.mirrors { | |
486 | data.mirrors = mirrors | |
487 | } | |
488 | if let Some(sync) = update.sync { | |
489 | data.sync = sync | |
490 | } | |
491 | if let Some(verify) = update.verify { | |
492 | data.verify = verify | |
493 | } | |
494 | ||
495 | config.set_data(&id, "medium", &data)?; | |
8b267808 | 496 | proxmox_offline_mirror::config::save_config(&config_file, &config)?; |
9ecde319 FG |
497 | |
498 | Ok(()) | |
499 | } | |
500 | ||
501 | pub fn config_commands() -> CommandLineInterface { | |
502 | let mirror_cmd_def = CliCommandMap::new() | |
503 | .insert("list", CliCommand::new(&API_METHOD_LIST_MIRROR)) | |
504 | .insert("add", CliCommand::new(&API_METHOD_ADD_MIRROR)) | |
505 | .insert("show", CliCommand::new(&API_METHOD_SHOW_MIRROR)) | |
506 | .insert("remove", CliCommand::new(&API_METHOD_REMOVE_MIRROR)) | |
507 | .insert("update", CliCommand::new(&API_METHOD_UPDATE_MIRROR)); | |
508 | ||
509 | let media_cmd_def = CliCommandMap::new() | |
510 | .insert("list", CliCommand::new(&API_METHOD_LIST_MEDIA)) | |
511 | .insert("add", CliCommand::new(&API_METHOD_ADD_MEDIUM)) | |
512 | .insert("show", CliCommand::new(&API_METHOD_SHOW_MEDIUM)) | |
513 | .insert("remove", CliCommand::new(&API_METHOD_REMOVE_MEDIUM)) | |
514 | .insert("update", CliCommand::new(&API_METHOD_UPDATE_MEDIUM)); | |
515 | ||
516 | let cmd_def = CliCommandMap::new() | |
517 | .insert("media", media_cmd_def) | |
518 | .insert("mirror", mirror_cmd_def); | |
519 | ||
520 | cmd_def.into() | |
521 | } |