]> git.proxmox.com Git - proxmox-offline-mirror.git/blob - src/bin/proxmox_offline_mirror_cmds/config.rs
fix #4259: mirror: add ignore-errors option
[proxmox-offline-mirror.git] / src / bin / proxmox_offline_mirror_cmds / config.rs
1 use std::{env, fs::remove_dir_all, path::Path};
2
3 use anyhow::{bail, Error};
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
12 use proxmox_offline_mirror::{
13 config::{MediaConfig, MediaConfigUpdater, MirrorConfig, MirrorConfigUpdater},
14 mirror,
15 types::{MEDIA_ID_SCHEMA, MIRROR_ID_SCHEMA},
16 };
17
18 pub fn get_config_path() -> String {
19 env::var("PROXMOX_OFFLINE_MIRROR_CONFIG")
20 .unwrap_or_else(|_| "/etc/proxmox-offline-mirror.cfg".to_string())
21 }
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> {
60 let config = config.unwrap_or_else(get_config_path);
61
62 let (config, _digest) = proxmox_offline_mirror::config::config(&config)?;
63 let config: Vec<MirrorConfig> = config.convert_to_typed_array("mirror")?;
64
65 let output_format = get_output_format(&param);
66 let options = default_table_format_options()
67 .column(ColumnConfig::new("id").header("ID"))
68 .column(ColumnConfig::new("repository"))
69 .column(ColumnConfig::new("base-dir"))
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> {
103 let config = config.unwrap_or_else(get_config_path);
104
105 let (config, _digest) = proxmox_offline_mirror::config::config(&config)?;
106 let mut config = config.lookup_json("mirror", &id)?;
107
108 let output_format = get_output_format(&param);
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> {
144 let config = config.unwrap_or_else(get_config_path);
145
146 let _lock = proxmox_offline_mirror::config::lock_config(&config)?;
147
148 let (mut section_config, _digest) = proxmox_offline_mirror::config::config(&config)?;
149
150 if section_config.sections.get(&data.id).is_some() {
151 param_bail!("name", "mirror config entry '{}' already exists.", data.id);
152 }
153
154 mirror::init(&data)?;
155
156 section_config.set_data(&data.id, "mirror", &data)?;
157 proxmox_offline_mirror::config::save_config(&config, &section_config)?;
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> {
191 let config_file = config.unwrap_or_else(get_config_path);
192
193 let _lock = proxmox_offline_mirror::config::lock_config(&config_file)?;
194
195 // TODO (optionally?) remove media entries?
196 let (mut section_config, _digest) = proxmox_offline_mirror::config::config(&config_file)?;
197 match section_config.lookup::<MirrorConfig>("mirror", &id) {
198 Ok(config) => {
199 if remove_data {
200 mirror::destroy(&config)?;
201 }
202
203 section_config.sections.remove(&id);
204 }
205 _ => {
206 param_bail!("id", "mirror config entry '{}' does not exist!", id);
207 }
208 }
209
210 proxmox_offline_mirror::config::save_config(&config_file, &section_config)?;
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> {
239 let config_file = config.unwrap_or_else(get_config_path);
240
241 let _lock = proxmox_offline_mirror::config::lock_config(&config_file)?;
242
243 let (mut config, _digest) = proxmox_offline_mirror::config::config(&config_file)?;
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 }
253 if let Some(base_dir) = update.base_dir {
254 data.base_dir = base_dir
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 }
265 if let Some(ignore_errors) = update.ignore_errors {
266 data.ignore_errors = ignore_errors
267 }
268
269 config.set_data(&id, "mirror", &data)?;
270 proxmox_offline_mirror::config::save_config(&config_file, &config)?;
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> {
292 let config = config.unwrap_or_else(get_config_path);
293
294 let (config, _digest) = proxmox_offline_mirror::config::config(&config)?;
295 let config: Vec<MediaConfig> = config.convert_to_typed_array("medium")?;
296
297 let output_format = get_output_format(&param);
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: {
324 schema: MEDIA_ID_SCHEMA,
325 },
326 "output-format": {
327 schema: OUTPUT_FORMAT,
328 optional: true,
329 },
330 }
331 },
332 )]
333 /// Show full medium config entry.
334 async fn show_medium(config: Option<String>, id: String, param: Value) -> Result<Value, Error> {
335 let config = config.unwrap_or_else(get_config_path);
336
337 let (config, _digest) = proxmox_offline_mirror::config::config(&config)?;
338 let mut config = config.lookup_json("medium", &id)?;
339
340 let output_format = get_output_format(&param);
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> {
376 let config = config.unwrap_or_else(get_config_path);
377
378 let _lock = proxmox_offline_mirror::config::lock_config(&config)?;
379
380 let (mut section_config, _digest) = proxmox_offline_mirror::config::config(&config)?;
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)?;
389 proxmox_offline_mirror::config::save_config(&config, &section_config)?;
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: {
403 schema: MEDIA_ID_SCHEMA,
404 },
405 "remove-data": {
406 type: bool,
407 description: "Remove ALL DATA on medium as well.",
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> {
423 let config_file = config.unwrap_or_else(get_config_path);
424
425 let _lock = proxmox_offline_mirror::config::lock_config(&config_file)?;
426
427 let (mut section_config, _digest) = proxmox_offline_mirror::config::config(&config_file)?;
428 match section_config.lookup::<MediaConfig>("medium", &id) {
429 Ok(medium) => {
430 if remove_data {
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)?;
436 }
437
438 section_config.sections.remove(&id);
439 }
440 _ => {
441 param_bail!("id", "config section '{}' does not exist!", id);
442 }
443 }
444
445 proxmox_offline_mirror::config::save_config(&config_file, &section_config)?;
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: {
459 schema: MEDIA_ID_SCHEMA,
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> {
474 let config_file = config.unwrap_or_else(get_config_path);
475
476 let _lock = proxmox_offline_mirror::config::lock_config(&config_file)?;
477
478 let (mut config, _digest) = proxmox_offline_mirror::config::config(&config_file)?;
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)?;
496 proxmox_offline_mirror::config::save_config(&config_file, &config)?;
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 }