]>
Commit | Line | Data |
---|---|---|
9e61c01c | 1 | use anyhow::{Error, bail, format_err}; |
a4e86972 SR |
2 | use serde_json::{json, Value}; |
3 | ||
e6513bd5 | 4 | use proxmox::list_subdirs_api_method; |
fa3f0584 TL |
5 | use proxmox::api::{api, RpcEnvironment, RpcEnvironmentType, Permission}; |
6 | use proxmox::api::router::{Router, SubdirMap}; | |
a4e86972 | 7 | |
fa3f0584 | 8 | use crate::server::WorkerTask; |
e6513bd5 | 9 | use crate::tools::{apt, http}; |
fa3f0584 TL |
10 | |
11 | use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; | |
e6dc35ac | 12 | use crate::api2::types::{Authid, APTUpdateInfo, NODE_SCHEMA, UPID_SCHEMA}; |
a4e86972 | 13 | |
a4e86972 SR |
14 | #[api( |
15 | input: { | |
16 | properties: { | |
17 | node: { | |
18 | schema: NODE_SCHEMA, | |
19 | }, | |
20 | }, | |
21 | }, | |
22 | returns: { | |
23 | description: "A list of packages with available updates.", | |
24 | type: Array, | |
e6513bd5 TL |
25 | items: { |
26 | type: APTUpdateInfo | |
27 | }, | |
a4e86972 | 28 | }, |
33508b12 | 29 | protected: true, |
a4e86972 SR |
30 | access: { |
31 | permission: &Permission::Privilege(&[], PRIV_SYS_AUDIT, false), | |
32 | }, | |
33 | )] | |
34 | /// List available APT updates | |
35 | fn apt_update_available(_param: Value) -> Result<Value, Error> { | |
33508b12 TL |
36 | |
37 | match apt::pkg_cache_expired() { | |
38 | Ok(false) => { | |
39 | if let Ok(Some(cache)) = apt::read_pkg_state() { | |
40 | return Ok(json!(cache.package_status)); | |
41 | } | |
42 | }, | |
43 | _ => (), | |
44 | } | |
45 | ||
46 | let cache = apt::update_cache()?; | |
47 | ||
48 | return Ok(json!(cache.package_status)); | |
a4e86972 SR |
49 | } |
50 | ||
b2825575 TL |
51 | fn do_apt_update(worker: &WorkerTask, quiet: bool) -> Result<(), Error> { |
52 | if !quiet { worker.log("starting apt-get update") } | |
53 | ||
54 | // TODO: set proxy /etc/apt/apt.conf.d/76pbsproxy like PVE | |
55 | ||
56 | let mut command = std::process::Command::new("apt-get"); | |
57 | command.arg("update"); | |
58 | ||
59 | // apt "errors" quite easily, and run_command is a bit rigid, so handle this inline for now. | |
60 | let output = command.output() | |
61 | .map_err(|err| format_err!("failed to execute {:?} - {}", command, err))?; | |
62 | ||
63 | if !quiet { | |
64 | worker.log(String::from_utf8(output.stdout)?); | |
65 | } | |
66 | ||
67 | // TODO: improve run_command to allow outputting both, stderr and stdout | |
68 | if !output.status.success() { | |
69 | if output.status.code().is_some() { | |
70 | let msg = String::from_utf8(output.stderr) | |
71 | .map(|m| if m.is_empty() { String::from("no error message") } else { m }) | |
72 | .unwrap_or_else(|_| String::from("non utf8 error message (suppressed)")); | |
73 | worker.warn(msg); | |
74 | } else { | |
75 | bail!("terminated by signal"); | |
76 | } | |
77 | } | |
78 | Ok(()) | |
79 | } | |
80 | ||
fa3f0584 | 81 | #[api( |
27fde647 | 82 | protected: true, |
fa3f0584 TL |
83 | input: { |
84 | properties: { | |
85 | node: { | |
86 | schema: NODE_SCHEMA, | |
87 | }, | |
86d60245 TL |
88 | notify: { |
89 | type: bool, | |
90 | description: r#"Send notification mail about new package updates availanle to the | |
91 | email address configured for 'root@pam')."#, | |
92 | optional: true, | |
93 | default: false, | |
94 | }, | |
fa3f0584 TL |
95 | quiet: { |
96 | description: "Only produces output suitable for logging, omitting progress indicators.", | |
97 | type: bool, | |
98 | default: false, | |
99 | optional: true, | |
100 | }, | |
101 | }, | |
102 | }, | |
103 | returns: { | |
104 | schema: UPID_SCHEMA, | |
105 | }, | |
106 | access: { | |
107 | permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false), | |
108 | }, | |
109 | )] | |
110 | /// Update the APT database | |
111 | pub fn apt_update_database( | |
86d60245 | 112 | notify: Option<bool>, |
fa3f0584 TL |
113 | quiet: Option<bool>, |
114 | rpcenv: &mut dyn RpcEnvironment, | |
115 | ) -> Result<String, Error> { | |
116 | ||
e6dc35ac | 117 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
fa3f0584 | 118 | let to_stdout = if rpcenv.env_type() == RpcEnvironmentType::CLI { true } else { false }; |
86d60245 | 119 | // FIXME: change to non-option in signature and drop below once we have proxmox-api-macro 0.2.3 |
98b17337 | 120 | let quiet = quiet.unwrap_or(API_METHOD_APT_UPDATE_DATABASE_PARAM_DEFAULT_QUIET); |
86d60245 | 121 | let notify = notify.unwrap_or(API_METHOD_APT_UPDATE_DATABASE_PARAM_DEFAULT_NOTIFY); |
fa3f0584 | 122 | |
e6dc35ac | 123 | let upid_str = WorkerTask::new_thread("aptupdate", None, auth_id, to_stdout, move |worker| { |
b2825575 | 124 | do_apt_update(&worker, quiet)?; |
86d60245 TL |
125 | |
126 | let mut cache = apt::update_cache()?; | |
127 | ||
128 | if notify { | |
129 | let mut notified = match cache.notified { | |
130 | Some(notified) => notified, | |
131 | None => std::collections::HashMap::new(), | |
132 | }; | |
133 | let mut to_notify: Vec<&APTUpdateInfo> = Vec::new(); | |
134 | ||
135 | for pkg in &cache.package_status { | |
136 | match notified.insert(pkg.package.to_owned(), pkg.version.to_owned()) { | |
137 | Some(notified_version) => { | |
138 | if notified_version != pkg.version { | |
139 | to_notify.push(pkg); | |
140 | } | |
141 | }, | |
142 | None => to_notify.push(pkg), | |
143 | } | |
144 | } | |
145 | if !to_notify.is_empty() { | |
0e16f57e | 146 | to_notify.sort_unstable_by_key(|k| &k.package); |
86d60245 TL |
147 | crate::server::send_updates_available(&to_notify)?; |
148 | } | |
149 | cache.notified = Some(notified); | |
150 | apt::write_pkg_cache(&cache)?; | |
151 | } | |
152 | ||
fa3f0584 TL |
153 | Ok(()) |
154 | })?; | |
155 | ||
156 | Ok(upid_str) | |
157 | } | |
158 | ||
9e61c01c SR |
159 | #[api( |
160 | input: { | |
161 | properties: { | |
162 | node: { | |
163 | schema: NODE_SCHEMA, | |
164 | }, | |
165 | name: { | |
166 | description: "Package name to get changelog of.", | |
167 | type: String, | |
168 | }, | |
169 | version: { | |
170 | description: "Package version to get changelog of. Omit to use candidate version.", | |
171 | type: String, | |
172 | optional: true, | |
173 | }, | |
174 | }, | |
175 | }, | |
176 | returns: { | |
177 | schema: UPID_SCHEMA, | |
178 | }, | |
179 | access: { | |
180 | permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false), | |
181 | }, | |
182 | )] | |
183 | /// Retrieve the changelog of the specified package. | |
184 | fn apt_get_changelog( | |
185 | param: Value, | |
186 | ) -> Result<Value, Error> { | |
187 | ||
188 | let name = crate::tools::required_string_param(¶m, "name")?.to_owned(); | |
189 | let version = param["version"].as_str(); | |
190 | ||
e6513bd5 | 191 | let pkg_info = apt::list_installed_apt_packages(|data| { |
9e61c01c SR |
192 | match version { |
193 | Some(version) => version == data.active_version, | |
194 | None => data.active_version == data.candidate_version | |
195 | } | |
196 | }, Some(&name)); | |
197 | ||
198 | if pkg_info.len() == 0 { | |
199 | bail!("Package '{}' not found", name); | |
200 | } | |
201 | ||
202 | let changelog_url = &pkg_info[0].change_log_url; | |
203 | // FIXME: use 'apt-get changelog' for proxmox packages as well, once repo supports it | |
204 | if changelog_url.starts_with("http://download.proxmox.com/") { | |
205 | let changelog = crate::tools::runtime::block_on(http::get_string(changelog_url)) | |
6eb41487 | 206 | .map_err(|err| format_err!("Error downloading changelog from '{}': {}", changelog_url, err))?; |
9e61c01c SR |
207 | return Ok(json!(changelog)); |
208 | } else { | |
209 | let mut command = std::process::Command::new("apt-get"); | |
210 | command.arg("changelog"); | |
211 | command.arg("-qq"); // don't display download progress | |
212 | command.arg(name); | |
213 | let output = crate::tools::run_command(command, None)?; | |
214 | return Ok(json!(output)); | |
215 | } | |
216 | } | |
217 | ||
a4e86972 | 218 | const SUBDIRS: SubdirMap = &[ |
9e61c01c | 219 | ("changelog", &Router::new().get(&API_METHOD_APT_GET_CHANGELOG)), |
fa3f0584 TL |
220 | ("update", &Router::new() |
221 | .get(&API_METHOD_APT_UPDATE_AVAILABLE) | |
222 | .post(&API_METHOD_APT_UPDATE_DATABASE) | |
223 | ), | |
a4e86972 SR |
224 | ]; |
225 | ||
226 | pub const ROUTER: Router = Router::new() | |
227 | .get(&list_subdirs_api_method!(SUBDIRS)) | |
228 | .subdirs(SUBDIRS); |