]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/config/datastore.rs
4f9cb7c764016a2e3ebe0f47aa75dec79b071757
[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 "keep-last": {
79 optional: true,
80 schema: PRUNE_SCHEMA_KEEP_LAST,
81 },
82 "keep-hourly": {
83 optional: true,
84 schema: PRUNE_SCHEMA_KEEP_HOURLY,
85 },
86 "keep-daily": {
87 optional: true,
88 schema: PRUNE_SCHEMA_KEEP_DAILY,
89 },
90 "keep-weekly": {
91 optional: true,
92 schema: PRUNE_SCHEMA_KEEP_WEEKLY,
93 },
94 "keep-monthly": {
95 optional: true,
96 schema: PRUNE_SCHEMA_KEEP_MONTHLY,
97 },
98 "keep-yearly": {
99 optional: true,
100 schema: PRUNE_SCHEMA_KEEP_YEARLY,
101 },
102 },
103 },
104 access: {
105 permission: &Permission::Privilege(&["datastore"], PRIV_DATASTORE_ALLOCATE, false),
106 },
107 )]
108 /// Create new datastore config.
109 pub fn create_datastore(param: Value) -> Result<(), Error> {
110
111 let _lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
112
113 let datastore: datastore::DataStoreConfig = serde_json::from_value(param.clone())?;
114
115 let (mut config, _digest) = datastore::config()?;
116
117 if let Some(_) = config.sections.get(&datastore.name) {
118 bail!("datastore '{}' already exists.", datastore.name);
119 }
120
121 let path: PathBuf = datastore.path.clone().into();
122
123 let backup_user = crate::backup::backup_user()?;
124 let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid)?;
125
126 config.set_data(&datastore.name, "datastore", &datastore)?;
127
128 datastore::save_config(&config)?;
129
130 crate::config::jobstate::create_state_file("prune", &datastore.name)?;
131 crate::config::jobstate::create_state_file("garbage_collection", &datastore.name)?;
132
133 Ok(())
134 }
135
136 #[api(
137 input: {
138 properties: {
139 name: {
140 schema: DATASTORE_SCHEMA,
141 },
142 },
143 },
144 returns: {
145 description: "The datastore configuration (with config digest).",
146 type: datastore::DataStoreConfig,
147 },
148 access: {
149 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT, false),
150 },
151 )]
152 /// Read a datastore configuration.
153 pub fn read_datastore(
154 name: String,
155 mut rpcenv: &mut dyn RpcEnvironment,
156 ) -> Result<DataStoreConfig, Error> {
157 let (config, digest) = datastore::config()?;
158
159 let store_config = config.lookup("datastore", &name)?;
160 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
161
162 Ok(store_config)
163 }
164
165 #[api()]
166 #[derive(Serialize, Deserialize)]
167 #[serde(rename_all="kebab-case")]
168 #[allow(non_camel_case_types)]
169 /// Deletable property name
170 pub enum DeletableProperty {
171 /// Delete the comment property.
172 comment,
173 /// Delete the garbage collection schedule.
174 gc_schedule,
175 /// Delete the prune job schedule.
176 prune_schedule,
177 /// Delete the keep-last property
178 keep_last,
179 /// Delete the keep-hourly property
180 keep_hourly,
181 /// Delete the keep-daily property
182 keep_daily,
183 /// Delete the keep-weekly property
184 keep_weekly,
185 /// Delete the keep-monthly property
186 keep_monthly,
187 /// Delete the keep-yearly property
188 keep_yearly,
189 }
190
191 #[api(
192 protected: true,
193 input: {
194 properties: {
195 name: {
196 schema: DATASTORE_SCHEMA,
197 },
198 comment: {
199 optional: true,
200 schema: SINGLE_LINE_COMMENT_SCHEMA,
201 },
202 "gc-schedule": {
203 optional: true,
204 schema: GC_SCHEDULE_SCHEMA,
205 },
206 "prune-schedule": {
207 optional: true,
208 schema: PRUNE_SCHEDULE_SCHEMA,
209 },
210 "keep-last": {
211 optional: true,
212 schema: PRUNE_SCHEMA_KEEP_LAST,
213 },
214 "keep-hourly": {
215 optional: true,
216 schema: PRUNE_SCHEMA_KEEP_HOURLY,
217 },
218 "keep-daily": {
219 optional: true,
220 schema: PRUNE_SCHEMA_KEEP_DAILY,
221 },
222 "keep-weekly": {
223 optional: true,
224 schema: PRUNE_SCHEMA_KEEP_WEEKLY,
225 },
226 "keep-monthly": {
227 optional: true,
228 schema: PRUNE_SCHEMA_KEEP_MONTHLY,
229 },
230 "keep-yearly": {
231 optional: true,
232 schema: PRUNE_SCHEMA_KEEP_YEARLY,
233 },
234 delete: {
235 description: "List of properties to delete.",
236 type: Array,
237 optional: true,
238 items: {
239 type: DeletableProperty,
240 }
241 },
242 digest: {
243 optional: true,
244 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
245 },
246 },
247 },
248 access: {
249 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY, false),
250 },
251 )]
252 /// Update datastore config.
253 pub fn update_datastore(
254 name: String,
255 comment: Option<String>,
256 gc_schedule: Option<String>,
257 prune_schedule: Option<String>,
258 keep_last: Option<u64>,
259 keep_hourly: Option<u64>,
260 keep_daily: Option<u64>,
261 keep_weekly: Option<u64>,
262 keep_monthly: Option<u64>,
263 keep_yearly: Option<u64>,
264 delete: Option<Vec<DeletableProperty>>,
265 digest: Option<String>,
266 ) -> Result<(), Error> {
267
268 let _lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
269
270 // pass/compare digest
271 let (mut config, expected_digest) = datastore::config()?;
272
273 if let Some(ref digest) = digest {
274 let digest = proxmox::tools::hex_to_digest(digest)?;
275 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
276 }
277
278 let mut data: datastore::DataStoreConfig = config.lookup("datastore", &name)?;
279
280 if let Some(delete) = delete {
281 for delete_prop in delete {
282 match delete_prop {
283 DeletableProperty::comment => { data.comment = None; },
284 DeletableProperty::gc_schedule => { data.gc_schedule = None; },
285 DeletableProperty::prune_schedule => { data.prune_schedule = None; },
286 DeletableProperty::keep_last => { data.keep_last = None; },
287 DeletableProperty::keep_hourly => { data.keep_hourly = None; },
288 DeletableProperty::keep_daily => { data.keep_daily = None; },
289 DeletableProperty::keep_weekly => { data.keep_weekly = None; },
290 DeletableProperty::keep_monthly => { data.keep_monthly = None; },
291 DeletableProperty::keep_yearly => { data.keep_yearly = None; },
292 }
293 }
294 }
295
296 if let Some(comment) = comment {
297 let comment = comment.trim().to_string();
298 if comment.is_empty() {
299 data.comment = None;
300 } else {
301 data.comment = Some(comment);
302 }
303 }
304
305 let mut gc_schedule_changed = false;
306 if gc_schedule.is_some() {
307 gc_schedule_changed = data.gc_schedule != gc_schedule;
308 data.gc_schedule = gc_schedule;
309 }
310
311 let mut prune_schedule_changed = false;
312 if prune_schedule.is_some() {
313 prune_schedule_changed = data.prune_schedule != prune_schedule;
314 data.prune_schedule = prune_schedule;
315 }
316
317 if keep_last.is_some() { data.keep_last = keep_last; }
318 if keep_hourly.is_some() { data.keep_hourly = keep_hourly; }
319 if keep_daily.is_some() { data.keep_daily = keep_daily; }
320 if keep_weekly.is_some() { data.keep_weekly = keep_weekly; }
321 if keep_monthly.is_some() { data.keep_monthly = keep_monthly; }
322 if keep_yearly.is_some() { data.keep_yearly = keep_yearly; }
323
324 config.set_data(&name, "datastore", &data)?;
325
326 datastore::save_config(&config)?;
327
328 // we want to reset the statefiles, to avoid an immediate action in some cases
329 // (e.g. going from monthly to weekly in the second week of the month)
330 if gc_schedule_changed {
331 crate::config::jobstate::create_state_file("garbage_collection", &name)?;
332 }
333
334 if prune_schedule_changed {
335 crate::config::jobstate::create_state_file("prune", &name)?;
336 }
337
338 Ok(())
339 }
340
341 #[api(
342 protected: true,
343 input: {
344 properties: {
345 name: {
346 schema: DATASTORE_SCHEMA,
347 },
348 digest: {
349 optional: true,
350 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
351 },
352 },
353 },
354 access: {
355 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_ALLOCATE, false),
356 },
357 )]
358 /// Remove a datastore configuration.
359 pub fn delete_datastore(name: String, digest: Option<String>) -> Result<(), Error> {
360
361 let _lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
362
363 let (mut config, expected_digest) = datastore::config()?;
364
365 if let Some(ref digest) = digest {
366 let digest = proxmox::tools::hex_to_digest(digest)?;
367 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
368 }
369
370 match config.sections.get(&name) {
371 Some(_) => { config.sections.remove(&name); },
372 None => bail!("datastore '{}' does not exist.", name),
373 }
374
375 datastore::save_config(&config)?;
376
377 // ignore errors
378 let _ = crate::config::jobstate::remove_state_file("prune", &name);
379 let _ = crate::config::jobstate::remove_state_file("garbage_collection", &name);
380
381 Ok(())
382 }
383
384 const ITEM_ROUTER: Router = Router::new()
385 .get(&API_METHOD_READ_DATASTORE)
386 .put(&API_METHOD_UPDATE_DATASTORE)
387 .delete(&API_METHOD_DELETE_DATASTORE);
388
389 pub const ROUTER: Router = Router::new()
390 .get(&API_METHOD_LIST_DATASTORES)
391 .post(&API_METHOD_CREATE_DATASTORE)
392 .match_all("name", &ITEM_ROUTER);