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