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