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