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