]>
Commit | Line | Data |
---|---|---|
9e61c01c | 1 | use anyhow::{Error, bail, format_err}; |
a4e86972 | 2 | use serde_json::{json, Value}; |
137a6ebc | 3 | use std::collections::HashMap; |
a4e86972 | 4 | |
e6513bd5 | 5 | use proxmox::list_subdirs_api_method; |
fa3f0584 TL |
6 | use proxmox::api::{api, RpcEnvironment, RpcEnvironmentType, Permission}; |
7 | use proxmox::api::router::{Router, SubdirMap}; | |
440472cb | 8 | use proxmox::tools::fs::{replace_file, CreateOptions}; |
a4e86972 | 9 | |
d830804f | 10 | use proxmox_apt::repositories::{ |
289738dc FE |
11 | APTRepositoryFile, APTRepositoryFileError, APTRepositoryHandle, APTRepositoryInfo, |
12 | APTStandardRepository, | |
d830804f | 13 | }; |
1d781c5b | 14 | use proxmox_http::ProxyConfig; |
4229633d | 15 | |
a1b71c3c | 16 | use crate::config::node; |
fa3f0584 | 17 | use crate::server::WorkerTask; |
57889533 FG |
18 | use crate::tools::{ |
19 | apt, | |
20 | pbs_simple_http, | |
21 | subscription, | |
22 | }; | |
fa3f0584 | 23 | use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; |
d830804f | 24 | use crate::api2::types::{Authid, APTUpdateInfo, NODE_SCHEMA, PROXMOX_CONFIG_DIGEST_SCHEMA, UPID_SCHEMA}; |
a4e86972 | 25 | |
a4e86972 SR |
26 | #[api( |
27 | input: { | |
28 | properties: { | |
29 | node: { | |
30 | schema: NODE_SCHEMA, | |
31 | }, | |
32 | }, | |
33 | }, | |
34 | returns: { | |
35 | description: "A list of packages with available updates.", | |
36 | type: Array, | |
e6513bd5 TL |
37 | items: { |
38 | type: APTUpdateInfo | |
39 | }, | |
a4e86972 | 40 | }, |
33508b12 | 41 | protected: true, |
a4e86972 SR |
42 | access: { |
43 | permission: &Permission::Privilege(&[], PRIV_SYS_AUDIT, false), | |
44 | }, | |
45 | )] | |
46 | /// List available APT updates | |
47 | fn apt_update_available(_param: Value) -> Result<Value, Error> { | |
33508b12 | 48 | |
b92cad09 FG |
49 | if let Ok(false) = apt::pkg_cache_expired() { |
50 | if let Ok(Some(cache)) = apt::read_pkg_state() { | |
51 | return Ok(json!(cache.package_status)); | |
52 | } | |
33508b12 TL |
53 | } |
54 | ||
55 | let cache = apt::update_cache()?; | |
56 | ||
38556bf6 | 57 | Ok(json!(cache.package_status)) |
a4e86972 SR |
58 | } |
59 | ||
440472cb DM |
60 | pub fn update_apt_proxy_config(proxy_config: Option<&ProxyConfig>) -> Result<(), Error> { |
61 | ||
62 | const PROXY_CFG_FN: &str = "/etc/apt/apt.conf.d/76pveproxy"; // use same file as PVE | |
63 | ||
64 | if let Some(proxy_config) = proxy_config { | |
65 | let proxy = proxy_config.to_proxy_string()?; | |
66 | let data = format!("Acquire::http::Proxy \"{}\";\n", proxy); | |
e9c2638f | 67 | replace_file(PROXY_CFG_FN, data.as_bytes(), CreateOptions::new()) |
440472cb | 68 | } else { |
e9c2638f TL |
69 | match std::fs::remove_file(PROXY_CFG_FN) { |
70 | Ok(()) => Ok(()), | |
71 | Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()), | |
72 | Err(err) => bail!("failed to remove proxy config '{}' - {}", PROXY_CFG_FN, err), | |
73 | } | |
440472cb | 74 | } |
440472cb DM |
75 | } |
76 | ||
77 | fn read_and_update_proxy_config() -> Result<Option<ProxyConfig>, Error> { | |
78 | let proxy_config = if let Ok((node_config, _digest)) = node::config() { | |
79 | node_config.http_proxy() | |
80 | } else { | |
81 | None | |
82 | }; | |
83 | update_apt_proxy_config(proxy_config.as_ref())?; | |
84 | ||
85 | Ok(proxy_config) | |
86 | } | |
87 | ||
b2825575 TL |
88 | fn do_apt_update(worker: &WorkerTask, quiet: bool) -> Result<(), Error> { |
89 | if !quiet { worker.log("starting apt-get update") } | |
90 | ||
440472cb | 91 | read_and_update_proxy_config()?; |
b2825575 TL |
92 | |
93 | let mut command = std::process::Command::new("apt-get"); | |
94 | command.arg("update"); | |
95 | ||
96 | // apt "errors" quite easily, and run_command is a bit rigid, so handle this inline for now. | |
97 | let output = command.output() | |
98 | .map_err(|err| format_err!("failed to execute {:?} - {}", command, err))?; | |
99 | ||
100 | if !quiet { | |
101 | worker.log(String::from_utf8(output.stdout)?); | |
102 | } | |
103 | ||
104 | // TODO: improve run_command to allow outputting both, stderr and stdout | |
105 | if !output.status.success() { | |
106 | if output.status.code().is_some() { | |
107 | let msg = String::from_utf8(output.stderr) | |
108 | .map(|m| if m.is_empty() { String::from("no error message") } else { m }) | |
109 | .unwrap_or_else(|_| String::from("non utf8 error message (suppressed)")); | |
110 | worker.warn(msg); | |
111 | } else { | |
112 | bail!("terminated by signal"); | |
113 | } | |
114 | } | |
115 | Ok(()) | |
116 | } | |
117 | ||
fa3f0584 | 118 | #[api( |
27fde647 | 119 | protected: true, |
fa3f0584 TL |
120 | input: { |
121 | properties: { | |
122 | node: { | |
123 | schema: NODE_SCHEMA, | |
124 | }, | |
86d60245 TL |
125 | notify: { |
126 | type: bool, | |
d1d74c43 | 127 | description: r#"Send notification mail about new package updates available to the |
86d60245 | 128 | email address configured for 'root@pam')."#, |
86d60245 | 129 | default: false, |
39735609 | 130 | optional: true, |
86d60245 | 131 | }, |
fa3f0584 TL |
132 | quiet: { |
133 | description: "Only produces output suitable for logging, omitting progress indicators.", | |
134 | type: bool, | |
135 | default: false, | |
136 | optional: true, | |
137 | }, | |
138 | }, | |
139 | }, | |
140 | returns: { | |
141 | schema: UPID_SCHEMA, | |
142 | }, | |
143 | access: { | |
144 | permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false), | |
145 | }, | |
146 | )] | |
147 | /// Update the APT database | |
148 | pub fn apt_update_database( | |
3e461dec FG |
149 | notify: bool, |
150 | quiet: bool, | |
fa3f0584 TL |
151 | rpcenv: &mut dyn RpcEnvironment, |
152 | ) -> Result<String, Error> { | |
153 | ||
e6dc35ac | 154 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
39735609 | 155 | let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI; |
fa3f0584 | 156 | |
e6dc35ac | 157 | let upid_str = WorkerTask::new_thread("aptupdate", None, auth_id, to_stdout, move |worker| { |
b2825575 | 158 | do_apt_update(&worker, quiet)?; |
86d60245 TL |
159 | |
160 | let mut cache = apt::update_cache()?; | |
161 | ||
162 | if notify { | |
163 | let mut notified = match cache.notified { | |
164 | Some(notified) => notified, | |
165 | None => std::collections::HashMap::new(), | |
166 | }; | |
167 | let mut to_notify: Vec<&APTUpdateInfo> = Vec::new(); | |
168 | ||
169 | for pkg in &cache.package_status { | |
170 | match notified.insert(pkg.package.to_owned(), pkg.version.to_owned()) { | |
171 | Some(notified_version) => { | |
172 | if notified_version != pkg.version { | |
173 | to_notify.push(pkg); | |
174 | } | |
175 | }, | |
176 | None => to_notify.push(pkg), | |
177 | } | |
178 | } | |
179 | if !to_notify.is_empty() { | |
0e16f57e | 180 | to_notify.sort_unstable_by_key(|k| &k.package); |
86d60245 TL |
181 | crate::server::send_updates_available(&to_notify)?; |
182 | } | |
183 | cache.notified = Some(notified); | |
184 | apt::write_pkg_cache(&cache)?; | |
185 | } | |
186 | ||
fa3f0584 TL |
187 | Ok(()) |
188 | })?; | |
189 | ||
190 | Ok(upid_str) | |
191 | } | |
192 | ||
9e61c01c | 193 | #[api( |
440472cb | 194 | protected: true, |
9e61c01c SR |
195 | input: { |
196 | properties: { | |
197 | node: { | |
198 | schema: NODE_SCHEMA, | |
199 | }, | |
200 | name: { | |
201 | description: "Package name to get changelog of.", | |
202 | type: String, | |
203 | }, | |
204 | version: { | |
205 | description: "Package version to get changelog of. Omit to use candidate version.", | |
206 | type: String, | |
207 | optional: true, | |
208 | }, | |
209 | }, | |
210 | }, | |
211 | returns: { | |
212 | schema: UPID_SCHEMA, | |
213 | }, | |
214 | access: { | |
215 | permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false), | |
216 | }, | |
217 | )] | |
218 | /// Retrieve the changelog of the specified package. | |
219 | fn apt_get_changelog( | |
220 | param: Value, | |
221 | ) -> Result<Value, Error> { | |
222 | ||
3c8c2827 | 223 | let name = pbs_tools::json::required_string_param(¶m, "name")?.to_owned(); |
9e61c01c SR |
224 | let version = param["version"].as_str(); |
225 | ||
e6513bd5 | 226 | let pkg_info = apt::list_installed_apt_packages(|data| { |
9e61c01c SR |
227 | match version { |
228 | Some(version) => version == data.active_version, | |
229 | None => data.active_version == data.candidate_version | |
230 | } | |
231 | }, Some(&name)); | |
232 | ||
3984a5fd | 233 | if pkg_info.is_empty() { |
9e61c01c SR |
234 | bail!("Package '{}' not found", name); |
235 | } | |
236 | ||
440472cb | 237 | let proxy_config = read_and_update_proxy_config()?; |
57889533 | 238 | let mut client = pbs_simple_http(proxy_config); |
26153589 | 239 | |
9e61c01c SR |
240 | let changelog_url = &pkg_info[0].change_log_url; |
241 | // FIXME: use 'apt-get changelog' for proxmox packages as well, once repo supports it | |
242 | if changelog_url.starts_with("http://download.proxmox.com/") { | |
d420962f | 243 | let changelog = pbs_runtime::block_on(client.get_string(changelog_url, None)) |
6eb41487 | 244 | .map_err(|err| format_err!("Error downloading changelog from '{}': {}", changelog_url, err))?; |
38556bf6 | 245 | Ok(json!(changelog)) |
137a6ebc SR |
246 | |
247 | } else if changelog_url.starts_with("https://enterprise.proxmox.com/") { | |
248 | let sub = match subscription::read_subscription()? { | |
249 | Some(sub) => sub, | |
250 | None => bail!("cannot retrieve changelog from enterprise repo: no subscription info found") | |
251 | }; | |
252 | let (key, id) = match sub.key { | |
253 | Some(key) => { | |
254 | match sub.serverid { | |
255 | Some(id) => (key, id), | |
256 | None => | |
257 | bail!("cannot retrieve changelog from enterprise repo: no server id found") | |
258 | } | |
259 | }, | |
260 | None => bail!("cannot retrieve changelog from enterprise repo: no subscription key found") | |
261 | }; | |
262 | ||
263 | let mut auth_header = HashMap::new(); | |
264 | auth_header.insert("Authorization".to_owned(), | |
265 | format!("Basic {}", base64::encode(format!("{}:{}", key, id)))); | |
266 | ||
d420962f | 267 | let changelog = pbs_runtime::block_on(client.get_string(changelog_url, Some(&auth_header))) |
137a6ebc | 268 | .map_err(|err| format_err!("Error downloading changelog from '{}': {}", changelog_url, err))?; |
38556bf6 | 269 | Ok(json!(changelog)) |
137a6ebc | 270 | |
9e61c01c SR |
271 | } else { |
272 | let mut command = std::process::Command::new("apt-get"); | |
273 | command.arg("changelog"); | |
274 | command.arg("-qq"); // don't display download progress | |
275 | command.arg(name); | |
276 | let output = crate::tools::run_command(command, None)?; | |
38556bf6 | 277 | Ok(json!(output)) |
9e61c01c SR |
278 | } |
279 | } | |
280 | ||
ed2beb33 TL |
281 | #[api( |
282 | input: { | |
283 | properties: { | |
284 | node: { | |
285 | schema: NODE_SCHEMA, | |
286 | }, | |
287 | }, | |
288 | }, | |
289 | returns: { | |
290 | description: "List of more relevant packages.", | |
291 | type: Array, | |
292 | items: { | |
293 | type: APTUpdateInfo, | |
294 | }, | |
295 | }, | |
296 | access: { | |
297 | permission: &Permission::Privilege(&[], PRIV_SYS_AUDIT, false), | |
298 | }, | |
299 | )] | |
300 | /// Get package information for important Proxmox Backup Server packages. | |
5e293f13 | 301 | pub fn get_versions() -> Result<Vec<APTUpdateInfo>, Error> { |
ed2beb33 TL |
302 | const PACKAGES: &[&str] = &[ |
303 | "ifupdown2", | |
304 | "libjs-extjs", | |
305 | "proxmox-backup", | |
306 | "proxmox-backup-docs", | |
307 | "proxmox-backup-client", | |
308 | "proxmox-backup-server", | |
309 | "proxmox-mini-journalreader", | |
310 | "proxmox-widget-toolkit", | |
311 | "pve-xtermjs", | |
312 | "smartmontools", | |
313 | "zfsutils-linux", | |
314 | ]; | |
315 | ||
2decf85d | 316 | fn unknown_package(package: String, extra_info: Option<String>) -> APTUpdateInfo { |
ed2beb33 TL |
317 | APTUpdateInfo { |
318 | package, | |
319 | title: "unknown".into(), | |
320 | arch: "unknown".into(), | |
321 | description: "unknown".into(), | |
322 | version: "unknown".into(), | |
323 | old_version: "unknown".into(), | |
324 | origin: "unknown".into(), | |
325 | priority: "unknown".into(), | |
326 | section: "unknown".into(), | |
327 | change_log_url: "unknown".into(), | |
2decf85d | 328 | extra_info, |
ed2beb33 TL |
329 | } |
330 | } | |
331 | ||
332 | let is_kernel = |name: &str| name.starts_with("pve-kernel-"); | |
333 | ||
334 | let mut packages: Vec<APTUpdateInfo> = Vec::new(); | |
335 | let pbs_packages = apt::list_installed_apt_packages( | |
336 | |filter| { | |
337 | filter.installed_version == Some(filter.active_version) | |
338 | && (is_kernel(filter.package) || PACKAGES.contains(&filter.package)) | |
339 | }, | |
340 | None, | |
341 | ); | |
2decf85d | 342 | |
51ac17b5 | 343 | let running_kernel = format!( |
8c62c15f | 344 | "running kernel: {}", |
51ac17b5 ML |
345 | nix::sys::utsname::uname().release().to_owned() |
346 | ); | |
bc1e52bc | 347 | if let Some(proxmox_backup) = pbs_packages.iter().find(|pkg| pkg.package == "proxmox-backup") { |
2decf85d | 348 | let mut proxmox_backup = proxmox_backup.clone(); |
51ac17b5 | 349 | proxmox_backup.extra_info = Some(running_kernel); |
2decf85d | 350 | packages.push(proxmox_backup); |
ed2beb33 | 351 | } else { |
bc1e52bc | 352 | packages.push(unknown_package("proxmox-backup".into(), Some(running_kernel))); |
ed2beb33 TL |
353 | } |
354 | ||
a12b1be7 WB |
355 | let version = pbs_buildcfg::PROXMOX_PKG_VERSION; |
356 | let release = pbs_buildcfg::PROXMOX_PKG_RELEASE; | |
e754da3a | 357 | let daemon_version_info = Some(format!("running version: {}.{}", version, release)); |
bc1e52bc | 358 | if let Some(pkg) = pbs_packages.iter().find(|pkg| pkg.package == "proxmox-backup-server") { |
2decf85d | 359 | let mut pkg = pkg.clone(); |
e754da3a | 360 | pkg.extra_info = daemon_version_info; |
2decf85d | 361 | packages.push(pkg); |
e754da3a TL |
362 | } else { |
363 | packages.push(unknown_package("proxmox-backup".into(), daemon_version_info)); | |
ed2beb33 TL |
364 | } |
365 | ||
366 | let mut kernel_pkgs: Vec<APTUpdateInfo> = pbs_packages | |
367 | .iter() | |
368 | .filter(|pkg| is_kernel(&pkg.package)) | |
369 | .cloned() | |
370 | .collect(); | |
371 | // make sure the cache mutex gets dropped before the next call to list_installed_apt_packages | |
372 | { | |
373 | let cache = apt_pkg_native::Cache::get_singleton(); | |
374 | kernel_pkgs.sort_by(|left, right| { | |
375 | cache | |
376 | .compare_versions(&left.old_version, &right.old_version) | |
377 | .reverse() | |
378 | }); | |
379 | } | |
380 | packages.append(&mut kernel_pkgs); | |
381 | ||
382 | // add entry for all packages we're interested in, even if not installed | |
383 | for pkg in PACKAGES.iter() { | |
384 | if pkg == &"proxmox-backup" || pkg == &"proxmox-backup-server" { | |
385 | continue; | |
386 | } | |
387 | match pbs_packages.iter().find(|item| &item.package == pkg) { | |
388 | Some(apt_pkg) => packages.push(apt_pkg.to_owned()), | |
2decf85d | 389 | None => packages.push(unknown_package(pkg.to_string(), None)), |
ed2beb33 TL |
390 | } |
391 | } | |
392 | ||
5e293f13 | 393 | Ok(packages) |
ed2beb33 TL |
394 | } |
395 | ||
d830804f FE |
396 | #[api( |
397 | input: { | |
398 | properties: { | |
399 | node: { | |
400 | schema: NODE_SCHEMA, | |
401 | }, | |
402 | }, | |
403 | }, | |
404 | returns: { | |
405 | type: Object, | |
406 | description: "Result from parsing the APT repository files in /etc/apt/.", | |
407 | properties: { | |
408 | files: { | |
409 | description: "List of parsed repository files.", | |
410 | type: Array, | |
411 | items: { | |
412 | type: APTRepositoryFile, | |
413 | }, | |
414 | }, | |
415 | errors: { | |
416 | description: "List of problematic files.", | |
417 | type: Array, | |
418 | items: { | |
419 | type: APTRepositoryFileError, | |
420 | }, | |
421 | }, | |
422 | digest: { | |
423 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
424 | }, | |
425 | infos: { | |
426 | description: "List of additional information/warnings about the repositories.", | |
427 | items: { | |
428 | type: APTRepositoryInfo, | |
429 | }, | |
430 | }, | |
431 | "standard-repos": { | |
432 | description: "List of standard repositories and their configuration status.", | |
433 | items: { | |
434 | type: APTStandardRepository, | |
435 | }, | |
436 | }, | |
437 | }, | |
438 | }, | |
439 | access: { | |
440 | permission: &Permission::Privilege(&[], PRIV_SYS_AUDIT, false), | |
441 | }, | |
442 | )] | |
443 | /// Get APT repository information. | |
444 | pub fn get_repositories() -> Result<Value, Error> { | |
445 | let (files, errors, digest) = proxmox_apt::repositories::repositories()?; | |
446 | let digest = proxmox::tools::digest_to_hex(&digest); | |
447 | ||
2eac3594 FE |
448 | let suite = proxmox_apt::repositories::get_current_release_codename()?; |
449 | ||
0b12a5a6 FE |
450 | let infos = proxmox_apt::repositories::check_repositories(&files, suite); |
451 | let standard_repos = proxmox_apt::repositories::standard_repositories(&files, "pbs", suite); | |
d830804f FE |
452 | |
453 | Ok(json!({ | |
454 | "files": files, | |
455 | "errors": errors, | |
456 | "digest": digest, | |
457 | "infos": infos, | |
458 | "standard-repos": standard_repos, | |
459 | })) | |
460 | } | |
461 | ||
289738dc FE |
462 | #[api( |
463 | input: { | |
464 | properties: { | |
465 | node: { | |
466 | schema: NODE_SCHEMA, | |
467 | }, | |
468 | handle: { | |
469 | type: APTRepositoryHandle, | |
470 | }, | |
471 | digest: { | |
472 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
473 | optional: true, | |
474 | }, | |
475 | }, | |
476 | }, | |
477 | protected: true, | |
478 | access: { | |
479 | permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false), | |
480 | }, | |
481 | )] | |
482 | /// Add the repository identified by the `handle`. | |
483 | /// If the repository is already configured, it will be set to enabled. | |
484 | /// | |
485 | /// The `digest` parameter asserts that the configuration has not been modified. | |
486 | pub fn add_repository(handle: APTRepositoryHandle, digest: Option<String>) -> Result<(), Error> { | |
487 | let (mut files, errors, current_digest) = proxmox_apt::repositories::repositories()?; | |
488 | ||
2eac3594 FE |
489 | let suite = proxmox_apt::repositories::get_current_release_codename()?; |
490 | ||
289738dc FE |
491 | if let Some(expected_digest) = digest { |
492 | let current_digest = proxmox::tools::digest_to_hex(¤t_digest); | |
493 | crate::tools::assert_if_modified(&expected_digest, ¤t_digest)?; | |
494 | } | |
495 | ||
496 | // check if it's already configured first | |
497 | for file in files.iter_mut() { | |
498 | for repo in file.repositories.iter_mut() { | |
0b12a5a6 | 499 | if repo.is_referenced_repository(handle, "pbs", &suite.to_string()) { |
289738dc FE |
500 | if repo.enabled { |
501 | return Ok(()); | |
502 | } | |
503 | ||
504 | repo.set_enabled(true); | |
505 | file.write()?; | |
506 | ||
507 | return Ok(()); | |
508 | } | |
509 | } | |
510 | } | |
511 | ||
0b12a5a6 | 512 | let (repo, path) = proxmox_apt::repositories::get_standard_repository(handle, "pbs", suite); |
289738dc FE |
513 | |
514 | if let Some(error) = errors.iter().find(|error| error.path == path) { | |
515 | bail!( | |
516 | "unable to parse existing file {} - {}", | |
517 | error.path, | |
518 | error.error, | |
519 | ); | |
520 | } | |
521 | ||
522 | if let Some(file) = files.iter_mut().find(|file| file.path == path) { | |
523 | file.repositories.push(repo); | |
524 | ||
525 | file.write()?; | |
526 | } else { | |
527 | let mut file = match APTRepositoryFile::new(&path)? { | |
528 | Some(file) => file, | |
529 | None => bail!("invalid path - {}", path), | |
530 | }; | |
531 | ||
532 | file.repositories.push(repo); | |
533 | ||
534 | file.write()?; | |
535 | } | |
536 | ||
537 | Ok(()) | |
538 | } | |
539 | ||
540 | #[api( | |
541 | input: { | |
542 | properties: { | |
543 | node: { | |
544 | schema: NODE_SCHEMA, | |
545 | }, | |
546 | path: { | |
547 | description: "Path to the containing file.", | |
548 | type: String, | |
549 | }, | |
550 | index: { | |
551 | description: "Index within the file (starting from 0).", | |
552 | type: usize, | |
553 | }, | |
554 | enabled: { | |
555 | description: "Whether the repository should be enabled or not.", | |
556 | type: bool, | |
557 | optional: true, | |
558 | }, | |
559 | digest: { | |
560 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
561 | optional: true, | |
562 | }, | |
563 | }, | |
564 | }, | |
565 | protected: true, | |
566 | access: { | |
567 | permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false), | |
568 | }, | |
569 | )] | |
570 | /// Change the properties of the specified repository. | |
571 | /// | |
572 | /// The `digest` parameter asserts that the configuration has not been modified. | |
573 | pub fn change_repository( | |
574 | path: String, | |
575 | index: usize, | |
576 | enabled: Option<bool>, | |
577 | digest: Option<String>, | |
578 | ) -> Result<(), Error> { | |
579 | let (mut files, errors, current_digest) = proxmox_apt::repositories::repositories()?; | |
580 | ||
581 | if let Some(expected_digest) = digest { | |
582 | let current_digest = proxmox::tools::digest_to_hex(¤t_digest); | |
583 | crate::tools::assert_if_modified(&expected_digest, ¤t_digest)?; | |
584 | } | |
585 | ||
586 | if let Some(error) = errors.iter().find(|error| error.path == path) { | |
587 | bail!("unable to parse file {} - {}", error.path, error.error); | |
588 | } | |
589 | ||
590 | if let Some(file) = files.iter_mut().find(|file| file.path == path) { | |
591 | if let Some(repo) = file.repositories.get_mut(index) { | |
592 | if let Some(enabled) = enabled { | |
593 | repo.set_enabled(enabled); | |
594 | } | |
595 | ||
596 | file.write()?; | |
597 | } else { | |
598 | bail!("invalid index - {}", index); | |
599 | } | |
600 | } else { | |
601 | bail!("invalid path - {}", path); | |
602 | } | |
603 | ||
604 | Ok(()) | |
605 | } | |
606 | ||
a4e86972 | 607 | const SUBDIRS: SubdirMap = &[ |
9e61c01c | 608 | ("changelog", &Router::new().get(&API_METHOD_APT_GET_CHANGELOG)), |
289738dc FE |
609 | ("repositories", &Router::new() |
610 | .get(&API_METHOD_GET_REPOSITORIES) | |
611 | .post(&API_METHOD_CHANGE_REPOSITORY) | |
612 | .put(&API_METHOD_ADD_REPOSITORY) | |
613 | ), | |
fa3f0584 TL |
614 | ("update", &Router::new() |
615 | .get(&API_METHOD_APT_UPDATE_AVAILABLE) | |
616 | .post(&API_METHOD_APT_UPDATE_DATABASE) | |
617 | ), | |
ed2beb33 | 618 | ("versions", &Router::new().get(&API_METHOD_GET_VERSIONS)), |
a4e86972 SR |
619 | ]; |
620 | ||
621 | pub const ROUTER: Router = Router::new() | |
622 | .get(&list_subdirs_api_method!(SUBDIRS)) | |
623 | .subdirs(SUBDIRS); |