]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/config/datastore.rs
clippy: remove unnecessary clones
[proxmox-backup.git] / src / api2 / config / datastore.rs
CommitLineData
a2479cfa 1use std::path::PathBuf;
6ce50400 2
f7d4e4b5 3use anyhow::{bail, Error};
5e62d19c 4use serde_json::Value;
0a00f6e0 5use ::serde::{Deserialize, Serialize};
6ce50400 6
67f7ffd0 7use proxmox::api::{api, Router, RpcEnvironment, Permission};
c26c9390 8use proxmox::api::schema::parse_property_string;
98c259b4 9use proxmox::tools::fs::open_file_locked;
a2479cfa 10
66c49c21 11use crate::api2::types::*;
a2479cfa 12use crate::backup::*;
b93bbab4 13use crate::config::cached_user_info::CachedUserInfo;
67f7ffd0 14use crate::config::datastore::{self, DataStoreConfig, DIR_NAME_SCHEMA};
41bfd249 15use crate::config::acl::{PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY};
1298618a 16use crate::server::jobstate;
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
32pub 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
67f7ffd0
DM
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
688fbe07
DM
58#[api(
59 protected: true,
60 input: {
61 properties: {
62 name: {
63 schema: DATASTORE_SCHEMA,
64 },
67f7ffd0
DM
65 path: {
66 schema: DIR_NAME_SCHEMA,
67 },
688fbe07
DM
68 comment: {
69 optional: true,
454c13ed 70 schema: SINGLE_LINE_COMMENT_SCHEMA,
688fbe07 71 },
6e545d00
DM
72 "notify-user": {
73 optional: true,
74 type: Userid,
75 },
76 "notify": {
77 optional: true,
c26c9390 78 schema: DATASTORE_NOTIFY_STRING_SCHEMA,
6e545d00 79 },
42fdbe51
DM
80 "gc-schedule": {
81 optional: true,
82 schema: GC_SCHEDULE_SCHEMA,
83 },
67f7ffd0
DM
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,
688fbe07
DM
111 },
112 },
113 },
c0ef209a 114 access: {
41bfd249 115 permission: &Permission::Privilege(&["datastore"], PRIV_DATASTORE_ALLOCATE, false),
c0ef209a 116 },
688fbe07
DM
117)]
118/// Create new datastore config.
67f7ffd0 119pub fn create_datastore(param: Value) -> Result<(), Error> {
ea0b8b6e 120
b56c111e 121 let _lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
652c1190 122
44288184 123 let datastore: datastore::DataStoreConfig = serde_json::from_value(param)?;
652c1190 124
d0187a51 125 let (mut config, _digest) = datastore::config()?;
652c1190 126
67f7ffd0
DM
127 if let Some(_) = config.sections.get(&datastore.name) {
128 bail!("datastore '{}' already exists.", datastore.name);
652c1190
DM
129 }
130
688fbe07
DM
131 let path: PathBuf = datastore.path.clone().into();
132
133 let backup_user = crate::backup::backup_user()?;
67f7ffd0 134 let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid)?;
688fbe07 135
67f7ffd0 136 config.set_data(&datastore.name, "datastore", &datastore)?;
652c1190
DM
137
138 datastore::save_config(&config)?;
139
1298618a
DM
140 jobstate::create_state_file("prune", &datastore.name)?;
141 jobstate::create_state_file("garbage_collection", &datastore.name)?;
9866de5e 142
688fbe07 143 Ok(())
6ce50400
DM
144}
145
c5799e40
DM
146#[api(
147 input: {
148 properties: {
149 name: {
150 schema: DATASTORE_SCHEMA,
151 },
152 },
153 },
9b93c620 154 returns: { type: datastore::DataStoreConfig },
c0ef209a
DM
155 access: {
156 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT, false),
157 },
c5799e40
DM
158)]
159/// Read a datastore configuration.
67f7ffd0
DM
160pub fn read_datastore(
161 name: String,
162 mut rpcenv: &mut dyn RpcEnvironment,
163) -> Result<DataStoreConfig, Error> {
c5799e40 164 let (config, digest) = datastore::config()?;
67f7ffd0
DM
165
166 let store_config = config.lookup("datastore", &name)?;
167 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
168
169 Ok(store_config)
c5799e40
DM
170}
171
0a00f6e0
DM
172#[api()]
173#[derive(Serialize, Deserialize)]
42fdbe51 174#[serde(rename_all="kebab-case")]
0a00f6e0
DM
175#[allow(non_camel_case_types)]
176/// Deletable property name
177pub enum DeletableProperty {
178 /// Delete the comment property.
179 comment,
42fdbe51
DM
180 /// Delete the garbage collection schedule.
181 gc_schedule,
67f7ffd0
DM
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,
ad53c1d6
TL
196 /// Delete the verify-new property
197 verify_new,
6e545d00
DM
198 /// Delete the notify-user property
199 notify_user,
200 /// Delete the notify property
201 notify,
0a00f6e0
DM
202}
203
c5799e40
DM
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 },
6e545d00
DM
215 "notify-user": {
216 optional: true,
217 type: Userid,
218 },
219 "notify": {
220 optional: true,
c26c9390 221 schema: DATASTORE_NOTIFY_STRING_SCHEMA,
6e545d00 222 },
42fdbe51
DM
223 "gc-schedule": {
224 optional: true,
225 schema: GC_SCHEDULE_SCHEMA,
226 },
67f7ffd0
DM
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 },
ad53c1d6
TL
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 },
0a00f6e0
DM
261 delete: {
262 description: "List of properties to delete.",
263 type: Array,
264 optional: true,
265 items: {
266 type: DeletableProperty,
267 }
268 },
002a191a
DM
269 digest: {
270 optional: true,
271 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
272 },
c5799e40
DM
273 },
274 },
c0ef209a 275 access: {
9c7fe29d 276 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY, false),
c0ef209a 277 },
c5799e40 278)]
2ea7bf1b 279/// Update datastore config.
c5799e40
DM
280pub fn update_datastore(
281 name: String,
282 comment: Option<String>,
42fdbe51 283 gc_schedule: Option<String>,
67f7ffd0 284 prune_schedule: Option<String>,
872062ee
DM
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>,
ad53c1d6 291 verify_new: Option<bool>,
c26c9390 292 notify: Option<String>,
6e545d00 293 notify_user: Option<Userid>,
0a00f6e0 294 delete: Option<Vec<DeletableProperty>>,
002a191a 295 digest: Option<String>,
c5799e40
DM
296) -> Result<(), Error> {
297
b56c111e 298 let _lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
347834df 299
c5799e40 300 // pass/compare digest
002a191a
DM
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 }
c5799e40
DM
307
308 let mut data: datastore::DataStoreConfig = config.lookup("datastore", &name)?;
309
0a00f6e0
DM
310 if let Some(delete) = delete {
311 for delete_prop in delete {
312 match delete_prop {
313 DeletableProperty::comment => { data.comment = None; },
42fdbe51 314 DeletableProperty::gc_schedule => { data.gc_schedule = None; },
67f7ffd0
DM
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; },
ad53c1d6 322 DeletableProperty::verify_new => { data.verify_new = None; },
6e545d00
DM
323 DeletableProperty::notify => { data.notify = None; },
324 DeletableProperty::notify_user => { data.notify_user = None; },
0a00f6e0
DM
325 }
326 }
327 }
328
c5799e40
DM
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 }
c5799e40 337
d7a122a0
DC
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
9866de5e
DC
344 let mut prune_schedule_changed = false;
345 if prune_schedule.is_some() {
d7a122a0 346 prune_schedule_changed = data.prune_schedule != prune_schedule;
9866de5e
DC
347 data.prune_schedule = prune_schedule;
348 }
d7a122a0 349
67f7ffd0
DM
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; }
42fdbe51 356
c26c9390
DM
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 }
ad53c1d6
TL
366 if verify_new.is_some() { data.verify_new = verify_new; }
367
6e545d00
DM
368 if notify_user.is_some() { data.notify_user = notify_user; }
369
c5799e40
DM
370 config.set_data(&name, "datastore", &data)?;
371
372 datastore::save_config(&config)?;
373
d7a122a0 374 // we want to reset the statefiles, to avoid an immediate action in some cases
9866de5e 375 // (e.g. going from monthly to weekly in the second week of the month)
d7a122a0 376 if gc_schedule_changed {
1298618a 377 jobstate::create_state_file("garbage_collection", &name)?;
d7a122a0
DC
378 }
379
9866de5e 380 if prune_schedule_changed {
1298618a 381 jobstate::create_state_file("prune", &name)?;
9866de5e
DC
382 }
383
c5799e40
DM
384 Ok(())
385}
386
688fbe07
DM
387#[api(
388 protected: true,
389 input: {
390 properties: {
391 name: {
392 schema: DATASTORE_SCHEMA,
393 },
c0ef209a
DM
394 digest: {
395 optional: true,
396 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
397 },
688fbe07
DM
398 },
399 },
c0ef209a 400 access: {
92dd02aa 401 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_ALLOCATE, false),
c0ef209a 402 },
688fbe07
DM
403)]
404/// Remove a datastore configuration.
c0ef209a 405pub fn delete_datastore(name: String, digest: Option<String>) -> Result<(), Error> {
34d3ba52 406
b56c111e 407 let _lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
34d3ba52 408
c0ef209a
DM
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 }
34d3ba52 415
688fbe07
DM
416 match config.sections.get(&name) {
417 Some(_) => { config.sections.remove(&name); },
34d3ba52
DM
418 None => bail!("datastore '{}' does not exist.", name),
419 }
420
421 datastore::save_config(&config)?;
422
d7a122a0 423 // ignore errors
1298618a
DM
424 let _ = jobstate::remove_state_file("prune", &name);
425 let _ = jobstate::remove_state_file("garbage_collection", &name);
9866de5e 426
688fbe07 427 Ok(())
34d3ba52
DM
428}
429
c5799e40
DM
430const ITEM_ROUTER: Router = Router::new()
431 .get(&API_METHOD_READ_DATASTORE)
432 .put(&API_METHOD_UPDATE_DATASTORE)
433 .delete(&API_METHOD_DELETE_DATASTORE);
434
255f378a 435pub const ROUTER: Router = Router::new()
688fbe07
DM
436 .get(&API_METHOD_LIST_DATASTORES)
437 .post(&API_METHOD_CREATE_DATASTORE)
c5799e40 438 .match_all("name", &ITEM_ROUTER);