]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/config/datastore.rs
proxmox-backup-proxy: fix leftover references on datastore removal
[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
3984a5fd 127 if config.sections.get(&datastore.name).is_some() {
67f7ffd0 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.
367c0ff7 280#[allow(clippy::too_many_arguments)]
c5799e40
DM
281pub fn update_datastore(
282 name: String,
283 comment: Option<String>,
42fdbe51 284 gc_schedule: Option<String>,
67f7ffd0 285 prune_schedule: Option<String>,
872062ee
DM
286 keep_last: Option<u64>,
287 keep_hourly: Option<u64>,
288 keep_daily: Option<u64>,
289 keep_weekly: Option<u64>,
290 keep_monthly: Option<u64>,
291 keep_yearly: Option<u64>,
ad53c1d6 292 verify_new: Option<bool>,
c26c9390 293 notify: Option<String>,
6e545d00 294 notify_user: Option<Userid>,
0a00f6e0 295 delete: Option<Vec<DeletableProperty>>,
002a191a 296 digest: Option<String>,
c5799e40
DM
297) -> Result<(), Error> {
298
b56c111e 299 let _lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
347834df 300
c5799e40 301 // pass/compare digest
002a191a
DM
302 let (mut config, expected_digest) = datastore::config()?;
303
304 if let Some(ref digest) = digest {
305 let digest = proxmox::tools::hex_to_digest(digest)?;
306 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
307 }
c5799e40
DM
308
309 let mut data: datastore::DataStoreConfig = config.lookup("datastore", &name)?;
310
0a00f6e0
DM
311 if let Some(delete) = delete {
312 for delete_prop in delete {
313 match delete_prop {
314 DeletableProperty::comment => { data.comment = None; },
42fdbe51 315 DeletableProperty::gc_schedule => { data.gc_schedule = None; },
67f7ffd0
DM
316 DeletableProperty::prune_schedule => { data.prune_schedule = None; },
317 DeletableProperty::keep_last => { data.keep_last = None; },
318 DeletableProperty::keep_hourly => { data.keep_hourly = None; },
319 DeletableProperty::keep_daily => { data.keep_daily = None; },
320 DeletableProperty::keep_weekly => { data.keep_weekly = None; },
321 DeletableProperty::keep_monthly => { data.keep_monthly = None; },
322 DeletableProperty::keep_yearly => { data.keep_yearly = None; },
ad53c1d6 323 DeletableProperty::verify_new => { data.verify_new = None; },
6e545d00
DM
324 DeletableProperty::notify => { data.notify = None; },
325 DeletableProperty::notify_user => { data.notify_user = None; },
0a00f6e0
DM
326 }
327 }
328 }
329
c5799e40
DM
330 if let Some(comment) = comment {
331 let comment = comment.trim().to_string();
332 if comment.is_empty() {
333 data.comment = None;
334 } else {
335 data.comment = Some(comment);
336 }
337 }
c5799e40 338
d7a122a0
DC
339 let mut gc_schedule_changed = false;
340 if gc_schedule.is_some() {
341 gc_schedule_changed = data.gc_schedule != gc_schedule;
342 data.gc_schedule = gc_schedule;
343 }
344
9866de5e
DC
345 let mut prune_schedule_changed = false;
346 if prune_schedule.is_some() {
d7a122a0 347 prune_schedule_changed = data.prune_schedule != prune_schedule;
9866de5e
DC
348 data.prune_schedule = prune_schedule;
349 }
d7a122a0 350
67f7ffd0
DM
351 if keep_last.is_some() { data.keep_last = keep_last; }
352 if keep_hourly.is_some() { data.keep_hourly = keep_hourly; }
353 if keep_daily.is_some() { data.keep_daily = keep_daily; }
354 if keep_weekly.is_some() { data.keep_weekly = keep_weekly; }
355 if keep_monthly.is_some() { data.keep_monthly = keep_monthly; }
356 if keep_yearly.is_some() { data.keep_yearly = keep_yearly; }
42fdbe51 357
c26c9390
DM
358 if let Some(notify_str) = notify {
359 let value = parse_property_string(&notify_str, &DatastoreNotify::API_SCHEMA)?;
360 let notify: DatastoreNotify = serde_json::from_value(value)?;
361 if let DatastoreNotify { gc: None, verify: None, sync: None } = notify {
362 data.notify = None;
363 } else {
364 data.notify = Some(notify_str);
365 }
366 }
ad53c1d6
TL
367 if verify_new.is_some() { data.verify_new = verify_new; }
368
6e545d00
DM
369 if notify_user.is_some() { data.notify_user = notify_user; }
370
c5799e40
DM
371 config.set_data(&name, "datastore", &data)?;
372
373 datastore::save_config(&config)?;
374
d7a122a0 375 // we want to reset the statefiles, to avoid an immediate action in some cases
9866de5e 376 // (e.g. going from monthly to weekly in the second week of the month)
d7a122a0 377 if gc_schedule_changed {
a588b679 378 jobstate::update_job_last_run_time("garbage_collection", &name)?;
d7a122a0
DC
379 }
380
9866de5e 381 if prune_schedule_changed {
a588b679 382 jobstate::update_job_last_run_time("prune", &name)?;
9866de5e
DC
383 }
384
c5799e40
DM
385 Ok(())
386}
387
688fbe07
DM
388#[api(
389 protected: true,
390 input: {
391 properties: {
392 name: {
393 schema: DATASTORE_SCHEMA,
394 },
c0ef209a
DM
395 digest: {
396 optional: true,
397 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
398 },
688fbe07
DM
399 },
400 },
c0ef209a 401 access: {
92dd02aa 402 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_ALLOCATE, false),
c0ef209a 403 },
688fbe07
DM
404)]
405/// Remove a datastore configuration.
062cf75c 406pub async fn delete_datastore(name: String, digest: Option<String>) -> Result<(), Error> {
34d3ba52 407
b56c111e 408 let _lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
34d3ba52 409
c0ef209a
DM
410 let (mut config, expected_digest) = datastore::config()?;
411
412 if let Some(ref digest) = digest {
413 let digest = proxmox::tools::hex_to_digest(digest)?;
414 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
415 }
34d3ba52 416
688fbe07
DM
417 match config.sections.get(&name) {
418 Some(_) => { config.sections.remove(&name); },
34d3ba52
DM
419 None => bail!("datastore '{}' does not exist.", name),
420 }
421
422 datastore::save_config(&config)?;
423
d7a122a0 424 // ignore errors
1298618a
DM
425 let _ = jobstate::remove_state_file("prune", &name);
426 let _ = jobstate::remove_state_file("garbage_collection", &name);
9866de5e 427
062cf75c
DC
428 crate::server::notify_datastore_removed().await?;
429
688fbe07 430 Ok(())
34d3ba52
DM
431}
432
c5799e40
DM
433const ITEM_ROUTER: Router = Router::new()
434 .get(&API_METHOD_READ_DATASTORE)
435 .put(&API_METHOD_UPDATE_DATASTORE)
436 .delete(&API_METHOD_DELETE_DATASTORE);
437
255f378a 438pub const ROUTER: Router = Router::new()
688fbe07
DM
439 .get(&API_METHOD_LIST_DATASTORES)
440 .post(&API_METHOD_CREATE_DATASTORE)
c5799e40 441 .match_all("name", &ITEM_ROUTER);