]> git.proxmox.com Git - proxmox-offline-mirror.git/blame - 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
CommitLineData
290810c2 1use std::{env, fs::remove_dir_all, path::Path};
06474893
FG
2
3use anyhow::{bail, Error};
9ecde319
FG
4use serde_json::Value;
5
6use proxmox_router::cli::{
7 default_table_format_options, format_and_print_result_full, get_output_format, CliCommand,
8 CliCommandMap, ColumnConfig, CommandLineInterface, OUTPUT_FORMAT,
9};
10use proxmox_schema::{api, param_bail, ApiType, ArraySchema, ReturnType};
11
8b267808 12use 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
18pub 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
23pub 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
28pub const SHOW_MIRROR_RETURN_TYPE: ReturnType = ReturnType {
29 schema: &MirrorConfig::API_SCHEMA,
30 optional: true,
31};
32
33pub 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
38pub 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
59async 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(&param);
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
102async 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(&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.
139async 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, &section_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.
185async 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, &section_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.
234pub 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.
291async 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(&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: {
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 334async 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(&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.
371async 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, &section_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.
417async 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, &section_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.
469pub 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
501pub 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}