]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/config/datastore.rs
server: add Datastore.Allocate privilege
[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};
98c259b4 8use proxmox::tools::fs::open_file_locked;
a2479cfa 9
66c49c21 10use crate::api2::types::*;
a2479cfa 11use crate::backup::*;
b93bbab4 12use crate::config::cached_user_info::CachedUserInfo;
67f7ffd0 13use crate::config::datastore::{self, DataStoreConfig, DIR_NAME_SCHEMA};
41bfd249 14use crate::config::acl::{PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY};
567713b4 15
688fbe07
DM
16#[api(
17 input: {
18 properties: {},
19 },
20 returns: {
f3ec5dae 21 description: "List the configured datastores (with config digest).",
688fbe07 22 type: Array,
67f7ffd0 23 items: { type: datastore::DataStoreConfig },
688fbe07 24 },
c0ef209a 25 access: {
b93bbab4 26 permission: &Permission::Anybody,
c0ef209a 27 },
688fbe07
DM
28)]
29/// List all datastores
30pub fn list_datastores(
6049b71f 31 _param: Value,
67f7ffd0
DM
32 mut rpcenv: &mut dyn RpcEnvironment,
33) -> Result<Vec<DataStoreConfig>, Error> {
567713b4 34
d0187a51 35 let (config, digest) = datastore::config()?;
b65eaac6 36
b93bbab4
FG
37 let userid: Userid = rpcenv.get_user().unwrap().parse()?;
38 let user_info = CachedUserInfo::new()?;
67f7ffd0
DM
39
40 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
41
b93bbab4
FG
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())
ea0b8b6e
DM
49}
50
67f7ffd0
DM
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
688fbe07
DM
56#[api(
57 protected: true,
58 input: {
59 properties: {
60 name: {
61 schema: DATASTORE_SCHEMA,
62 },
67f7ffd0
DM
63 path: {
64 schema: DIR_NAME_SCHEMA,
65 },
688fbe07
DM
66 comment: {
67 optional: true,
454c13ed 68 schema: SINGLE_LINE_COMMENT_SCHEMA,
688fbe07 69 },
42fdbe51
DM
70 "gc-schedule": {
71 optional: true,
72 schema: GC_SCHEDULE_SCHEMA,
73 },
67f7ffd0
DM
74 "prune-schedule": {
75 optional: true,
76 schema: PRUNE_SCHEDULE_SCHEMA,
77 },
47785966
HL
78 "verify-schedule": {
79 optional: true,
80 schema: VERIFY_SCHEDULE_SCHEMA,
81 },
67f7ffd0
DM
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,
688fbe07
DM
105 },
106 },
107 },
c0ef209a 108 access: {
41bfd249 109 permission: &Permission::Privilege(&["datastore"], PRIV_DATASTORE_ALLOCATE, false),
c0ef209a 110 },
688fbe07
DM
111)]
112/// Create new datastore config.
67f7ffd0 113pub fn create_datastore(param: Value) -> Result<(), Error> {
ea0b8b6e 114
b56c111e 115 let _lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
652c1190 116
688fbe07 117 let datastore: datastore::DataStoreConfig = serde_json::from_value(param.clone())?;
652c1190 118
d0187a51 119 let (mut config, _digest) = datastore::config()?;
652c1190 120
67f7ffd0
DM
121 if let Some(_) = config.sections.get(&datastore.name) {
122 bail!("datastore '{}' already exists.", datastore.name);
652c1190
DM
123 }
124
688fbe07
DM
125 let path: PathBuf = datastore.path.clone().into();
126
127 let backup_user = crate::backup::backup_user()?;
67f7ffd0 128 let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid)?;
688fbe07 129
67f7ffd0 130 config.set_data(&datastore.name, "datastore", &datastore)?;
652c1190
DM
131
132 datastore::save_config(&config)?;
133
9866de5e 134 crate::config::jobstate::create_state_file("prune", &datastore.name)?;
d7a122a0
DC
135 crate::config::jobstate::create_state_file("garbage_collection", &datastore.name)?;
136 crate::config::jobstate::create_state_file("verify", &datastore.name)?;
9866de5e 137
688fbe07 138 Ok(())
6ce50400
DM
139}
140
c5799e40
DM
141#[api(
142 input: {
143 properties: {
144 name: {
145 schema: DATASTORE_SCHEMA,
146 },
147 },
148 },
f3ec5dae
DM
149 returns: {
150 description: "The datastore configuration (with config digest).",
151 type: datastore::DataStoreConfig,
152 },
c0ef209a
DM
153 access: {
154 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT, false),
155 },
c5799e40
DM
156)]
157/// Read a datastore configuration.
67f7ffd0
DM
158pub fn read_datastore(
159 name: String,
160 mut rpcenv: &mut dyn RpcEnvironment,
161) -> Result<DataStoreConfig, Error> {
c5799e40 162 let (config, digest) = datastore::config()?;
67f7ffd0
DM
163
164 let store_config = config.lookup("datastore", &name)?;
165 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
166
167 Ok(store_config)
c5799e40
DM
168}
169
0a00f6e0
DM
170#[api()]
171#[derive(Serialize, Deserialize)]
42fdbe51 172#[serde(rename_all="kebab-case")]
0a00f6e0
DM
173#[allow(non_camel_case_types)]
174/// Deletable property name
175pub enum DeletableProperty {
176 /// Delete the comment property.
177 comment,
42fdbe51
DM
178 /// Delete the garbage collection schedule.
179 gc_schedule,
67f7ffd0
DM
180 /// Delete the prune job schedule.
181 prune_schedule,
2b67de2e
HL
182 /// Delete the verify schedule property
183 verify_schedule,
67f7ffd0
DM
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,
0a00f6e0
DM
196}
197
c5799e40
DM
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 },
42fdbe51
DM
209 "gc-schedule": {
210 optional: true,
211 schema: GC_SCHEDULE_SCHEMA,
212 },
67f7ffd0
DM
213 "prune-schedule": {
214 optional: true,
215 schema: PRUNE_SCHEDULE_SCHEMA,
216 },
47785966
HL
217 "verify-schedule": {
218 optional: true,
219 schema: VERIFY_SCHEDULE_SCHEMA,
220 },
67f7ffd0
DM
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 },
0a00f6e0
DM
245 delete: {
246 description: "List of properties to delete.",
247 type: Array,
248 optional: true,
249 items: {
250 type: DeletableProperty,
251 }
252 },
002a191a
DM
253 digest: {
254 optional: true,
255 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
256 },
c5799e40
DM
257 },
258 },
c0ef209a 259 access: {
9c7fe29d 260 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY, false),
c0ef209a 261 },
c5799e40 262)]
2ea7bf1b 263/// Update datastore config.
c5799e40
DM
264pub fn update_datastore(
265 name: String,
266 comment: Option<String>,
42fdbe51 267 gc_schedule: Option<String>,
67f7ffd0 268 prune_schedule: Option<String>,
47785966 269 verify_schedule: Option<String>,
872062ee
DM
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>,
0a00f6e0 276 delete: Option<Vec<DeletableProperty>>,
002a191a 277 digest: Option<String>,
c5799e40
DM
278) -> Result<(), Error> {
279
b56c111e 280 let _lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
347834df 281
c5799e40 282 // pass/compare digest
002a191a
DM
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 }
c5799e40
DM
289
290 let mut data: datastore::DataStoreConfig = config.lookup("datastore", &name)?;
291
0a00f6e0
DM
292 if let Some(delete) = delete {
293 for delete_prop in delete {
294 match delete_prop {
295 DeletableProperty::comment => { data.comment = None; },
42fdbe51 296 DeletableProperty::gc_schedule => { data.gc_schedule = None; },
67f7ffd0 297 DeletableProperty::prune_schedule => { data.prune_schedule = None; },
2b67de2e 298 DeletableProperty::verify_schedule => { data.verify_schedule = None; },
67f7ffd0
DM
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; },
0a00f6e0
DM
305 }
306 }
307 }
308
c5799e40
DM
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 }
c5799e40 317
d7a122a0
DC
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
9866de5e
DC
324 let mut prune_schedule_changed = false;
325 if prune_schedule.is_some() {
d7a122a0 326 prune_schedule_changed = data.prune_schedule != prune_schedule;
9866de5e
DC
327 data.prune_schedule = prune_schedule;
328 }
d7a122a0
DC
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 }
67f7ffd0
DM
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; }
42fdbe51 342
c5799e40
DM
343 config.set_data(&name, "datastore", &data)?;
344
345 datastore::save_config(&config)?;
346
d7a122a0 347 // we want to reset the statefiles, to avoid an immediate action in some cases
9866de5e 348 // (e.g. going from monthly to weekly in the second week of the month)
d7a122a0
DC
349 if gc_schedule_changed {
350 crate::config::jobstate::create_state_file("garbage_collection", &name)?;
351 }
352
9866de5e
DC
353 if prune_schedule_changed {
354 crate::config::jobstate::create_state_file("prune", &name)?;
355 }
356
d7a122a0
DC
357 if verify_schedule_changed {
358 crate::config::jobstate::create_state_file("verify", &name)?;
359 }
360
c5799e40
DM
361 Ok(())
362}
363
688fbe07
DM
364#[api(
365 protected: true,
366 input: {
367 properties: {
368 name: {
369 schema: DATASTORE_SCHEMA,
370 },
c0ef209a
DM
371 digest: {
372 optional: true,
373 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
374 },
688fbe07
DM
375 },
376 },
c0ef209a 377 access: {
9c7fe29d 378 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY, false),
c0ef209a 379 },
688fbe07
DM
380)]
381/// Remove a datastore configuration.
c0ef209a 382pub fn delete_datastore(name: String, digest: Option<String>) -> Result<(), Error> {
34d3ba52 383
b56c111e 384 let _lock = open_file_locked(datastore::DATASTORE_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
34d3ba52 385
c0ef209a
DM
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 }
34d3ba52 392
688fbe07
DM
393 match config.sections.get(&name) {
394 Some(_) => { config.sections.remove(&name); },
34d3ba52
DM
395 None => bail!("datastore '{}' does not exist.", name),
396 }
397
398 datastore::save_config(&config)?;
399
d7a122a0
DC
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);
9866de5e 404
688fbe07 405 Ok(())
34d3ba52
DM
406}
407
c5799e40
DM
408const ITEM_ROUTER: Router = Router::new()
409 .get(&API_METHOD_READ_DATASTORE)
410 .put(&API_METHOD_UPDATE_DATASTORE)
411 .delete(&API_METHOD_DELETE_DATASTORE);
412
255f378a 413pub const ROUTER: Router = Router::new()
688fbe07
DM
414 .get(&API_METHOD_LIST_DATASTORES)
415 .post(&API_METHOD_CREATE_DATASTORE)
c5799e40 416 .match_all("name", &ITEM_ROUTER);