]> git.proxmox.com Git - proxmox-offline-mirror.git/blob - src/bin/proxmox_offline_mirror_cmds/config.rs
b48a70808072b9179cc9a3cf9783e2fefbc34295
[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
266 config.set_data(&id, "mirror", &data)?;
267 proxmox_offline_mirror::config::save_config(&config_file, &config)?;
268
269 Ok(())
270 }
271
272 #[api(
273 input: {
274 properties: {
275 config: {
276 type: String,
277 optional: true,
278 description: "Path to mirroring config file.",
279 },
280 "output-format": {
281 schema: OUTPUT_FORMAT,
282 optional: true,
283 },
284 }
285 },
286 )]
287 /// List configured media.
288 async fn list_media(config: Option<String>, param: Value) -> Result<Value, Error> {
289 let config = config.unwrap_or_else(get_config_path);
290
291 let (config, _digest) = proxmox_offline_mirror::config::config(&config)?;
292 let config: Vec<MediaConfig> = config.convert_to_typed_array("medium")?;
293
294 let output_format = get_output_format(&param);
295 let options = default_table_format_options()
296 .column(ColumnConfig::new("id").header("ID"))
297 .column(ColumnConfig::new("mountpoint"))
298 .column(ColumnConfig::new("mirrors"))
299 .column(ColumnConfig::new("verify"))
300 .column(ColumnConfig::new("sync"));
301
302 format_and_print_result_full(
303 &mut serde_json::json!(config),
304 &LIST_MEDIA_RETURN_TYPE,
305 &output_format,
306 &options,
307 );
308
309 Ok(Value::Null)
310 }
311
312 #[api(
313 input: {
314 properties: {
315 config: {
316 type: String,
317 optional: true,
318 description: "Path to mirroring config file.",
319 },
320 id: {
321 schema: MEDIA_ID_SCHEMA,
322 },
323 "output-format": {
324 schema: OUTPUT_FORMAT,
325 optional: true,
326 },
327 }
328 },
329 )]
330 /// Show full medium config entry.
331 async fn show_medium(config: Option<String>, id: String, param: Value) -> Result<Value, Error> {
332 let config = config.unwrap_or_else(get_config_path);
333
334 let (config, _digest) = proxmox_offline_mirror::config::config(&config)?;
335 let mut config = config.lookup_json("medium", &id)?;
336
337 let output_format = get_output_format(&param);
338 format_and_print_result_full(
339 &mut config,
340 &SHOW_MEDIUM_RETURN_TYPE,
341 &output_format,
342 &default_table_format_options(),
343 );
344 Ok(Value::Null)
345 }
346
347 #[api(
348 protected: true,
349 input: {
350 properties: {
351 config: {
352 type: String,
353 optional: true,
354 description: "Path to mirroring config file.",
355 },
356 data: {
357 type: MediaConfig,
358 flatten: true,
359 },
360 "output-format": {
361 schema: OUTPUT_FORMAT,
362 optional: true,
363 },
364 },
365 },
366 )]
367 /// Create new medium config entry.
368 async fn add_medium(
369 config: Option<String>,
370 data: MediaConfig,
371 _param: Value,
372 ) -> Result<Value, Error> {
373 let config = config.unwrap_or_else(get_config_path);
374
375 let _lock = proxmox_offline_mirror::config::lock_config(&config)?;
376
377 let (mut section_config, _digest) = proxmox_offline_mirror::config::config(&config)?;
378
379 if section_config.sections.get(&data.id).is_some() {
380 param_bail!("name", "config section '{}' already exists.", data.id);
381 }
382
383 // TODO check mountpoint and mirrors exist?
384
385 section_config.set_data(&data.id, "medium", &data)?;
386 proxmox_offline_mirror::config::save_config(&config, &section_config)?;
387
388 Ok(Value::Null)
389 }
390
391 #[api(
392 input: {
393 properties: {
394 config: {
395 type: String,
396 optional: true,
397 description: "Path to mirroring config file.",
398 },
399 id: {
400 schema: MEDIA_ID_SCHEMA,
401 },
402 "remove-data": {
403 type: bool,
404 description: "Remove ALL DATA on medium as well.",
405 },
406 "output-format": {
407 schema: OUTPUT_FORMAT,
408 optional: true,
409 },
410 }
411 },
412 )]
413 /// Remove medium config entry.
414 async fn remove_medium(
415 config: Option<String>,
416 id: String,
417 remove_data: bool,
418 _param: Value,
419 ) -> Result<Value, Error> {
420 let config_file = config.unwrap_or_else(get_config_path);
421
422 let _lock = proxmox_offline_mirror::config::lock_config(&config_file)?;
423
424 let (mut section_config, _digest) = proxmox_offline_mirror::config::config(&config_file)?;
425 match section_config.lookup::<MediaConfig>("medium", &id) {
426 Ok(medium) => {
427 if remove_data {
428 let medium_base = Path::new(&medium.mountpoint);
429 if !medium_base.exists() {
430 bail!("Medium mountpoint doesn't exist.");
431 }
432 remove_dir_all(medium_base)?;
433 }
434
435 section_config.sections.remove(&id);
436 }
437 _ => {
438 param_bail!("id", "config section '{}' does not exist!", id);
439 }
440 }
441
442 proxmox_offline_mirror::config::save_config(&config_file, &section_config)?;
443
444 Ok(Value::Null)
445 }
446
447 #[api(
448 input: {
449 properties: {
450 config: {
451 type: String,
452 optional: true,
453 description: "Path to mirroring config file.",
454 },
455 id: {
456 schema: MEDIA_ID_SCHEMA,
457 },
458 update: {
459 type: MediaConfigUpdater,
460 flatten: true,
461 },
462 },
463 },
464 )]
465 /// Update medium config entry.
466 pub fn update_medium(
467 update: MediaConfigUpdater,
468 config: Option<String>,
469 id: String,
470 ) -> Result<(), Error> {
471 let config_file = config.unwrap_or_else(get_config_path);
472
473 let _lock = proxmox_offline_mirror::config::lock_config(&config_file)?;
474
475 let (mut config, _digest) = proxmox_offline_mirror::config::config(&config_file)?;
476
477 let mut data: MediaConfig = config.lookup("medium", &id)?;
478
479 if let Some(mountpoint) = update.mountpoint {
480 data.mountpoint = mountpoint
481 }
482 if let Some(mirrors) = update.mirrors {
483 data.mirrors = mirrors
484 }
485 if let Some(sync) = update.sync {
486 data.sync = sync
487 }
488 if let Some(verify) = update.verify {
489 data.verify = verify
490 }
491
492 config.set_data(&id, "medium", &data)?;
493 proxmox_offline_mirror::config::save_config(&config_file, &config)?;
494
495 Ok(())
496 }
497
498 pub fn config_commands() -> CommandLineInterface {
499 let mirror_cmd_def = CliCommandMap::new()
500 .insert("list", CliCommand::new(&API_METHOD_LIST_MIRROR))
501 .insert("add", CliCommand::new(&API_METHOD_ADD_MIRROR))
502 .insert("show", CliCommand::new(&API_METHOD_SHOW_MIRROR))
503 .insert("remove", CliCommand::new(&API_METHOD_REMOVE_MIRROR))
504 .insert("update", CliCommand::new(&API_METHOD_UPDATE_MIRROR));
505
506 let media_cmd_def = CliCommandMap::new()
507 .insert("list", CliCommand::new(&API_METHOD_LIST_MEDIA))
508 .insert("add", CliCommand::new(&API_METHOD_ADD_MEDIUM))
509 .insert("show", CliCommand::new(&API_METHOD_SHOW_MEDIUM))
510 .insert("remove", CliCommand::new(&API_METHOD_REMOVE_MEDIUM))
511 .insert("update", CliCommand::new(&API_METHOD_UPDATE_MEDIUM));
512
513 let cmd_def = CliCommandMap::new()
514 .insert("media", media_cmd_def)
515 .insert("mirror", mirror_cmd_def);
516
517 cmd_def.into()
518 }