]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/config/datastore.rs
api2: add optional verify-schdule field to create/update datastore endpoint
[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_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_MODIFY, 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))?;
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 Ok(())
135 }
136
137 #[api(
138 input: {
139 properties: {
140 name: {
141 schema: DATASTORE_SCHEMA,
142 },
143 },
144 },
145 returns: {
146 description: "The datastore configuration (with config digest).",
147 type: datastore::DataStoreConfig,
148 },
149 access: {
150 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT, false),
151 },
152 )]
153 /// Read a datastore configuration.
154 pub fn read_datastore(
155 name: String,
156 mut rpcenv: &mut dyn RpcEnvironment,
157 ) -> Result<DataStoreConfig, Error> {
158 let (config, digest) = datastore::config()?;
159
160 let store_config = config.lookup("datastore", &name)?;
161 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
162
163 Ok(store_config)
164 }
165
166 #[api()]
167 #[derive(Serialize, Deserialize)]
168 #[serde(rename_all="kebab-case")]
169 #[allow(non_camel_case_types)]
170 /// Deletable property name
171 pub enum DeletableProperty {
172 /// Delete the comment property.
173 comment,
174 /// Delete the garbage collection schedule.
175 gc_schedule,
176 /// Delete the prune job schedule.
177 prune_schedule,
178 /// Delete the keep-last property
179 keep_last,
180 /// Delete the keep-hourly property
181 keep_hourly,
182 /// Delete the keep-daily property
183 keep_daily,
184 /// Delete the keep-weekly property
185 keep_weekly,
186 /// Delete the keep-monthly property
187 keep_monthly,
188 /// Delete the keep-yearly property
189 keep_yearly,
190 }
191
192 #[api(
193 protected: true,
194 input: {
195 properties: {
196 name: {
197 schema: DATASTORE_SCHEMA,
198 },
199 comment: {
200 optional: true,
201 schema: SINGLE_LINE_COMMENT_SCHEMA,
202 },
203 "gc-schedule": {
204 optional: true,
205 schema: GC_SCHEDULE_SCHEMA,
206 },
207 "prune-schedule": {
208 optional: true,
209 schema: PRUNE_SCHEDULE_SCHEMA,
210 },
211 "verify-schedule": {
212 optional: true,
213 schema: VERIFY_SCHEDULE_SCHEMA,
214 },
215 "keep-last": {
216 optional: true,
217 schema: PRUNE_SCHEMA_KEEP_LAST,
218 },
219 "keep-hourly": {
220 optional: true,
221 schema: PRUNE_SCHEMA_KEEP_HOURLY,
222 },
223 "keep-daily": {
224 optional: true,
225 schema: PRUNE_SCHEMA_KEEP_DAILY,
226 },
227 "keep-weekly": {
228 optional: true,
229 schema: PRUNE_SCHEMA_KEEP_WEEKLY,
230 },
231 "keep-monthly": {
232 optional: true,
233 schema: PRUNE_SCHEMA_KEEP_MONTHLY,
234 },
235 "keep-yearly": {
236 optional: true,
237 schema: PRUNE_SCHEMA_KEEP_YEARLY,
238 },
239 delete: {
240 description: "List of properties to delete.",
241 type: Array,
242 optional: true,
243 items: {
244 type: DeletableProperty,
245 }
246 },
247 digest: {
248 optional: true,
249 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
250 },
251 },
252 },
253 access: {
254 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY, false),
255 },
256 )]
257 /// Update datastore config.
258 pub fn update_datastore(
259 name: String,
260 comment: Option<String>,
261 gc_schedule: Option<String>,
262 prune_schedule: Option<String>,
263 verify_schedule: Option<String>,
264 keep_last: Option<u64>,
265 keep_hourly: Option<u64>,
266 keep_daily: Option<u64>,
267 keep_weekly: Option<u64>,
268 keep_monthly: Option<u64>,
269 keep_yearly: Option<u64>,
270 delete: Option<Vec<DeletableProperty>>,
271 digest: Option<String>,
272 ) -> Result<(), Error> {
273
274 let _lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0))?;
275
276 // pass/compare digest
277 let (mut config, expected_digest) = datastore::config()?;
278
279 if let Some(ref digest) = digest {
280 let digest = proxmox::tools::hex_to_digest(digest)?;
281 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
282 }
283
284 let mut data: datastore::DataStoreConfig = config.lookup("datastore", &name)?;
285
286 if let Some(delete) = delete {
287 for delete_prop in delete {
288 match delete_prop {
289 DeletableProperty::comment => { data.comment = None; },
290 DeletableProperty::gc_schedule => { data.gc_schedule = None; },
291 DeletableProperty::prune_schedule => { data.prune_schedule = None; },
292 DeletableProperty::keep_last => { data.keep_last = None; },
293 DeletableProperty::keep_hourly => { data.keep_hourly = None; },
294 DeletableProperty::keep_daily => { data.keep_daily = None; },
295 DeletableProperty::keep_weekly => { data.keep_weekly = None; },
296 DeletableProperty::keep_monthly => { data.keep_monthly = None; },
297 DeletableProperty::keep_yearly => { data.keep_yearly = None; },
298 }
299 }
300 }
301
302 if let Some(comment) = comment {
303 let comment = comment.trim().to_string();
304 if comment.is_empty() {
305 data.comment = None;
306 } else {
307 data.comment = Some(comment);
308 }
309 }
310
311 if gc_schedule.is_some() { data.gc_schedule = gc_schedule; }
312 if prune_schedule.is_some() { data.prune_schedule = prune_schedule; }
313 if verify_schedule.is_some() { data.verify_schedule = verify_schedule; }
314
315 if keep_last.is_some() { data.keep_last = keep_last; }
316 if keep_hourly.is_some() { data.keep_hourly = keep_hourly; }
317 if keep_daily.is_some() { data.keep_daily = keep_daily; }
318 if keep_weekly.is_some() { data.keep_weekly = keep_weekly; }
319 if keep_monthly.is_some() { data.keep_monthly = keep_monthly; }
320 if keep_yearly.is_some() { data.keep_yearly = keep_yearly; }
321
322 config.set_data(&name, "datastore", &data)?;
323
324 datastore::save_config(&config)?;
325
326 Ok(())
327 }
328
329 #[api(
330 protected: true,
331 input: {
332 properties: {
333 name: {
334 schema: DATASTORE_SCHEMA,
335 },
336 digest: {
337 optional: true,
338 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
339 },
340 },
341 },
342 access: {
343 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY, false),
344 },
345 )]
346 /// Remove a datastore configuration.
347 pub fn delete_datastore(name: String, digest: Option<String>) -> Result<(), Error> {
348
349 let _lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0))?;
350
351 let (mut config, expected_digest) = datastore::config()?;
352
353 if let Some(ref digest) = digest {
354 let digest = proxmox::tools::hex_to_digest(digest)?;
355 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
356 }
357
358 match config.sections.get(&name) {
359 Some(_) => { config.sections.remove(&name); },
360 None => bail!("datastore '{}' does not exist.", name),
361 }
362
363 datastore::save_config(&config)?;
364
365 Ok(())
366 }
367
368 const ITEM_ROUTER: Router = Router::new()
369 .get(&API_METHOD_READ_DATASTORE)
370 .put(&API_METHOD_UPDATE_DATASTORE)
371 .delete(&API_METHOD_DELETE_DATASTORE);
372
373 pub const ROUTER: Router = Router::new()
374 .get(&API_METHOD_LIST_DATASTORES)
375 .post(&API_METHOD_CREATE_DATASTORE)
376 .match_all("name", &ITEM_ROUTER);