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