]>
Commit | Line | Data |
---|---|---|
a2479cfa | 1 | use std::path::PathBuf; |
6ce50400 | 2 | |
f7d4e4b5 | 3 | use anyhow::{bail, Error}; |
5e62d19c | 4 | use serde_json::Value; |
0a00f6e0 | 5 | use ::serde::{Deserialize, Serialize}; |
6ce50400 | 6 | |
67f7ffd0 | 7 | use proxmox::api::{api, Router, RpcEnvironment, Permission}; |
4708f4fc | 8 | use proxmox::api::section_config::SectionConfigData; |
c26c9390 | 9 | use proxmox::api::schema::parse_property_string; |
a2479cfa | 10 | |
66c49c21 | 11 | use crate::api2::types::*; |
a2479cfa | 12 | use crate::backup::*; |
b93bbab4 | 13 | use crate::config::cached_user_info::CachedUserInfo; |
67f7ffd0 | 14 | use crate::config::datastore::{self, DataStoreConfig, DIR_NAME_SCHEMA}; |
41bfd249 | 15 | use crate::config::acl::{PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY}; |
4708f4fc | 16 | use crate::server::{jobstate, WorkerTask}; |
567713b4 | 17 | |
688fbe07 DM |
18 | #[api( |
19 | input: { | |
20 | properties: {}, | |
21 | }, | |
22 | returns: { | |
f3ec5dae | 23 | description: "List the configured datastores (with config digest).", |
688fbe07 | 24 | type: Array, |
67f7ffd0 | 25 | items: { type: datastore::DataStoreConfig }, |
688fbe07 | 26 | }, |
c0ef209a | 27 | access: { |
b93bbab4 | 28 | permission: &Permission::Anybody, |
c0ef209a | 29 | }, |
688fbe07 DM |
30 | )] |
31 | /// List all datastores | |
32 | pub fn list_datastores( | |
6049b71f | 33 | _param: Value, |
67f7ffd0 DM |
34 | mut rpcenv: &mut dyn RpcEnvironment, |
35 | ) -> Result<Vec<DataStoreConfig>, Error> { | |
567713b4 | 36 | |
d0187a51 | 37 | let (config, digest) = datastore::config()?; |
b65eaac6 | 38 | |
e6dc35ac | 39 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
b93bbab4 | 40 | let user_info = CachedUserInfo::new()?; |
67f7ffd0 DM |
41 | |
42 | rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); | |
43 | ||
b93bbab4 FG |
44 | let list:Vec<DataStoreConfig> = config.convert_to_typed_array("datastore")?; |
45 | let filter_by_privs = |store: &DataStoreConfig| { | |
e6dc35ac | 46 | let user_privs = user_info.lookup_privs(&auth_id, &["datastore", &store.name]); |
b93bbab4 FG |
47 | (user_privs & PRIV_DATASTORE_AUDIT) != 0 |
48 | }; | |
49 | ||
50 | Ok(list.into_iter().filter(filter_by_privs).collect()) | |
ea0b8b6e DM |
51 | } |
52 | ||
4708f4fc DC |
53 | pub(crate) fn do_create_datastore( |
54 | _lock: std::fs::File, | |
55 | mut config: SectionConfigData, | |
56 | datastore: DataStoreConfig, | |
2de4dc3a | 57 | worker: Option<&dyn crate::task::TaskState>, |
4708f4fc DC |
58 | ) -> Result<(), Error> { |
59 | let path: PathBuf = datastore.path.clone().into(); | |
60 | ||
61 | let backup_user = crate::backup::backup_user()?; | |
2de4dc3a | 62 | let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid, worker)?; |
4708f4fc DC |
63 | |
64 | config.set_data(&datastore.name, "datastore", &datastore)?; | |
65 | ||
66 | datastore::save_config(&config)?; | |
67 | ||
68 | jobstate::create_state_file("prune", &datastore.name)?; | |
69 | jobstate::create_state_file("garbage_collection", &datastore.name)?; | |
70 | ||
71 | Ok(()) | |
72 | } | |
67f7ffd0 DM |
73 | |
74 | // fixme: impl. const fn get_object_schema(datastore::DataStoreConfig::API_SCHEMA), | |
75 | // but this need support for match inside const fn | |
76 | // see: https://github.com/rust-lang/rust/issues/49146 | |
77 | ||
688fbe07 DM |
78 | #[api( |
79 | protected: true, | |
80 | input: { | |
81 | properties: { | |
82 | name: { | |
83 | schema: DATASTORE_SCHEMA, | |
84 | }, | |
67f7ffd0 DM |
85 | path: { |
86 | schema: DIR_NAME_SCHEMA, | |
87 | }, | |
688fbe07 DM |
88 | comment: { |
89 | optional: true, | |
454c13ed | 90 | schema: SINGLE_LINE_COMMENT_SCHEMA, |
688fbe07 | 91 | }, |
6e545d00 DM |
92 | "notify-user": { |
93 | optional: true, | |
94 | type: Userid, | |
95 | }, | |
96 | "notify": { | |
97 | optional: true, | |
c26c9390 | 98 | schema: DATASTORE_NOTIFY_STRING_SCHEMA, |
6e545d00 | 99 | }, |
42fdbe51 DM |
100 | "gc-schedule": { |
101 | optional: true, | |
102 | schema: GC_SCHEDULE_SCHEMA, | |
103 | }, | |
67f7ffd0 DM |
104 | "prune-schedule": { |
105 | optional: true, | |
106 | schema: PRUNE_SCHEDULE_SCHEMA, | |
107 | }, | |
108 | "keep-last": { | |
109 | optional: true, | |
110 | schema: PRUNE_SCHEMA_KEEP_LAST, | |
111 | }, | |
112 | "keep-hourly": { | |
113 | optional: true, | |
114 | schema: PRUNE_SCHEMA_KEEP_HOURLY, | |
115 | }, | |
116 | "keep-daily": { | |
117 | optional: true, | |
118 | schema: PRUNE_SCHEMA_KEEP_DAILY, | |
119 | }, | |
120 | "keep-weekly": { | |
121 | optional: true, | |
122 | schema: PRUNE_SCHEMA_KEEP_WEEKLY, | |
123 | }, | |
124 | "keep-monthly": { | |
125 | optional: true, | |
126 | schema: PRUNE_SCHEMA_KEEP_MONTHLY, | |
127 | }, | |
128 | "keep-yearly": { | |
129 | optional: true, | |
130 | schema: PRUNE_SCHEMA_KEEP_YEARLY, | |
688fbe07 DM |
131 | }, |
132 | }, | |
133 | }, | |
c0ef209a | 134 | access: { |
41bfd249 | 135 | permission: &Permission::Privilege(&["datastore"], PRIV_DATASTORE_ALLOCATE, false), |
c0ef209a | 136 | }, |
688fbe07 DM |
137 | )] |
138 | /// Create new datastore config. | |
4708f4fc DC |
139 | pub fn create_datastore( |
140 | param: Value, | |
141 | rpcenv: &mut dyn RpcEnvironment, | |
142 | ) -> Result<String, Error> { | |
ea0b8b6e | 143 | |
b90036da | 144 | let lock = datastore::lock_config()?; |
652c1190 | 145 | |
44288184 | 146 | let datastore: datastore::DataStoreConfig = serde_json::from_value(param)?; |
652c1190 | 147 | |
4708f4fc | 148 | let (config, _digest) = datastore::config()?; |
652c1190 | 149 | |
3984a5fd | 150 | if config.sections.get(&datastore.name).is_some() { |
67f7ffd0 | 151 | bail!("datastore '{}' already exists.", datastore.name); |
652c1190 DM |
152 | } |
153 | ||
4708f4fc | 154 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
9866de5e | 155 | |
4708f4fc DC |
156 | WorkerTask::new_thread( |
157 | "create-datastore", | |
158 | Some(datastore.name.to_string()), | |
159 | auth_id, | |
160 | false, | |
2de4dc3a | 161 | move |worker| do_create_datastore(lock, config, datastore, Some(&worker)), |
4708f4fc | 162 | ) |
6ce50400 DM |
163 | } |
164 | ||
c5799e40 DM |
165 | #[api( |
166 | input: { | |
167 | properties: { | |
168 | name: { | |
169 | schema: DATASTORE_SCHEMA, | |
170 | }, | |
171 | }, | |
172 | }, | |
9b93c620 | 173 | returns: { type: datastore::DataStoreConfig }, |
c0ef209a DM |
174 | access: { |
175 | permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT, false), | |
176 | }, | |
c5799e40 DM |
177 | )] |
178 | /// Read a datastore configuration. | |
67f7ffd0 DM |
179 | pub fn read_datastore( |
180 | name: String, | |
181 | mut rpcenv: &mut dyn RpcEnvironment, | |
182 | ) -> Result<DataStoreConfig, Error> { | |
c5799e40 | 183 | let (config, digest) = datastore::config()?; |
67f7ffd0 DM |
184 | |
185 | let store_config = config.lookup("datastore", &name)?; | |
186 | rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); | |
187 | ||
188 | Ok(store_config) | |
c5799e40 DM |
189 | } |
190 | ||
0a00f6e0 DM |
191 | #[api()] |
192 | #[derive(Serialize, Deserialize)] | |
42fdbe51 | 193 | #[serde(rename_all="kebab-case")] |
0a00f6e0 DM |
194 | #[allow(non_camel_case_types)] |
195 | /// Deletable property name | |
196 | pub enum DeletableProperty { | |
197 | /// Delete the comment property. | |
198 | comment, | |
42fdbe51 DM |
199 | /// Delete the garbage collection schedule. |
200 | gc_schedule, | |
67f7ffd0 DM |
201 | /// Delete the prune job schedule. |
202 | prune_schedule, | |
203 | /// Delete the keep-last property | |
204 | keep_last, | |
205 | /// Delete the keep-hourly property | |
206 | keep_hourly, | |
207 | /// Delete the keep-daily property | |
208 | keep_daily, | |
209 | /// Delete the keep-weekly property | |
210 | keep_weekly, | |
211 | /// Delete the keep-monthly property | |
212 | keep_monthly, | |
213 | /// Delete the keep-yearly property | |
214 | keep_yearly, | |
ad53c1d6 TL |
215 | /// Delete the verify-new property |
216 | verify_new, | |
6e545d00 DM |
217 | /// Delete the notify-user property |
218 | notify_user, | |
219 | /// Delete the notify property | |
220 | notify, | |
0a00f6e0 DM |
221 | } |
222 | ||
c5799e40 DM |
223 | #[api( |
224 | protected: true, | |
225 | input: { | |
226 | properties: { | |
227 | name: { | |
228 | schema: DATASTORE_SCHEMA, | |
229 | }, | |
230 | comment: { | |
231 | optional: true, | |
232 | schema: SINGLE_LINE_COMMENT_SCHEMA, | |
233 | }, | |
6e545d00 DM |
234 | "notify-user": { |
235 | optional: true, | |
236 | type: Userid, | |
237 | }, | |
238 | "notify": { | |
239 | optional: true, | |
c26c9390 | 240 | schema: DATASTORE_NOTIFY_STRING_SCHEMA, |
6e545d00 | 241 | }, |
42fdbe51 DM |
242 | "gc-schedule": { |
243 | optional: true, | |
244 | schema: GC_SCHEDULE_SCHEMA, | |
245 | }, | |
67f7ffd0 DM |
246 | "prune-schedule": { |
247 | optional: true, | |
248 | schema: PRUNE_SCHEDULE_SCHEMA, | |
249 | }, | |
250 | "keep-last": { | |
251 | optional: true, | |
252 | schema: PRUNE_SCHEMA_KEEP_LAST, | |
253 | }, | |
254 | "keep-hourly": { | |
255 | optional: true, | |
256 | schema: PRUNE_SCHEMA_KEEP_HOURLY, | |
257 | }, | |
258 | "keep-daily": { | |
259 | optional: true, | |
260 | schema: PRUNE_SCHEMA_KEEP_DAILY, | |
261 | }, | |
262 | "keep-weekly": { | |
263 | optional: true, | |
264 | schema: PRUNE_SCHEMA_KEEP_WEEKLY, | |
265 | }, | |
266 | "keep-monthly": { | |
267 | optional: true, | |
268 | schema: PRUNE_SCHEMA_KEEP_MONTHLY, | |
269 | }, | |
270 | "keep-yearly": { | |
271 | optional: true, | |
272 | schema: PRUNE_SCHEMA_KEEP_YEARLY, | |
273 | }, | |
ad53c1d6 TL |
274 | "verify-new": { |
275 | description: "If enabled, all new backups will be verified right after completion.", | |
276 | type: bool, | |
277 | optional: true, | |
278 | default: false, | |
279 | }, | |
0a00f6e0 DM |
280 | delete: { |
281 | description: "List of properties to delete.", | |
282 | type: Array, | |
283 | optional: true, | |
284 | items: { | |
285 | type: DeletableProperty, | |
286 | } | |
287 | }, | |
002a191a DM |
288 | digest: { |
289 | optional: true, | |
290 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
291 | }, | |
c5799e40 DM |
292 | }, |
293 | }, | |
c0ef209a | 294 | access: { |
9c7fe29d | 295 | permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY, false), |
c0ef209a | 296 | }, |
c5799e40 | 297 | )] |
2ea7bf1b | 298 | /// Update datastore config. |
367c0ff7 | 299 | #[allow(clippy::too_many_arguments)] |
c5799e40 DM |
300 | pub fn update_datastore( |
301 | name: String, | |
302 | comment: Option<String>, | |
42fdbe51 | 303 | gc_schedule: Option<String>, |
67f7ffd0 | 304 | prune_schedule: Option<String>, |
872062ee DM |
305 | keep_last: Option<u64>, |
306 | keep_hourly: Option<u64>, | |
307 | keep_daily: Option<u64>, | |
308 | keep_weekly: Option<u64>, | |
309 | keep_monthly: Option<u64>, | |
310 | keep_yearly: Option<u64>, | |
ad53c1d6 | 311 | verify_new: Option<bool>, |
c26c9390 | 312 | notify: Option<String>, |
6e545d00 | 313 | notify_user: Option<Userid>, |
0a00f6e0 | 314 | delete: Option<Vec<DeletableProperty>>, |
002a191a | 315 | digest: Option<String>, |
c5799e40 DM |
316 | ) -> Result<(), Error> { |
317 | ||
b90036da | 318 | let _lock = datastore::lock_config()?; |
347834df | 319 | |
c5799e40 | 320 | // pass/compare digest |
002a191a DM |
321 | let (mut config, expected_digest) = datastore::config()?; |
322 | ||
323 | if let Some(ref digest) = digest { | |
324 | let digest = proxmox::tools::hex_to_digest(digest)?; | |
325 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; | |
326 | } | |
c5799e40 DM |
327 | |
328 | let mut data: datastore::DataStoreConfig = config.lookup("datastore", &name)?; | |
329 | ||
0a00f6e0 DM |
330 | if let Some(delete) = delete { |
331 | for delete_prop in delete { | |
332 | match delete_prop { | |
333 | DeletableProperty::comment => { data.comment = None; }, | |
42fdbe51 | 334 | DeletableProperty::gc_schedule => { data.gc_schedule = None; }, |
67f7ffd0 DM |
335 | DeletableProperty::prune_schedule => { data.prune_schedule = None; }, |
336 | DeletableProperty::keep_last => { data.keep_last = None; }, | |
337 | DeletableProperty::keep_hourly => { data.keep_hourly = None; }, | |
338 | DeletableProperty::keep_daily => { data.keep_daily = None; }, | |
339 | DeletableProperty::keep_weekly => { data.keep_weekly = None; }, | |
340 | DeletableProperty::keep_monthly => { data.keep_monthly = None; }, | |
341 | DeletableProperty::keep_yearly => { data.keep_yearly = None; }, | |
ad53c1d6 | 342 | DeletableProperty::verify_new => { data.verify_new = None; }, |
6e545d00 DM |
343 | DeletableProperty::notify => { data.notify = None; }, |
344 | DeletableProperty::notify_user => { data.notify_user = None; }, | |
0a00f6e0 DM |
345 | } |
346 | } | |
347 | } | |
348 | ||
c5799e40 DM |
349 | if let Some(comment) = comment { |
350 | let comment = comment.trim().to_string(); | |
351 | if comment.is_empty() { | |
352 | data.comment = None; | |
353 | } else { | |
354 | data.comment = Some(comment); | |
355 | } | |
356 | } | |
c5799e40 | 357 | |
d7a122a0 DC |
358 | let mut gc_schedule_changed = false; |
359 | if gc_schedule.is_some() { | |
360 | gc_schedule_changed = data.gc_schedule != gc_schedule; | |
361 | data.gc_schedule = gc_schedule; | |
362 | } | |
363 | ||
9866de5e DC |
364 | let mut prune_schedule_changed = false; |
365 | if prune_schedule.is_some() { | |
d7a122a0 | 366 | prune_schedule_changed = data.prune_schedule != prune_schedule; |
9866de5e DC |
367 | data.prune_schedule = prune_schedule; |
368 | } | |
d7a122a0 | 369 | |
67f7ffd0 DM |
370 | if keep_last.is_some() { data.keep_last = keep_last; } |
371 | if keep_hourly.is_some() { data.keep_hourly = keep_hourly; } | |
372 | if keep_daily.is_some() { data.keep_daily = keep_daily; } | |
373 | if keep_weekly.is_some() { data.keep_weekly = keep_weekly; } | |
374 | if keep_monthly.is_some() { data.keep_monthly = keep_monthly; } | |
375 | if keep_yearly.is_some() { data.keep_yearly = keep_yearly; } | |
42fdbe51 | 376 | |
c26c9390 DM |
377 | if let Some(notify_str) = notify { |
378 | let value = parse_property_string(¬ify_str, &DatastoreNotify::API_SCHEMA)?; | |
379 | let notify: DatastoreNotify = serde_json::from_value(value)?; | |
380 | if let DatastoreNotify { gc: None, verify: None, sync: None } = notify { | |
381 | data.notify = None; | |
382 | } else { | |
383 | data.notify = Some(notify_str); | |
384 | } | |
385 | } | |
ad53c1d6 TL |
386 | if verify_new.is_some() { data.verify_new = verify_new; } |
387 | ||
6e545d00 DM |
388 | if notify_user.is_some() { data.notify_user = notify_user; } |
389 | ||
c5799e40 DM |
390 | config.set_data(&name, "datastore", &data)?; |
391 | ||
392 | datastore::save_config(&config)?; | |
393 | ||
d7a122a0 | 394 | // we want to reset the statefiles, to avoid an immediate action in some cases |
9866de5e | 395 | // (e.g. going from monthly to weekly in the second week of the month) |
d7a122a0 | 396 | if gc_schedule_changed { |
a588b679 | 397 | jobstate::update_job_last_run_time("garbage_collection", &name)?; |
d7a122a0 DC |
398 | } |
399 | ||
9866de5e | 400 | if prune_schedule_changed { |
a588b679 | 401 | jobstate::update_job_last_run_time("prune", &name)?; |
9866de5e DC |
402 | } |
403 | ||
c5799e40 DM |
404 | Ok(()) |
405 | } | |
406 | ||
688fbe07 DM |
407 | #[api( |
408 | protected: true, | |
409 | input: { | |
410 | properties: { | |
411 | name: { | |
412 | schema: DATASTORE_SCHEMA, | |
413 | }, | |
c0ef209a DM |
414 | digest: { |
415 | optional: true, | |
416 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
417 | }, | |
688fbe07 DM |
418 | }, |
419 | }, | |
c0ef209a | 420 | access: { |
92dd02aa | 421 | permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_ALLOCATE, false), |
c0ef209a | 422 | }, |
688fbe07 DM |
423 | )] |
424 | /// Remove a datastore configuration. | |
062cf75c | 425 | pub async fn delete_datastore(name: String, digest: Option<String>) -> Result<(), Error> { |
34d3ba52 | 426 | |
b90036da | 427 | let _lock = datastore::lock_config()?; |
34d3ba52 | 428 | |
c0ef209a DM |
429 | let (mut config, expected_digest) = datastore::config()?; |
430 | ||
431 | if let Some(ref digest) = digest { | |
432 | let digest = proxmox::tools::hex_to_digest(digest)?; | |
433 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; | |
434 | } | |
34d3ba52 | 435 | |
688fbe07 DM |
436 | match config.sections.get(&name) { |
437 | Some(_) => { config.sections.remove(&name); }, | |
34d3ba52 DM |
438 | None => bail!("datastore '{}' does not exist.", name), |
439 | } | |
440 | ||
441 | datastore::save_config(&config)?; | |
442 | ||
d7a122a0 | 443 | // ignore errors |
1298618a DM |
444 | let _ = jobstate::remove_state_file("prune", &name); |
445 | let _ = jobstate::remove_state_file("garbage_collection", &name); | |
9866de5e | 446 | |
062cf75c DC |
447 | crate::server::notify_datastore_removed().await?; |
448 | ||
688fbe07 | 449 | Ok(()) |
34d3ba52 DM |
450 | } |
451 | ||
c5799e40 DM |
452 | const ITEM_ROUTER: Router = Router::new() |
453 | .get(&API_METHOD_READ_DATASTORE) | |
454 | .put(&API_METHOD_UPDATE_DATASTORE) | |
455 | .delete(&API_METHOD_DELETE_DATASTORE); | |
456 | ||
255f378a | 457 | pub const ROUTER: Router = Router::new() |
688fbe07 DM |
458 | .get(&API_METHOD_LIST_DATASTORES) |
459 | .post(&API_METHOD_CREATE_DATASTORE) | |
c5799e40 | 460 | .match_all("name", &ITEM_ROUTER); |