]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/config/datastore.rs
update to first proxmox crate split
[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_router::{Router, RpcEnvironment, RpcEnvironmentType, Permission};
8 use proxmox_schema::{api, ApiType, parse_property_string};
9 use proxmox_section_config::SectionConfigData;
10
11 use pbs_datastore::chunk_store::ChunkStore;
12 use pbs_config::BackupLockGuard;
13 use pbs_api_types::{
14 Authid, DatastoreNotify,
15 DATASTORE_SCHEMA, PROXMOX_CONFIG_DIGEST_SCHEMA,
16 PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY,
17 DataStoreConfig, DataStoreConfigUpdater,
18 };
19
20 use crate::api2::config::sync::delete_sync_job;
21 use crate::api2::config::verify::delete_verification_job;
22 use crate::api2::config::tape_backup_job::{list_tape_backup_jobs, delete_tape_backup_job};
23 use crate::api2::admin::{
24 sync::list_sync_jobs,
25 verify::list_verification_jobs,
26 };
27 use pbs_config::CachedUserInfo;
28 use pbs_tools::task::WorkerTaskContext;
29
30 use proxmox_rest_server::WorkerTask;
31
32 use crate::server::jobstate;
33
34 #[api(
35 input: {
36 properties: {},
37 },
38 returns: {
39 description: "List the configured datastores (with config digest).",
40 type: Array,
41 items: { type: DataStoreConfig },
42 },
43 access: {
44 permission: &Permission::Anybody,
45 },
46 )]
47 /// List all datastores
48 pub fn list_datastores(
49 _param: Value,
50 mut rpcenv: &mut dyn RpcEnvironment,
51 ) -> Result<Vec<DataStoreConfig>, Error> {
52
53 let (config, digest) = pbs_config::datastore::config()?;
54
55 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
56 let user_info = CachedUserInfo::new()?;
57
58 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
59
60 let list:Vec<DataStoreConfig> = config.convert_to_typed_array("datastore")?;
61 let filter_by_privs = |store: &DataStoreConfig| {
62 let user_privs = user_info.lookup_privs(&auth_id, &["datastore", &store.name]);
63 (user_privs & PRIV_DATASTORE_AUDIT) != 0
64 };
65
66 Ok(list.into_iter().filter(filter_by_privs).collect())
67 }
68
69 pub(crate) fn do_create_datastore(
70 _lock: BackupLockGuard,
71 mut config: SectionConfigData,
72 datastore: DataStoreConfig,
73 worker: Option<&dyn WorkerTaskContext>,
74 ) -> Result<(), Error> {
75 let path: PathBuf = datastore.path.clone().into();
76
77 let backup_user = pbs_config::backup_user()?;
78 let _store = ChunkStore::create(&datastore.name, path, backup_user.uid, backup_user.gid, worker)?;
79
80 config.set_data(&datastore.name, "datastore", &datastore)?;
81
82 pbs_config::datastore::save_config(&config)?;
83
84 jobstate::create_state_file("prune", &datastore.name)?;
85 jobstate::create_state_file("garbage_collection", &datastore.name)?;
86
87 Ok(())
88 }
89
90 #[api(
91 protected: true,
92 input: {
93 properties: {
94 config: {
95 type: DataStoreConfig,
96 flatten: true,
97 },
98 },
99 },
100 access: {
101 permission: &Permission::Privilege(&["datastore"], PRIV_DATASTORE_ALLOCATE, false),
102 },
103 )]
104 /// Create new datastore config.
105 pub fn create_datastore(
106 config: DataStoreConfig,
107 rpcenv: &mut dyn RpcEnvironment,
108 ) -> Result<String, Error> {
109
110 let lock = pbs_config::datastore::lock_config()?;
111
112 let (section_config, _digest) = pbs_config::datastore::config()?;
113
114 if section_config.sections.get(&config.name).is_some() {
115 bail!("datastore '{}' already exists.", config.name);
116 }
117
118 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
119 let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI;
120
121 WorkerTask::new_thread(
122 "create-datastore",
123 Some(config.name.to_string()),
124 auth_id.to_string(),
125 to_stdout,
126 move |worker| do_create_datastore(lock, section_config, config, Some(&worker)),
127 )
128 }
129
130 #[api(
131 input: {
132 properties: {
133 name: {
134 schema: DATASTORE_SCHEMA,
135 },
136 },
137 },
138 returns: { type: DataStoreConfig },
139 access: {
140 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT, false),
141 },
142 )]
143 /// Read a datastore configuration.
144 pub fn read_datastore(
145 name: String,
146 mut rpcenv: &mut dyn RpcEnvironment,
147 ) -> Result<DataStoreConfig, Error> {
148 let (config, digest) = pbs_config::datastore::config()?;
149
150 let store_config = config.lookup("datastore", &name)?;
151 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
152
153 Ok(store_config)
154 }
155
156 #[api()]
157 #[derive(Serialize, Deserialize)]
158 #[serde(rename_all="kebab-case")]
159 #[allow(non_camel_case_types)]
160 /// Deletable property name
161 pub enum DeletableProperty {
162 /// Delete the comment property.
163 comment,
164 /// Delete the garbage collection schedule.
165 gc_schedule,
166 /// Delete the prune job schedule.
167 prune_schedule,
168 /// Delete the keep-last property
169 keep_last,
170 /// Delete the keep-hourly property
171 keep_hourly,
172 /// Delete the keep-daily property
173 keep_daily,
174 /// Delete the keep-weekly property
175 keep_weekly,
176 /// Delete the keep-monthly property
177 keep_monthly,
178 /// Delete the keep-yearly property
179 keep_yearly,
180 /// Delete the verify-new property
181 verify_new,
182 /// Delete the notify-user property
183 notify_user,
184 /// Delete the notify property
185 notify,
186 }
187
188 #[api(
189 protected: true,
190 input: {
191 properties: {
192 name: {
193 schema: DATASTORE_SCHEMA,
194 },
195 update: {
196 type: DataStoreConfigUpdater,
197 flatten: true,
198 },
199 delete: {
200 description: "List of properties to delete.",
201 type: Array,
202 optional: true,
203 items: {
204 type: DeletableProperty,
205 }
206 },
207 digest: {
208 optional: true,
209 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
210 },
211 },
212 },
213 access: {
214 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY, false),
215 },
216 )]
217 /// Update datastore config.
218 pub fn update_datastore(
219 update: DataStoreConfigUpdater,
220 name: String,
221 delete: Option<Vec<DeletableProperty>>,
222 digest: Option<String>,
223 ) -> Result<(), Error> {
224
225 let _lock = pbs_config::datastore::lock_config()?;
226
227 // pass/compare digest
228 let (mut config, expected_digest) = pbs_config::datastore::config()?;
229
230 if let Some(ref digest) = digest {
231 let digest = proxmox::tools::hex_to_digest(digest)?;
232 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
233 }
234
235 let mut data: DataStoreConfig = config.lookup("datastore", &name)?;
236
237 if let Some(delete) = delete {
238 for delete_prop in delete {
239 match delete_prop {
240 DeletableProperty::comment => { data.comment = None; },
241 DeletableProperty::gc_schedule => { data.gc_schedule = None; },
242 DeletableProperty::prune_schedule => { data.prune_schedule = None; },
243 DeletableProperty::keep_last => { data.keep_last = None; },
244 DeletableProperty::keep_hourly => { data.keep_hourly = None; },
245 DeletableProperty::keep_daily => { data.keep_daily = None; },
246 DeletableProperty::keep_weekly => { data.keep_weekly = None; },
247 DeletableProperty::keep_monthly => { data.keep_monthly = None; },
248 DeletableProperty::keep_yearly => { data.keep_yearly = None; },
249 DeletableProperty::verify_new => { data.verify_new = None; },
250 DeletableProperty::notify => { data.notify = None; },
251 DeletableProperty::notify_user => { data.notify_user = None; },
252 }
253 }
254 }
255
256 if let Some(comment) = update.comment {
257 let comment = comment.trim().to_string();
258 if comment.is_empty() {
259 data.comment = None;
260 } else {
261 data.comment = Some(comment);
262 }
263 }
264
265 let mut gc_schedule_changed = false;
266 if update.gc_schedule.is_some() {
267 gc_schedule_changed = data.gc_schedule != update.gc_schedule;
268 data.gc_schedule = update.gc_schedule;
269 }
270
271 let mut prune_schedule_changed = false;
272 if update.prune_schedule.is_some() {
273 prune_schedule_changed = data.prune_schedule != update.prune_schedule;
274 data.prune_schedule = update.prune_schedule;
275 }
276
277 if update.keep_last.is_some() { data.keep_last = update.keep_last; }
278 if update.keep_hourly.is_some() { data.keep_hourly = update.keep_hourly; }
279 if update.keep_daily.is_some() { data.keep_daily = update.keep_daily; }
280 if update.keep_weekly.is_some() { data.keep_weekly = update.keep_weekly; }
281 if update.keep_monthly.is_some() { data.keep_monthly = update.keep_monthly; }
282 if update.keep_yearly.is_some() { data.keep_yearly = update.keep_yearly; }
283
284 if let Some(notify_str) = update.notify {
285 let value = parse_property_string(&notify_str, &DatastoreNotify::API_SCHEMA)?;
286 let notify: DatastoreNotify = serde_json::from_value(value)?;
287 if let DatastoreNotify { gc: None, verify: None, sync: None } = notify {
288 data.notify = None;
289 } else {
290 data.notify = Some(notify_str);
291 }
292 }
293 if update.verify_new.is_some() { data.verify_new = update.verify_new; }
294
295 if update.notify_user.is_some() { data.notify_user = update.notify_user; }
296
297 config.set_data(&name, "datastore", &data)?;
298
299 pbs_config::datastore::save_config(&config)?;
300
301 // we want to reset the statefiles, to avoid an immediate action in some cases
302 // (e.g. going from monthly to weekly in the second week of the month)
303 if gc_schedule_changed {
304 jobstate::update_job_last_run_time("garbage_collection", &name)?;
305 }
306
307 if prune_schedule_changed {
308 jobstate::update_job_last_run_time("prune", &name)?;
309 }
310
311 Ok(())
312 }
313
314 #[api(
315 protected: true,
316 input: {
317 properties: {
318 name: {
319 schema: DATASTORE_SCHEMA,
320 },
321 "keep-job-configs": {
322 description: "If enabled, the job configurations related to this datastore will be kept.",
323 type: bool,
324 optional: true,
325 default: false,
326 },
327 digest: {
328 optional: true,
329 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
330 },
331 },
332 },
333 access: {
334 permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_ALLOCATE, false),
335 },
336 )]
337 /// Remove a datastore configuration.
338 pub async fn delete_datastore(
339 name: String,
340 keep_job_configs: bool,
341 digest: Option<String>,
342 rpcenv: &mut dyn RpcEnvironment,
343 ) -> Result<(), Error> {
344
345 let _lock = pbs_config::datastore::lock_config()?;
346
347 let (mut config, expected_digest) = pbs_config::datastore::config()?;
348
349 if let Some(ref digest) = digest {
350 let digest = proxmox::tools::hex_to_digest(digest)?;
351 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
352 }
353
354 match config.sections.get(&name) {
355 Some(_) => { config.sections.remove(&name); },
356 None => bail!("datastore '{}' does not exist.", name),
357 }
358
359 if !keep_job_configs {
360 for job in list_verification_jobs(Some(name.clone()), Value::Null, rpcenv)? {
361 delete_verification_job(job.config.id, None, rpcenv)?
362 }
363 for job in list_sync_jobs(Some(name.clone()), Value::Null, rpcenv)? {
364 delete_sync_job(job.config.id, None, rpcenv)?
365 }
366
367 let tape_jobs = list_tape_backup_jobs(Value::Null, rpcenv)?;
368 for job_config in tape_jobs.into_iter().filter(|config| config.setup.store == name) {
369 delete_tape_backup_job(job_config.id, None, rpcenv)?;
370 }
371 }
372
373 pbs_config::datastore::save_config(&config)?;
374
375 // ignore errors
376 let _ = jobstate::remove_state_file("prune", &name);
377 let _ = jobstate::remove_state_file("garbage_collection", &name);
378
379 crate::server::notify_datastore_removed().await?;
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);