]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/config/tape_backup_job.rs
move acl to pbs_config workspaces, pbs_api_types cleanups
[proxmox-backup.git] / src / api2 / config / tape_backup_job.rs
1 use anyhow::{bail, Error};
2 use serde_json::Value;
3 use ::serde::{Deserialize, Serialize};
4
5 use proxmox::api::{api, Router, RpcEnvironment, Permission};
6
7 use pbs_api_types::{
8 Authid, TapeBackupJobConfig, TapeBackupJobConfigUpdater,
9 JOB_ID_SCHEMA, PROXMOX_CONFIG_DIGEST_SCHEMA,
10 PRIV_TAPE_AUDIT, PRIV_TAPE_MODIFY,
11 };
12
13 use crate::config::cached_user_info::CachedUserInfo;
14
15 #[api(
16 input: {
17 properties: {},
18 },
19 returns: {
20 description: "List configured jobs.",
21 type: Array,
22 items: { type: TapeBackupJobConfig },
23 },
24 access: {
25 description: "List configured tape jobs filtered by Tape.Audit privileges",
26 permission: &Permission::Anybody,
27 },
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> {
34 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
35 let user_info = CachedUserInfo::new()?;
36
37 let (config, digest) = pbs_config::tape_job::config()?;
38
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();
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 },
64 access: {
65 permission: &Permission::Privilege(&["tape", "job"], PRIV_TAPE_MODIFY, false),
66 },
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> {
73 let _lock = pbs_config::tape_job::lock()?;
74
75 let (mut config, _digest) = pbs_config::tape_job::config()?;
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
83 pbs_config::tape_job::save_config(&config)?;
84
85 crate::server::jobstate::create_state_file("tape-backup-job", &job.id)?;
86
87 Ok(())
88 }
89
90 #[api(
91 input: {
92 properties: {
93 id: {
94 schema: JOB_ID_SCHEMA,
95 },
96 },
97 },
98 returns: { type: TapeBackupJobConfig },
99 access: {
100 permission: &Permission::Privilege(&["tape", "job", "{id}"], PRIV_TAPE_AUDIT, false),
101 },
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
109 let (config, digest) = pbs_config::tape_job::config()?;
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")]
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 }
136
137 #[api(
138 protected: true,
139 input: {
140 properties: {
141 id: {
142 schema: JOB_ID_SCHEMA,
143 },
144 update: {
145 type: TapeBackupJobConfigUpdater,
146 flatten: true,
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 },
162 access: {
163 permission: &Permission::Privilege(&["tape", "job", "{id}"], PRIV_TAPE_MODIFY, false),
164 },
165 )]
166 /// Update the tape backup job
167 pub fn update_tape_backup_job(
168 id: String,
169 update: TapeBackupJobConfigUpdater,
170 delete: Option<Vec<DeletableProperty>>,
171 digest: Option<String>,
172 ) -> Result<(), Error> {
173 let _lock = pbs_config::tape_job::lock()?;
174
175 let (mut config, expected_digest) = pbs_config::tape_job::config()?;
176
177 let mut data: TapeBackupJobConfig = config.lookup("backup", &id)?;
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
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; },
190 DeletableProperty::NotifyUser => { data.setup.notify_user = None; },
191 DeletableProperty::Schedule => { data.schedule = None; },
192 DeletableProperty::Comment => { data.comment = None; },
193 }
194 }
195 }
196
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; }
200
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; }
205
206 let schedule_changed = data.schedule != update.schedule;
207 if update.schedule.is_some() { data.schedule = update.schedule; }
208
209 if let Some(comment) = update.comment {
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)?;
219
220 pbs_config::tape_job::save_config(&config)?;
221
222 if schedule_changed {
223 crate::server::jobstate::update_job_last_run_time("tape-backup-job", &id)?;
224 }
225
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 },
242 access: {
243 permission: &Permission::Privilege(&["tape", "job", "{id}"], PRIV_TAPE_MODIFY, false),
244 },
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> {
252 let _lock = pbs_config::tape_job::lock()?;
253
254 let (mut config, expected_digest) = pbs_config::tape_job::config()?;
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
268 pbs_config::tape_job::save_config(&config)?;
269
270 crate::server::jobstate::remove_state_file("tape-backup-job", &id)?;
271
272 Ok(())
273 }
274
275 const ITEM_ROUTER: Router = Router::new()
276 .get(&API_METHOD_READ_TAPE_BACKUP_JOB)
277 .put(&API_METHOD_UPDATE_TAPE_BACKUP_JOB)
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);