]>
Commit | Line | Data |
---|---|---|
a2479cfa | 1 | use std::path::PathBuf; |
6ce50400 | 2 | |
0a00f6e0 | 3 | use ::serde::{Deserialize, Serialize}; |
dc7a5b34 | 4 | use anyhow::Error; |
25877d05 | 5 | use hex::FromHex; |
dc7a5b34 | 6 | use serde_json::Value; |
6ce50400 | 7 | |
dc7a5b34 | 8 | use proxmox_router::{http_bail, Permission, Router, RpcEnvironment, RpcEnvironmentType}; |
8d6425aa | 9 | use proxmox_schema::{api, param_bail, ApiType}; |
6ef1b649 | 10 | use proxmox_section_config::SectionConfigData; |
857f346c | 11 | use proxmox_sys::{task_warn, WorkerTaskContext}; |
b2065dc7 | 12 | |
8cc3760e | 13 | use pbs_api_types::{ |
647186dd DC |
14 | Authid, DataStoreConfig, DataStoreConfigUpdater, DatastoreNotify, DatastoreTuning, |
15 | DATASTORE_SCHEMA, PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY, | |
857f346c | 16 | PROXMOX_CONFIG_DIGEST_SCHEMA, UPID_SCHEMA, |
8cc3760e | 17 | }; |
dc7a5b34 TL |
18 | use pbs_config::BackupLockGuard; |
19 | use pbs_datastore::chunk_store::ChunkStore; | |
c23192d3 | 20 | |
ca1da2cb HL |
21 | use crate::api2::admin::{ |
22 | prune::list_prune_jobs, sync::list_sync_jobs, verify::list_verification_jobs, | |
23 | }; | |
24 | use crate::api2::config::prune::delete_prune_job; | |
eb1c59cc | 25 | use crate::api2::config::sync::delete_sync_job; |
dc7a5b34 | 26 | use crate::api2::config::tape_backup_job::{delete_tape_backup_job, list_tape_backup_jobs}; |
eb1c59cc | 27 | use crate::api2::config::verify::delete_verification_job; |
ba3d7e19 | 28 | use pbs_config::CachedUserInfo; |
c8449217 | 29 | |
b9700a9f | 30 | use proxmox_rest_server::WorkerTask; |
e7d4be9d | 31 | |
b9700a9f | 32 | use crate::server::jobstate; |
567713b4 | 33 | |
688fbe07 DM |
34 | #[api( |
35 | input: { | |
36 | properties: {}, | |
37 | }, | |
38 | returns: { | |
f3ec5dae | 39 | description: "List the configured datastores (with config digest).", |
688fbe07 | 40 | type: Array, |
68e77657 | 41 | items: { type: DataStoreConfig }, |
688fbe07 | 42 | }, |
c0ef209a | 43 | access: { |
b93bbab4 | 44 | permission: &Permission::Anybody, |
c0ef209a | 45 | }, |
688fbe07 DM |
46 | )] |
47 | /// List all datastores | |
48 | pub fn list_datastores( | |
6049b71f | 49 | _param: Value, |
41c1a179 | 50 | rpcenv: &mut dyn RpcEnvironment, |
67f7ffd0 | 51 | ) -> Result<Vec<DataStoreConfig>, Error> { |
e7d4be9d | 52 | let (config, digest) = pbs_config::datastore::config()?; |
b65eaac6 | 53 | |
e6dc35ac | 54 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
b93bbab4 | 55 | let user_info = CachedUserInfo::new()?; |
67f7ffd0 | 56 | |
16f6766a | 57 | rpcenv["digest"] = hex::encode(digest).into(); |
67f7ffd0 | 58 | |
dc7a5b34 | 59 | let list: Vec<DataStoreConfig> = config.convert_to_typed_array("datastore")?; |
b93bbab4 | 60 | let filter_by_privs = |store: &DataStoreConfig| { |
e6dc35ac | 61 | let user_privs = user_info.lookup_privs(&auth_id, &["datastore", &store.name]); |
b93bbab4 FG |
62 | (user_privs & PRIV_DATASTORE_AUDIT) != 0 |
63 | }; | |
64 | ||
65 | Ok(list.into_iter().filter(filter_by_privs).collect()) | |
ea0b8b6e DM |
66 | } |
67 | ||
4708f4fc | 68 | pub(crate) fn do_create_datastore( |
7526d864 | 69 | _lock: BackupLockGuard, |
4708f4fc DC |
70 | mut config: SectionConfigData, |
71 | datastore: DataStoreConfig, | |
c8449217 | 72 | worker: Option<&dyn WorkerTaskContext>, |
4708f4fc DC |
73 | ) -> Result<(), Error> { |
74 | let path: PathBuf = datastore.path.clone().into(); | |
75 | ||
647186dd DC |
76 | let tuning: DatastoreTuning = serde_json::from_value( |
77 | DatastoreTuning::API_SCHEMA | |
78 | .parse_property_string(datastore.tuning.as_deref().unwrap_or(""))?, | |
79 | )?; | |
21211748 | 80 | let backup_user = pbs_config::backup_user()?; |
dc7a5b34 TL |
81 | let _store = ChunkStore::create( |
82 | &datastore.name, | |
83 | path, | |
84 | backup_user.uid, | |
85 | backup_user.gid, | |
86 | worker, | |
647186dd | 87 | tuning.sync_level.unwrap_or_default(), |
dc7a5b34 | 88 | )?; |
4708f4fc DC |
89 | |
90 | config.set_data(&datastore.name, "datastore", &datastore)?; | |
91 | ||
e7d4be9d | 92 | pbs_config::datastore::save_config(&config)?; |
4708f4fc DC |
93 | |
94 | jobstate::create_state_file("prune", &datastore.name)?; | |
95 | jobstate::create_state_file("garbage_collection", &datastore.name)?; | |
96 | ||
97 | Ok(()) | |
98 | } | |
67f7ffd0 | 99 | |
688fbe07 DM |
100 | #[api( |
101 | protected: true, | |
102 | input: { | |
103 | properties: { | |
68e77657 DM |
104 | config: { |
105 | type: DataStoreConfig, | |
106 | flatten: true, | |
688fbe07 DM |
107 | }, |
108 | }, | |
109 | }, | |
c0ef209a | 110 | access: { |
41bfd249 | 111 | permission: &Permission::Privilege(&["datastore"], PRIV_DATASTORE_ALLOCATE, false), |
c0ef209a | 112 | }, |
688fbe07 DM |
113 | )] |
114 | /// Create new datastore config. | |
4708f4fc | 115 | pub fn create_datastore( |
68e77657 | 116 | config: DataStoreConfig, |
4708f4fc DC |
117 | rpcenv: &mut dyn RpcEnvironment, |
118 | ) -> Result<String, Error> { | |
e7d4be9d | 119 | let lock = pbs_config::datastore::lock_config()?; |
652c1190 | 120 | |
e7d4be9d | 121 | let (section_config, _digest) = pbs_config::datastore::config()?; |
652c1190 | 122 | |
68e77657 | 123 | if section_config.sections.get(&config.name).is_some() { |
8d6425aa | 124 | param_bail!("name", "datastore '{}' already exists.", config.name); |
652c1190 DM |
125 | } |
126 | ||
4708f4fc | 127 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
bfa942c0 | 128 | let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI; |
9866de5e | 129 | |
4708f4fc DC |
130 | WorkerTask::new_thread( |
131 | "create-datastore", | |
68e77657 | 132 | Some(config.name.to_string()), |
049a22a3 | 133 | auth_id.to_string(), |
bfa942c0 | 134 | to_stdout, |
dc7a5b34 | 135 | move |worker| do_create_datastore(lock, section_config, config, Some(&worker)), |
4708f4fc | 136 | ) |
6ce50400 DM |
137 | } |
138 | ||
c5799e40 DM |
139 | #[api( |
140 | input: { | |
141 | properties: { | |
142 | name: { | |
143 | schema: DATASTORE_SCHEMA, | |
144 | }, | |
145 | }, | |
146 | }, | |
68e77657 | 147 | returns: { type: DataStoreConfig }, |
c0ef209a DM |
148 | access: { |
149 | permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_AUDIT, false), | |
150 | }, | |
c5799e40 DM |
151 | )] |
152 | /// Read a datastore configuration. | |
67f7ffd0 DM |
153 | pub fn read_datastore( |
154 | name: String, | |
41c1a179 | 155 | rpcenv: &mut dyn RpcEnvironment, |
67f7ffd0 | 156 | ) -> Result<DataStoreConfig, Error> { |
e7d4be9d | 157 | let (config, digest) = pbs_config::datastore::config()?; |
67f7ffd0 DM |
158 | |
159 | let store_config = config.lookup("datastore", &name)?; | |
16f6766a | 160 | rpcenv["digest"] = hex::encode(digest).into(); |
67f7ffd0 DM |
161 | |
162 | Ok(store_config) | |
c5799e40 DM |
163 | } |
164 | ||
0a00f6e0 DM |
165 | #[api()] |
166 | #[derive(Serialize, Deserialize)] | |
dc7a5b34 | 167 | #[serde(rename_all = "kebab-case")] |
0a00f6e0 DM |
168 | /// Deletable property name |
169 | pub enum DeletableProperty { | |
170 | /// Delete the comment property. | |
a2055c38 | 171 | Comment, |
42fdbe51 | 172 | /// Delete the garbage collection schedule. |
a2055c38 | 173 | GcSchedule, |
67f7ffd0 | 174 | /// Delete the prune job schedule. |
a2055c38 | 175 | PruneSchedule, |
67f7ffd0 | 176 | /// Delete the keep-last property |
a2055c38 | 177 | KeepLast, |
67f7ffd0 | 178 | /// Delete the keep-hourly property |
a2055c38 | 179 | KeepHourly, |
67f7ffd0 | 180 | /// Delete the keep-daily property |
a2055c38 | 181 | KeepDaily, |
67f7ffd0 | 182 | /// Delete the keep-weekly property |
a2055c38 | 183 | KeepWeekly, |
67f7ffd0 | 184 | /// Delete the keep-monthly property |
a2055c38 | 185 | KeepMonthly, |
67f7ffd0 | 186 | /// Delete the keep-yearly property |
a2055c38 | 187 | KeepYearly, |
ad53c1d6 | 188 | /// Delete the verify-new property |
a2055c38 | 189 | VerifyNew, |
6e545d00 | 190 | /// Delete the notify-user property |
a2055c38 | 191 | NotifyUser, |
6e545d00 | 192 | /// Delete the notify property |
a2055c38 | 193 | Notify, |
fef61684 | 194 | /// Delete the tuning property |
a2055c38 | 195 | Tuning, |
758c6ed5 | 196 | /// Delete the maintenance-mode property |
a2055c38 | 197 | MaintenanceMode, |
0a00f6e0 DM |
198 | } |
199 | ||
c5799e40 DM |
200 | #[api( |
201 | protected: true, | |
202 | input: { | |
203 | properties: { | |
204 | name: { | |
205 | schema: DATASTORE_SCHEMA, | |
206 | }, | |
a8a20e92 DM |
207 | update: { |
208 | type: DataStoreConfigUpdater, | |
209 | flatten: true, | |
ad53c1d6 | 210 | }, |
0a00f6e0 DM |
211 | delete: { |
212 | description: "List of properties to delete.", | |
213 | type: Array, | |
214 | optional: true, | |
215 | items: { | |
216 | type: DeletableProperty, | |
217 | } | |
218 | }, | |
002a191a DM |
219 | digest: { |
220 | optional: true, | |
221 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
222 | }, | |
c5799e40 DM |
223 | }, |
224 | }, | |
c0ef209a | 225 | access: { |
9c7fe29d | 226 | permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_MODIFY, false), |
c0ef209a | 227 | }, |
c5799e40 | 228 | )] |
2ea7bf1b | 229 | /// Update datastore config. |
c5799e40 | 230 | pub fn update_datastore( |
a8a20e92 | 231 | update: DataStoreConfigUpdater, |
c5799e40 | 232 | name: String, |
0a00f6e0 | 233 | delete: Option<Vec<DeletableProperty>>, |
002a191a | 234 | digest: Option<String>, |
c5799e40 | 235 | ) -> Result<(), Error> { |
e7d4be9d | 236 | let _lock = pbs_config::datastore::lock_config()?; |
347834df | 237 | |
c5799e40 | 238 | // pass/compare digest |
e7d4be9d | 239 | let (mut config, expected_digest) = pbs_config::datastore::config()?; |
002a191a DM |
240 | |
241 | if let Some(ref digest) = digest { | |
25877d05 | 242 | let digest = <[u8; 32]>::from_hex(digest)?; |
002a191a DM |
243 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; |
244 | } | |
c5799e40 | 245 | |
68e77657 | 246 | let mut data: DataStoreConfig = config.lookup("datastore", &name)?; |
c5799e40 | 247 | |
dc7a5b34 | 248 | if let Some(delete) = delete { |
0a00f6e0 DM |
249 | for delete_prop in delete { |
250 | match delete_prop { | |
a2055c38 | 251 | DeletableProperty::Comment => { |
dc7a5b34 TL |
252 | data.comment = None; |
253 | } | |
a2055c38 | 254 | DeletableProperty::GcSchedule => { |
dc7a5b34 TL |
255 | data.gc_schedule = None; |
256 | } | |
a2055c38 | 257 | DeletableProperty::PruneSchedule => { |
dc7a5b34 TL |
258 | data.prune_schedule = None; |
259 | } | |
a2055c38 | 260 | DeletableProperty::KeepLast => { |
13477966 | 261 | data.keep.keep_last = None; |
dc7a5b34 | 262 | } |
a2055c38 | 263 | DeletableProperty::KeepHourly => { |
13477966 | 264 | data.keep.keep_hourly = None; |
dc7a5b34 | 265 | } |
a2055c38 | 266 | DeletableProperty::KeepDaily => { |
13477966 | 267 | data.keep.keep_daily = None; |
dc7a5b34 | 268 | } |
a2055c38 | 269 | DeletableProperty::KeepWeekly => { |
13477966 | 270 | data.keep.keep_weekly = None; |
dc7a5b34 | 271 | } |
a2055c38 | 272 | DeletableProperty::KeepMonthly => { |
13477966 | 273 | data.keep.keep_monthly = None; |
dc7a5b34 | 274 | } |
a2055c38 | 275 | DeletableProperty::KeepYearly => { |
13477966 | 276 | data.keep.keep_yearly = None; |
dc7a5b34 | 277 | } |
a2055c38 | 278 | DeletableProperty::VerifyNew => { |
dc7a5b34 TL |
279 | data.verify_new = None; |
280 | } | |
a2055c38 | 281 | DeletableProperty::Notify => { |
dc7a5b34 TL |
282 | data.notify = None; |
283 | } | |
a2055c38 | 284 | DeletableProperty::NotifyUser => { |
dc7a5b34 TL |
285 | data.notify_user = None; |
286 | } | |
a2055c38 | 287 | DeletableProperty::Tuning => { |
dc7a5b34 TL |
288 | data.tuning = None; |
289 | } | |
a2055c38 | 290 | DeletableProperty::MaintenanceMode => { |
dc7a5b34 TL |
291 | data.maintenance_mode = None; |
292 | } | |
0a00f6e0 DM |
293 | } |
294 | } | |
295 | } | |
296 | ||
a8a20e92 | 297 | if let Some(comment) = update.comment { |
c5799e40 DM |
298 | let comment = comment.trim().to_string(); |
299 | if comment.is_empty() { | |
300 | data.comment = None; | |
301 | } else { | |
302 | data.comment = Some(comment); | |
303 | } | |
304 | } | |
c5799e40 | 305 | |
d7a122a0 | 306 | let mut gc_schedule_changed = false; |
a8a20e92 DM |
307 | if update.gc_schedule.is_some() { |
308 | gc_schedule_changed = data.gc_schedule != update.gc_schedule; | |
309 | data.gc_schedule = update.gc_schedule; | |
d7a122a0 DC |
310 | } |
311 | ||
aa32a461 WB |
312 | macro_rules! prune_disabled { |
313 | ($(($param:literal, $($member:tt)+)),+) => { | |
314 | $( | |
315 | if update.$($member)+.is_some() { | |
316 | param_bail!( | |
317 | $param, | |
318 | "datastore prune settings have been replaced by prune jobs", | |
319 | ); | |
320 | } | |
321 | )+ | |
322 | }; | |
dc7a5b34 | 323 | } |
aa32a461 WB |
324 | prune_disabled! { |
325 | ("keep-last", keep.keep_last), | |
326 | ("keep-hourly", keep.keep_hourly), | |
327 | ("keep-daily", keep.keep_daily), | |
328 | ("keep-weekly", keep.keep_weekly), | |
329 | ("keep-monthly", keep.keep_monthly), | |
330 | ("keep-yearly", keep.keep_yearly), | |
331 | ("prune-schedule", prune_schedule) | |
dc7a5b34 | 332 | } |
42fdbe51 | 333 | |
a8a20e92 | 334 | if let Some(notify_str) = update.notify { |
9fa3026a | 335 | let value = DatastoreNotify::API_SCHEMA.parse_property_string(¬ify_str)?; |
c26c9390 | 336 | let notify: DatastoreNotify = serde_json::from_value(value)?; |
dc7a5b34 TL |
337 | if let DatastoreNotify { |
338 | gc: None, | |
339 | verify: None, | |
340 | sync: None, | |
cf91a072 | 341 | prune: None, |
dc7a5b34 TL |
342 | } = notify |
343 | { | |
c26c9390 DM |
344 | data.notify = None; |
345 | } else { | |
346 | data.notify = Some(notify_str); | |
347 | } | |
348 | } | |
dc7a5b34 TL |
349 | if update.verify_new.is_some() { |
350 | data.verify_new = update.verify_new; | |
351 | } | |
ad53c1d6 | 352 | |
dc7a5b34 TL |
353 | if update.notify_user.is_some() { |
354 | data.notify_user = update.notify_user; | |
355 | } | |
6e545d00 | 356 | |
dc7a5b34 TL |
357 | if update.tuning.is_some() { |
358 | data.tuning = update.tuning; | |
359 | } | |
fef61684 | 360 | |
dc7a5b34 TL |
361 | if update.maintenance_mode.is_some() { |
362 | data.maintenance_mode = update.maintenance_mode; | |
363 | } | |
758c6ed5 | 364 | |
c5799e40 DM |
365 | config.set_data(&name, "datastore", &data)?; |
366 | ||
e7d4be9d | 367 | pbs_config::datastore::save_config(&config)?; |
c5799e40 | 368 | |
d7a122a0 | 369 | // we want to reset the statefiles, to avoid an immediate action in some cases |
9866de5e | 370 | // (e.g. going from monthly to weekly in the second week of the month) |
d7a122a0 | 371 | if gc_schedule_changed { |
a588b679 | 372 | jobstate::update_job_last_run_time("garbage_collection", &name)?; |
d7a122a0 DC |
373 | } |
374 | ||
c5799e40 DM |
375 | Ok(()) |
376 | } | |
377 | ||
688fbe07 DM |
378 | #[api( |
379 | protected: true, | |
380 | input: { | |
381 | properties: { | |
382 | name: { | |
383 | schema: DATASTORE_SCHEMA, | |
384 | }, | |
eb1c59cc HL |
385 | "keep-job-configs": { |
386 | description: "If enabled, the job configurations related to this datastore will be kept.", | |
387 | type: bool, | |
388 | optional: true, | |
389 | default: false, | |
390 | }, | |
857f346c WB |
391 | "destroy-data": { |
392 | description: "Delete the datastore's underlying contents", | |
393 | optional: true, | |
394 | type: bool, | |
395 | default: false, | |
396 | }, | |
c0ef209a DM |
397 | digest: { |
398 | optional: true, | |
399 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
400 | }, | |
688fbe07 DM |
401 | }, |
402 | }, | |
c0ef209a | 403 | access: { |
92dd02aa | 404 | permission: &Permission::Privilege(&["datastore", "{name}"], PRIV_DATASTORE_ALLOCATE, false), |
c0ef209a | 405 | }, |
857f346c WB |
406 | returns: { |
407 | schema: UPID_SCHEMA, | |
408 | }, | |
688fbe07 | 409 | )] |
857f346c | 410 | /// Remove a datastore configuration and optionally delete all its contents. |
eb1c59cc HL |
411 | pub async fn delete_datastore( |
412 | name: String, | |
413 | keep_job_configs: bool, | |
857f346c | 414 | destroy_data: bool, |
eb1c59cc HL |
415 | digest: Option<String>, |
416 | rpcenv: &mut dyn RpcEnvironment, | |
857f346c | 417 | ) -> Result<String, Error> { |
e7d4be9d | 418 | let _lock = pbs_config::datastore::lock_config()?; |
34d3ba52 | 419 | |
857f346c | 420 | let (config, expected_digest) = pbs_config::datastore::config()?; |
c0ef209a DM |
421 | |
422 | if let Some(ref digest) = digest { | |
25877d05 | 423 | let digest = <[u8; 32]>::from_hex(digest)?; |
c0ef209a DM |
424 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; |
425 | } | |
34d3ba52 | 426 | |
857f346c WB |
427 | if !config.sections.contains_key(&name) { |
428 | http_bail!(NOT_FOUND, "datastore '{}' does not exist.", name); | |
34d3ba52 DM |
429 | } |
430 | ||
eb1c59cc HL |
431 | if !keep_job_configs { |
432 | for job in list_verification_jobs(Some(name.clone()), Value::Null, rpcenv)? { | |
433 | delete_verification_job(job.config.id, None, rpcenv)? | |
434 | } | |
435 | for job in list_sync_jobs(Some(name.clone()), Value::Null, rpcenv)? { | |
436 | delete_sync_job(job.config.id, None, rpcenv)? | |
437 | } | |
ca1da2cb HL |
438 | for job in list_prune_jobs(Some(name.clone()), Value::Null, rpcenv)? { |
439 | delete_prune_job(job.config.id, None, rpcenv)? | |
440 | } | |
49f44ced | 441 | |
f6d6b5a3 HL |
442 | let (mut tree, _digest) = pbs_config::acl::config()?; |
443 | tree.delete_node(&format!("/datastore/{}", name)); | |
444 | pbs_config::acl::save_config(&tree)?; | |
445 | ||
49f44ced | 446 | let tape_jobs = list_tape_backup_jobs(Value::Null, rpcenv)?; |
dc7a5b34 TL |
447 | for job_config in tape_jobs |
448 | .into_iter() | |
449 | .filter(|config| config.setup.store == name) | |
450 | { | |
49f44ced DC |
451 | delete_tape_backup_job(job_config.id, None, rpcenv)?; |
452 | } | |
eb1c59cc HL |
453 | } |
454 | ||
857f346c WB |
455 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
456 | let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI; | |
34d3ba52 | 457 | |
857f346c WB |
458 | let upid = WorkerTask::new_thread( |
459 | "delete-datastore", | |
460 | Some(name.clone()), | |
461 | auth_id.to_string(), | |
462 | to_stdout, | |
463 | move |worker| { | |
464 | pbs_datastore::DataStore::destroy(&name, destroy_data, &worker)?; | |
9866de5e | 465 | |
857f346c WB |
466 | // ignore errors |
467 | let _ = jobstate::remove_state_file("prune", &name); | |
468 | let _ = jobstate::remove_state_file("garbage_collection", &name); | |
062cf75c | 469 | |
857f346c WB |
470 | if let Err(err) = |
471 | proxmox_async::runtime::block_on(crate::server::notify_datastore_removed()) | |
472 | { | |
473 | task_warn!(worker, "failed to notify after datastore removal: {err}"); | |
474 | } | |
475 | ||
476 | Ok(()) | |
477 | }, | |
478 | )?; | |
479 | ||
480 | Ok(upid) | |
34d3ba52 DM |
481 | } |
482 | ||
c5799e40 DM |
483 | const ITEM_ROUTER: Router = Router::new() |
484 | .get(&API_METHOD_READ_DATASTORE) | |
485 | .put(&API_METHOD_UPDATE_DATASTORE) | |
486 | .delete(&API_METHOD_DELETE_DATASTORE); | |
487 | ||
255f378a | 488 | pub const ROUTER: Router = Router::new() |
688fbe07 DM |
489 | .get(&API_METHOD_LIST_DATASTORES) |
490 | .post(&API_METHOD_CREATE_DATASTORE) | |
c5799e40 | 491 | .match_all("name", &ITEM_ROUTER); |