]>
Commit | Line | Data |
---|---|---|
9b2bad7a HL |
1 | use anyhow::{bail, Error}; |
2 | use serde_json::Value; | |
3 | use ::serde::{Deserialize, Serialize}; | |
4 | ||
b7ce2e57 | 5 | use proxmox::api::{api, Permission, Router, RpcEnvironment}; |
9b2bad7a | 6 | |
e3619d41 DM |
7 | use pbs_api_types::{ |
8 | Authid, VerificationJobConfig, | |
9 | SINGLE_LINE_COMMENT_SCHEMA, JOB_ID_SCHEMA, IGNORE_VERIFIED_BACKUPS_SCHEMA, | |
10 | VERIFICATION_OUTDATED_AFTER_SCHEMA, VERIFICATION_SCHEDULE_SCHEMA, | |
11 | DATASTORE_SCHEMA, PROXMOX_CONFIG_DIGEST_SCHEMA, | |
12 | }; | |
802189f7 | 13 | use pbs_config::verify; |
b7ce2e57 FG |
14 | |
15 | use crate::config::acl::{ | |
16 | PRIV_DATASTORE_AUDIT, | |
b7ce2e57 FG |
17 | PRIV_DATASTORE_VERIFY, |
18 | }; | |
19 | ||
35c80d69 | 20 | use crate::config::cached_user_info::CachedUserInfo; |
9b2bad7a HL |
21 | |
22 | #[api( | |
23 | input: { | |
24 | properties: {}, | |
25 | }, | |
26 | returns: { | |
27 | description: "List configured jobs.", | |
28 | type: Array, | |
e3619d41 | 29 | items: { type: VerificationJobConfig }, |
9b2bad7a | 30 | }, |
b7ce2e57 | 31 | access: { |
35c80d69 FG |
32 | permission: &Permission::Anybody, |
33 | description: "Requires Datastore.Audit or Datastore.Verify on datastore.", | |
b7ce2e57 | 34 | }, |
9b2bad7a HL |
35 | )] |
36 | /// List all verification jobs | |
37 | pub fn list_verification_jobs( | |
38 | _param: Value, | |
39 | mut rpcenv: &mut dyn RpcEnvironment, | |
40 | ) -> Result<Vec<VerificationJobConfig>, Error> { | |
35c80d69 FG |
41 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
42 | let user_info = CachedUserInfo::new()?; | |
43 | ||
44 | let required_privs = PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_VERIFY; | |
9b2bad7a HL |
45 | |
46 | let (config, digest) = verify::config()?; | |
47 | ||
48 | let list = config.convert_to_typed_array("verification")?; | |
49 | ||
35c80d69 FG |
50 | let list = list.into_iter() |
51 | .filter(|job: &VerificationJobConfig| { | |
52 | let privs = user_info.lookup_privs(&auth_id, &["datastore", &job.store]); | |
53 | ||
54 | privs & required_privs != 00 | |
55 | }).collect(); | |
56 | ||
9b2bad7a HL |
57 | rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); |
58 | ||
59 | Ok(list) | |
60 | } | |
61 | ||
62 | ||
63 | #[api( | |
64 | protected: true, | |
65 | input: { | |
66 | properties: { | |
67 | id: { | |
68 | schema: JOB_ID_SCHEMA, | |
69 | }, | |
70 | store: { | |
71 | schema: DATASTORE_SCHEMA, | |
72 | }, | |
73 | "ignore-verified": { | |
74 | optional: true, | |
75 | schema: IGNORE_VERIFIED_BACKUPS_SCHEMA, | |
76 | }, | |
77 | "outdated-after": { | |
78 | optional: true, | |
79 | schema: VERIFICATION_OUTDATED_AFTER_SCHEMA, | |
80 | }, | |
81 | comment: { | |
82 | optional: true, | |
83 | schema: SINGLE_LINE_COMMENT_SCHEMA, | |
84 | }, | |
85 | schedule: { | |
86 | optional: true, | |
87 | schema: VERIFICATION_SCHEDULE_SCHEMA, | |
88 | }, | |
89 | } | |
b7ce2e57 FG |
90 | }, |
91 | access: { | |
35c80d69 FG |
92 | permission: &Permission::Anybody, |
93 | description: "Requires Datastore.Verify on job's datastore.", | |
b7ce2e57 | 94 | }, |
9b2bad7a HL |
95 | )] |
96 | /// Create a new verification job. | |
35c80d69 FG |
97 | pub fn create_verification_job( |
98 | param: Value, | |
99 | rpcenv: &mut dyn RpcEnvironment | |
100 | ) -> Result<(), Error> { | |
101 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; | |
102 | let user_info = CachedUserInfo::new()?; | |
9b2bad7a | 103 | |
e3619d41 | 104 | let verification_job: VerificationJobConfig = serde_json::from_value(param)?; |
9b2bad7a | 105 | |
35c80d69 FG |
106 | user_info.check_privs(&auth_id, &["datastore", &verification_job.store], PRIV_DATASTORE_VERIFY, false)?; |
107 | ||
802189f7 | 108 | let _lock = verify::lock_config()?; |
35c80d69 | 109 | |
9b2bad7a HL |
110 | let (mut config, _digest) = verify::config()?; |
111 | ||
3984a5fd | 112 | if config.sections.get(&verification_job.id).is_some() { |
9b2bad7a HL |
113 | bail!("job '{}' already exists.", verification_job.id); |
114 | } | |
115 | ||
116 | config.set_data(&verification_job.id, "verification", &verification_job)?; | |
117 | ||
118 | verify::save_config(&config)?; | |
119 | ||
1298618a | 120 | crate::server::jobstate::create_state_file("verificationjob", &verification_job.id)?; |
9b2bad7a HL |
121 | |
122 | Ok(()) | |
123 | } | |
124 | ||
125 | #[api( | |
126 | input: { | |
127 | properties: { | |
128 | id: { | |
129 | schema: JOB_ID_SCHEMA, | |
130 | }, | |
131 | }, | |
132 | }, | |
e3619d41 | 133 | returns: { type: VerificationJobConfig }, |
b7ce2e57 | 134 | access: { |
35c80d69 FG |
135 | permission: &Permission::Anybody, |
136 | description: "Requires Datastore.Audit or Datastore.Verify on job's datastore.", | |
b7ce2e57 | 137 | }, |
9b2bad7a HL |
138 | )] |
139 | /// Read a verification job configuration. | |
140 | pub fn read_verification_job( | |
141 | id: String, | |
142 | mut rpcenv: &mut dyn RpcEnvironment, | |
143 | ) -> Result<VerificationJobConfig, Error> { | |
35c80d69 FG |
144 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
145 | let user_info = CachedUserInfo::new()?; | |
146 | ||
9b2bad7a HL |
147 | let (config, digest) = verify::config()?; |
148 | ||
e3619d41 | 149 | let verification_job: VerificationJobConfig = config.lookup("verification", &id)?; |
35c80d69 FG |
150 | |
151 | let required_privs = PRIV_DATASTORE_AUDIT | PRIV_DATASTORE_VERIFY; | |
152 | user_info.check_privs(&auth_id, &["datastore", &verification_job.store], required_privs, true)?; | |
153 | ||
9b2bad7a HL |
154 | rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); |
155 | ||
156 | Ok(verification_job) | |
157 | } | |
158 | ||
159 | #[api()] | |
160 | #[derive(Serialize, Deserialize)] | |
161 | #[serde(rename_all="kebab-case")] | |
162 | /// Deletable property name | |
163 | pub enum DeletableProperty { | |
164 | /// Delete the ignore verified property. | |
165 | IgnoreVerified, | |
166 | /// Delete the comment property. | |
167 | Comment, | |
168 | /// Delete the job schedule. | |
169 | Schedule, | |
170 | /// Delete outdated after property. | |
171 | OutdatedAfter | |
172 | } | |
173 | ||
174 | #[api( | |
175 | protected: true, | |
176 | input: { | |
177 | properties: { | |
178 | id: { | |
179 | schema: JOB_ID_SCHEMA, | |
180 | }, | |
181 | store: { | |
182 | optional: true, | |
183 | schema: DATASTORE_SCHEMA, | |
184 | }, | |
185 | "ignore-verified": { | |
186 | optional: true, | |
187 | schema: IGNORE_VERIFIED_BACKUPS_SCHEMA, | |
188 | }, | |
189 | "outdated-after": { | |
190 | optional: true, | |
191 | schema: VERIFICATION_OUTDATED_AFTER_SCHEMA, | |
192 | }, | |
193 | comment: { | |
194 | optional: true, | |
195 | schema: SINGLE_LINE_COMMENT_SCHEMA, | |
196 | }, | |
197 | schedule: { | |
198 | optional: true, | |
199 | schema: VERIFICATION_SCHEDULE_SCHEMA, | |
200 | }, | |
201 | delete: { | |
202 | description: "List of properties to delete.", | |
203 | type: Array, | |
204 | optional: true, | |
205 | items: { | |
206 | type: DeletableProperty, | |
207 | } | |
208 | }, | |
209 | digest: { | |
210 | optional: true, | |
211 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
212 | }, | |
213 | }, | |
214 | }, | |
b7ce2e57 | 215 | access: { |
35c80d69 FG |
216 | permission: &Permission::Anybody, |
217 | description: "Requires Datastore.Verify on job's datastore.", | |
b7ce2e57 | 218 | }, |
9b2bad7a HL |
219 | )] |
220 | /// Update verification job config. | |
367c0ff7 | 221 | #[allow(clippy::too_many_arguments)] |
9b2bad7a HL |
222 | pub fn update_verification_job( |
223 | id: String, | |
224 | store: Option<String>, | |
225 | ignore_verified: Option<bool>, | |
226 | outdated_after: Option<i64>, | |
227 | comment: Option<String>, | |
228 | schedule: Option<String>, | |
229 | delete: Option<Vec<DeletableProperty>>, | |
230 | digest: Option<String>, | |
35c80d69 | 231 | rpcenv: &mut dyn RpcEnvironment, |
9b2bad7a | 232 | ) -> Result<(), Error> { |
35c80d69 FG |
233 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
234 | let user_info = CachedUserInfo::new()?; | |
9b2bad7a | 235 | |
802189f7 | 236 | let _lock = verify::lock_config()?; |
9b2bad7a HL |
237 | |
238 | // pass/compare digest | |
239 | let (mut config, expected_digest) = verify::config()?; | |
240 | ||
241 | if let Some(ref digest) = digest { | |
242 | let digest = proxmox::tools::hex_to_digest(digest)?; | |
243 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; | |
244 | } | |
245 | ||
e3619d41 | 246 | let mut data: VerificationJobConfig = config.lookup("verification", &id)?; |
9b2bad7a | 247 | |
35c80d69 FG |
248 | // check existing store |
249 | user_info.check_privs(&auth_id, &["datastore", &data.store], PRIV_DATASTORE_VERIFY, true)?; | |
250 | ||
251 | if let Some(delete) = delete { | |
9b2bad7a HL |
252 | for delete_prop in delete { |
253 | match delete_prop { | |
254 | DeletableProperty::IgnoreVerified => { data.ignore_verified = None; }, | |
255 | DeletableProperty::OutdatedAfter => { data.outdated_after = None; }, | |
256 | DeletableProperty::Comment => { data.comment = None; }, | |
257 | DeletableProperty::Schedule => { data.schedule = None; }, | |
258 | } | |
259 | } | |
260 | } | |
261 | ||
262 | if let Some(comment) = comment { | |
263 | let comment = comment.trim().to_string(); | |
264 | if comment.is_empty() { | |
265 | data.comment = None; | |
266 | } else { | |
267 | data.comment = Some(comment); | |
268 | } | |
269 | } | |
270 | ||
35c80d69 FG |
271 | if let Some(store) = store { |
272 | // check new store | |
273 | user_info.check_privs(&auth_id, &["datastore", &store], PRIV_DATASTORE_VERIFY, true)?; | |
274 | data.store = store; | |
275 | } | |
276 | ||
9b2bad7a HL |
277 | |
278 | if ignore_verified.is_some() { data.ignore_verified = ignore_verified; } | |
279 | if outdated_after.is_some() { data.outdated_after = outdated_after; } | |
951fe0cb | 280 | let schedule_changed = data.schedule != schedule; |
9b2bad7a HL |
281 | if schedule.is_some() { data.schedule = schedule; } |
282 | ||
283 | config.set_data(&id, "verification", &data)?; | |
284 | ||
285 | verify::save_config(&config)?; | |
286 | ||
951fe0cb | 287 | if schedule_changed { |
37a634f5 | 288 | crate::server::jobstate::update_job_last_run_time("verificationjob", &id)?; |
951fe0cb DC |
289 | } |
290 | ||
9b2bad7a HL |
291 | Ok(()) |
292 | } | |
293 | ||
294 | #[api( | |
295 | protected: true, | |
296 | input: { | |
297 | properties: { | |
298 | id: { | |
299 | schema: JOB_ID_SCHEMA, | |
300 | }, | |
301 | digest: { | |
302 | optional: true, | |
303 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
304 | }, | |
305 | }, | |
306 | }, | |
b7ce2e57 | 307 | access: { |
35c80d69 FG |
308 | permission: &Permission::Anybody, |
309 | description: "Requires Datastore.Verify on job's datastore.", | |
b7ce2e57 | 310 | }, |
9b2bad7a HL |
311 | )] |
312 | /// Remove a verification job configuration | |
35c80d69 FG |
313 | pub fn delete_verification_job( |
314 | id: String, | |
315 | digest: Option<String>, | |
316 | rpcenv: &mut dyn RpcEnvironment, | |
317 | ) -> Result<(), Error> { | |
318 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; | |
319 | let user_info = CachedUserInfo::new()?; | |
9b2bad7a | 320 | |
802189f7 | 321 | let _lock = verify::lock_config()?; |
9b2bad7a HL |
322 | |
323 | let (mut config, expected_digest) = verify::config()?; | |
324 | ||
e3619d41 | 325 | let job: VerificationJobConfig = config.lookup("verification", &id)?; |
35c80d69 FG |
326 | user_info.check_privs(&auth_id, &["datastore", &job.store], PRIV_DATASTORE_VERIFY, true)?; |
327 | ||
9b2bad7a HL |
328 | if let Some(ref digest) = digest { |
329 | let digest = proxmox::tools::hex_to_digest(digest)?; | |
330 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; | |
331 | } | |
332 | ||
333 | match config.sections.get(&id) { | |
334 | Some(_) => { config.sections.remove(&id); }, | |
335 | None => bail!("job '{}' does not exist.", id), | |
336 | } | |
337 | ||
338 | verify::save_config(&config)?; | |
339 | ||
1298618a | 340 | crate::server::jobstate::remove_state_file("verificationjob", &id)?; |
9b2bad7a HL |
341 | |
342 | Ok(()) | |
343 | } | |
344 | ||
345 | const ITEM_ROUTER: Router = Router::new() | |
346 | .get(&API_METHOD_READ_VERIFICATION_JOB) | |
347 | .put(&API_METHOD_UPDATE_VERIFICATION_JOB) | |
348 | .delete(&API_METHOD_DELETE_VERIFICATION_JOB); | |
349 | ||
350 | pub const ROUTER: Router = Router::new() | |
351 | .get(&API_METHOD_LIST_VERIFICATION_JOBS) | |
352 | .post(&API_METHOD_CREATE_VERIFICATION_JOB) | |
1298618a | 353 | .match_all("id", &ITEM_ROUTER); |