]>
Commit | Line | Data |
---|---|---|
0b90c67f | 1 | use anyhow::{bail, Error}; |
be327dbc DM |
2 | use serde_json::Value; |
3 | use ::serde::{Deserialize, Serialize}; | |
4 | ||
0b90c67f | 5 | use proxmox::api::{api, Router, RpcEnvironment, Permission}; |
e3619d41 DM |
6 | |
7 | use pbs_api_types::{ | |
cdc83c4e DM |
8 | Authid, TapeBackupJobConfig, TapeBackupJobConfigUpdater, |
9 | JOB_ID_SCHEMA, PROXMOX_CONFIG_DIGEST_SCHEMA, | |
8cc3760e | 10 | PRIV_TAPE_AUDIT, PRIV_TAPE_MODIFY, |
e3619d41 | 11 | }; |
be327dbc | 12 | |
8cc3760e | 13 | use crate::config::cached_user_info::CachedUserInfo; |
be327dbc DM |
14 | |
15 | #[api( | |
16 | input: { | |
17 | properties: {}, | |
18 | }, | |
19 | returns: { | |
20 | description: "List configured jobs.", | |
21 | type: Array, | |
22 | items: { type: TapeBackupJobConfig }, | |
23 | }, | |
396fd747 DM |
24 | access: { |
25 | description: "List configured tape jobs filtered by Tape.Audit privileges", | |
26 | permission: &Permission::Anybody, | |
27 | }, | |
be327dbc DM |
28 | )] |
29 | /// List all tape backup jobs | |
30 | pub fn list_tape_backup_jobs( | |
31 | _param: Value, | |
32 | mut rpcenv: &mut dyn RpcEnvironment, | |
33 | ) -> Result<Vec<TapeBackupJobConfig>, Error> { | |
396fd747 DM |
34 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
35 | let user_info = CachedUserInfo::new()?; | |
be327dbc | 36 | |
e3619d41 | 37 | let (config, digest) = pbs_config::tape_job::config()?; |
be327dbc | 38 | |
396fd747 DM |
39 | let list = config.convert_to_typed_array::<TapeBackupJobConfig>("backup")?; |
40 | ||
41 | let list = list | |
42 | .into_iter() | |
43 | .filter(|job| { | |
44 | let privs = user_info.lookup_privs(&auth_id, &["tape", "job", &job.id]); | |
45 | privs & PRIV_TAPE_AUDIT != 0 | |
46 | }) | |
47 | .collect(); | |
be327dbc DM |
48 | |
49 | rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); | |
50 | ||
51 | Ok(list) | |
52 | } | |
53 | ||
54 | #[api( | |
55 | protected: true, | |
56 | input: { | |
57 | properties: { | |
58 | job: { | |
59 | type: TapeBackupJobConfig, | |
60 | flatten: true, | |
61 | }, | |
62 | }, | |
63 | }, | |
396fd747 DM |
64 | access: { |
65 | permission: &Permission::Privilege(&["tape", "job"], PRIV_TAPE_MODIFY, false), | |
66 | }, | |
be327dbc DM |
67 | )] |
68 | /// Create a new tape backup job. | |
69 | pub fn create_tape_backup_job( | |
70 | job: TapeBackupJobConfig, | |
71 | _rpcenv: &mut dyn RpcEnvironment, | |
72 | ) -> Result<(), Error> { | |
e3619d41 | 73 | let _lock = pbs_config::tape_job::lock()?; |
be327dbc | 74 | |
e3619d41 | 75 | let (mut config, _digest) = pbs_config::tape_job::config()?; |
be327dbc DM |
76 | |
77 | if config.sections.get(&job.id).is_some() { | |
78 | bail!("job '{}' already exists.", job.id); | |
79 | } | |
80 | ||
81 | config.set_data(&job.id, "backup", &job)?; | |
82 | ||
e3619d41 | 83 | pbs_config::tape_job::save_config(&config)?; |
be327dbc | 84 | |
8513626b DM |
85 | crate::server::jobstate::create_state_file("tape-backup-job", &job.id)?; |
86 | ||
be327dbc DM |
87 | Ok(()) |
88 | } | |
89 | ||
90 | #[api( | |
91 | input: { | |
92 | properties: { | |
93 | id: { | |
94 | schema: JOB_ID_SCHEMA, | |
95 | }, | |
96 | }, | |
97 | }, | |
98 | returns: { type: TapeBackupJobConfig }, | |
396fd747 DM |
99 | access: { |
100 | permission: &Permission::Privilege(&["tape", "job", "{id}"], PRIV_TAPE_AUDIT, false), | |
101 | }, | |
be327dbc DM |
102 | )] |
103 | /// Read a tape backup job configuration. | |
104 | pub fn read_tape_backup_job( | |
105 | id: String, | |
106 | mut rpcenv: &mut dyn RpcEnvironment, | |
107 | ) -> Result<TapeBackupJobConfig, Error> { | |
108 | ||
e3619d41 | 109 | let (config, digest) = pbs_config::tape_job::config()?; |
be327dbc DM |
110 | |
111 | let job = config.lookup("backup", &id)?; | |
112 | ||
113 | rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); | |
114 | ||
115 | Ok(job) | |
116 | } | |
117 | ||
118 | #[api()] | |
119 | #[derive(Serialize, Deserialize)] | |
120 | #[serde(rename_all="kebab-case")] | |
be327dbc DM |
121 | /// Deletable property name |
122 | pub enum DeletableProperty { | |
123 | /// Delete the comment property. | |
fe291ab7 | 124 | Comment, |
be327dbc | 125 | /// Delete the job schedule. |
fe291ab7 | 126 | Schedule, |
8513626b | 127 | /// Delete the eject-media property |
fe291ab7 | 128 | EjectMedia, |
8513626b | 129 | /// Delete the export-media-set property |
fe291ab7 | 130 | ExportMediaSet, |
21e3ed34 DM |
131 | /// Delete the 'latest-only' property |
132 | LatestOnly, | |
9152a007 DC |
133 | /// Delete the 'notify-user' property |
134 | NotifyUser, | |
be327dbc DM |
135 | } |
136 | ||
137 | #[api( | |
138 | protected: true, | |
139 | input: { | |
140 | properties: { | |
0b90c67f DM |
141 | id: { |
142 | schema: JOB_ID_SCHEMA, | |
143 | }, | |
cdc83c4e DM |
144 | update: { |
145 | type: TapeBackupJobConfigUpdater, | |
146 | flatten: true, | |
be327dbc DM |
147 | }, |
148 | delete: { | |
149 | description: "List of properties to delete.", | |
150 | type: Array, | |
151 | optional: true, | |
152 | items: { | |
153 | type: DeletableProperty, | |
154 | } | |
155 | }, | |
156 | digest: { | |
157 | optional: true, | |
158 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
159 | }, | |
160 | }, | |
161 | }, | |
396fd747 DM |
162 | access: { |
163 | permission: &Permission::Privilege(&["tape", "job", "{id}"], PRIV_TAPE_MODIFY, false), | |
164 | }, | |
be327dbc DM |
165 | )] |
166 | /// Update the tape backup job | |
167 | pub fn update_tape_backup_job( | |
0b90c67f | 168 | id: String, |
cdc83c4e | 169 | update: TapeBackupJobConfigUpdater, |
0b90c67f | 170 | delete: Option<Vec<DeletableProperty>>, |
be327dbc DM |
171 | digest: Option<String>, |
172 | ) -> Result<(), Error> { | |
e3619d41 | 173 | let _lock = pbs_config::tape_job::lock()?; |
be327dbc | 174 | |
e3619d41 | 175 | let (mut config, expected_digest) = pbs_config::tape_job::config()?; |
be327dbc | 176 | |
0b90c67f | 177 | let mut data: TapeBackupJobConfig = config.lookup("backup", &id)?; |
be327dbc DM |
178 | |
179 | if let Some(ref digest) = digest { | |
180 | let digest = proxmox::tools::hex_to_digest(digest)?; | |
181 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; | |
182 | } | |
183 | ||
0b90c67f DM |
184 | if let Some(delete) = delete { |
185 | for delete_prop in delete { | |
186 | match delete_prop { | |
187 | DeletableProperty::EjectMedia => { data.setup.eject_media = None; }, | |
188 | DeletableProperty::ExportMediaSet => { data.setup.export_media_set = None; }, | |
189 | DeletableProperty::LatestOnly => { data.setup.latest_only = None; }, | |
9152a007 | 190 | DeletableProperty::NotifyUser => { data.setup.notify_user = None; }, |
0b90c67f DM |
191 | DeletableProperty::Schedule => { data.schedule = None; }, |
192 | DeletableProperty::Comment => { data.comment = None; }, | |
193 | } | |
194 | } | |
195 | } | |
be327dbc | 196 | |
cdc83c4e DM |
197 | if let Some(store) = update.setup.store { data.setup.store = store; } |
198 | if let Some(pool) = update.setup.pool { data.setup.pool = pool; } | |
199 | if let Some(drive) = update.setup.drive { data.setup.drive = drive; } | |
0b90c67f | 200 | |
cdc83c4e DM |
201 | if update.setup.eject_media.is_some() { data.setup.eject_media = update.setup.eject_media; }; |
202 | if update.setup.export_media_set.is_some() { data.setup.export_media_set = update.setup.export_media_set; } | |
203 | if update.setup.latest_only.is_some() { data.setup.latest_only = update.setup.latest_only; } | |
204 | if update.setup.notify_user.is_some() { data.setup.notify_user = update.setup.notify_user; } | |
0b90c67f | 205 | |
cdc83c4e DM |
206 | let schedule_changed = data.schedule != update.schedule; |
207 | if update.schedule.is_some() { data.schedule = update.schedule; } | |
0b90c67f | 208 | |
cdc83c4e | 209 | if let Some(comment) = update.comment { |
0b90c67f DM |
210 | let comment = comment.trim(); |
211 | if comment.is_empty() { | |
212 | data.comment = None; | |
213 | } else { | |
214 | data.comment = Some(comment.to_string()); | |
215 | } | |
216 | } | |
217 | ||
218 | config.set_data(&id, "backup", &data)?; | |
be327dbc | 219 | |
e3619d41 | 220 | pbs_config::tape_job::save_config(&config)?; |
be327dbc | 221 | |
951fe0cb | 222 | if schedule_changed { |
37a634f5 | 223 | crate::server::jobstate::update_job_last_run_time("tape-backup-job", &id)?; |
951fe0cb DC |
224 | } |
225 | ||
be327dbc DM |
226 | Ok(()) |
227 | } | |
228 | ||
229 | #[api( | |
230 | protected: true, | |
231 | input: { | |
232 | properties: { | |
233 | id: { | |
234 | schema: JOB_ID_SCHEMA, | |
235 | }, | |
236 | digest: { | |
237 | optional: true, | |
238 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
239 | }, | |
240 | }, | |
241 | }, | |
396fd747 DM |
242 | access: { |
243 | permission: &Permission::Privilege(&["tape", "job", "{id}"], PRIV_TAPE_MODIFY, false), | |
244 | }, | |
be327dbc DM |
245 | )] |
246 | /// Remove a tape backup job configuration | |
247 | pub fn delete_tape_backup_job( | |
248 | id: String, | |
249 | digest: Option<String>, | |
250 | _rpcenv: &mut dyn RpcEnvironment, | |
251 | ) -> Result<(), Error> { | |
e3619d41 | 252 | let _lock = pbs_config::tape_job::lock()?; |
be327dbc | 253 | |
e3619d41 | 254 | let (mut config, expected_digest) = pbs_config::tape_job::config()?; |
be327dbc DM |
255 | |
256 | if let Some(ref digest) = digest { | |
257 | let digest = proxmox::tools::hex_to_digest(digest)?; | |
258 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; | |
259 | } | |
260 | ||
261 | match config.lookup::<TapeBackupJobConfig>("backup", &id) { | |
262 | Ok(_job) => { | |
263 | config.sections.remove(&id); | |
264 | }, | |
265 | Err(_) => { bail!("job '{}' does not exist.", id) }, | |
266 | }; | |
267 | ||
e3619d41 | 268 | pbs_config::tape_job::save_config(&config)?; |
be327dbc | 269 | |
8513626b DM |
270 | crate::server::jobstate::remove_state_file("tape-backup-job", &id)?; |
271 | ||
be327dbc DM |
272 | Ok(()) |
273 | } | |
274 | ||
275 | const ITEM_ROUTER: Router = Router::new() | |
276 | .get(&API_METHOD_READ_TAPE_BACKUP_JOB) | |
4961404c | 277 | .put(&API_METHOD_UPDATE_TAPE_BACKUP_JOB) |
be327dbc DM |
278 | .delete(&API_METHOD_DELETE_TAPE_BACKUP_JOB); |
279 | ||
280 | pub const ROUTER: Router = Router::new() | |
281 | .get(&API_METHOD_LIST_TAPE_BACKUP_JOBS) | |
282 | .post(&API_METHOD_CREATE_TAPE_BACKUP_JOB) | |
283 | .match_all("id", &ITEM_ROUTER); |