]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/config/changer.rs
cleanup schema function calls
[proxmox-backup.git] / src / api2 / config / changer.rs
1 use anyhow::{bail, Error};
2 use ::serde::{Deserialize, Serialize};
3 use serde_json::Value;
4 use hex::FromHex;
5
6 use proxmox_router::{Router, RpcEnvironment, Permission};
7 use proxmox_schema::api;
8
9 use pbs_api_types::{
10 Authid, ScsiTapeChanger, ScsiTapeChangerUpdater, LtoTapeDrive,
11 PROXMOX_CONFIG_DIGEST_SCHEMA, CHANGER_NAME_SCHEMA, SLOT_ARRAY_SCHEMA,
12 PRIV_TAPE_AUDIT, PRIV_TAPE_MODIFY,
13 };
14 use pbs_config::CachedUserInfo;
15 use pbs_tape::linux_list_drives::{linux_tape_changer_list, check_drive_path};
16
17 #[api(
18 protected: true,
19 input: {
20 properties: {
21 config: {
22 type: ScsiTapeChanger,
23 flatten: true,
24 },
25 },
26 },
27 access: {
28 permission: &Permission::Privilege(&["tape", "device"], PRIV_TAPE_MODIFY, false),
29 },
30 )]
31 /// Create a new changer device
32 pub fn create_changer(config: ScsiTapeChanger) -> Result<(), Error> {
33
34 let _lock = pbs_config::drive::lock()?;
35
36 let (mut section_config, _digest) = pbs_config::drive::config()?;
37
38 let linux_changers = linux_tape_changer_list();
39
40 check_drive_path(&linux_changers, &config.path)?;
41
42 let existing: Vec<ScsiTapeChanger> = section_config.convert_to_typed_array("changer")?;
43
44 for changer in existing {
45 if changer.name == config.name {
46 bail!("Entry '{}' already exists", config.name);
47 }
48
49 if changer.path == config.path {
50 bail!("Path '{}' already in use by '{}'", config.path, changer.name);
51 }
52 }
53
54 section_config.set_data(&config.name, "changer", &config)?;
55
56 pbs_config::drive::save_config(&section_config)?;
57
58 Ok(())
59 }
60
61 #[api(
62 input: {
63 properties: {
64 name: {
65 schema: CHANGER_NAME_SCHEMA,
66 },
67 },
68 },
69 returns: {
70 type: ScsiTapeChanger,
71 },
72 access: {
73 permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_AUDIT, false),
74 },
75 )]
76 /// Get tape changer configuration
77 pub fn get_config(
78 name: String,
79 _param: Value,
80 mut rpcenv: &mut dyn RpcEnvironment,
81 ) -> Result<ScsiTapeChanger, Error> {
82
83 let (config, digest) = pbs_config::drive::config()?;
84
85 let data: ScsiTapeChanger = config.lookup("changer", &name)?;
86
87 rpcenv["digest"] = hex::encode(&digest).into();
88
89 Ok(data)
90 }
91
92 #[api(
93 input: {
94 properties: {},
95 },
96 returns: {
97 description: "The list of configured changers (with config digest).",
98 type: Array,
99 items: {
100 type: ScsiTapeChanger,
101 },
102 },
103 access: {
104 description: "List configured tape changer filtered by Tape.Audit privileges",
105 permission: &Permission::Anybody,
106 },
107 )]
108 /// List changers
109 pub fn list_changers(
110 _param: Value,
111 mut rpcenv: &mut dyn RpcEnvironment,
112 ) -> Result<Vec<ScsiTapeChanger>, Error> {
113 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
114 let user_info = CachedUserInfo::new()?;
115
116 let (config, digest) = pbs_config::drive::config()?;
117
118 let list: Vec<ScsiTapeChanger> = config.convert_to_typed_array("changer")?;
119
120 let list = list
121 .into_iter()
122 .filter(|changer| {
123 let privs = user_info.lookup_privs(&auth_id, &["tape", "device", &changer.name]);
124 privs & PRIV_TAPE_AUDIT != 0
125 })
126 .collect();
127
128 rpcenv["digest"] = hex::encode(&digest).into();
129
130 Ok(list)
131 }
132 #[api()]
133 #[derive(Serialize, Deserialize)]
134 #[allow(non_camel_case_types)]
135 #[serde(rename_all = "kebab-case")]
136 /// Deletable property name
137 pub enum DeletableProperty {
138 /// Delete export-slots.
139 export_slots,
140 }
141
142 #[api(
143 protected: true,
144 input: {
145 properties: {
146 name: {
147 schema: CHANGER_NAME_SCHEMA,
148 },
149 update: {
150 type: ScsiTapeChangerUpdater,
151 flatten: true,
152 },
153 delete: {
154 description: "List of properties to delete.",
155 type: Array,
156 optional: true,
157 items: {
158 type: DeletableProperty,
159 },
160 },
161 digest: {
162 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
163 optional: true,
164 },
165 },
166 },
167 access: {
168 permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_MODIFY, false),
169 },
170 )]
171 /// Update a tape changer configuration
172 pub fn update_changer(
173 name: String,
174 update: ScsiTapeChangerUpdater,
175 delete: Option<Vec<DeletableProperty>>,
176 digest: Option<String>,
177 _param: Value,
178 ) -> Result<(), Error> {
179
180 let _lock = pbs_config::drive::lock()?;
181
182 let (mut config, expected_digest) = pbs_config::drive::config()?;
183
184 if let Some(ref digest) = digest {
185 let digest = <[u8; 32]>::from_hex(digest)?;
186 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
187 }
188
189 let mut data: ScsiTapeChanger = config.lookup("changer", &name)?;
190
191 if let Some(delete) = delete {
192 for delete_prop in delete {
193 match delete_prop {
194 DeletableProperty::export_slots => {
195 data.export_slots = None;
196 }
197 }
198 }
199 }
200
201 if let Some(path) = update.path {
202 let changers = linux_tape_changer_list();
203 check_drive_path(&changers, &path)?;
204 data.path = path;
205 }
206
207 if let Some(export_slots) = update.export_slots {
208 let slots: Value = SLOT_ARRAY_SCHEMA.parse_property_string(&export_slots)?;
209 let mut slots: Vec<String> = slots
210 .as_array()
211 .unwrap()
212 .iter()
213 .map(|v| v.to_string())
214 .collect();
215 slots.sort();
216
217 if slots.is_empty() {
218 data.export_slots = None;
219 } else {
220 let slots = slots.join(",");
221 data.export_slots = Some(slots);
222 }
223 }
224
225 config.set_data(&name, "changer", &data)?;
226
227 pbs_config::drive::save_config(&config)?;
228
229 Ok(())
230 }
231
232 #[api(
233 protected: true,
234 input: {
235 properties: {
236 name: {
237 schema: CHANGER_NAME_SCHEMA,
238 },
239 },
240 },
241 access: {
242 permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_MODIFY, false),
243 },
244 )]
245 /// Delete a tape changer configuration
246 pub fn delete_changer(name: String, _param: Value) -> Result<(), Error> {
247
248 let _lock = pbs_config::drive::lock()?;
249
250 let (mut config, _digest) = pbs_config::drive::config()?;
251
252 match config.sections.get(&name) {
253 Some((section_type, _)) => {
254 if section_type != "changer" {
255 bail!("Entry '{}' exists, but is not a changer device", name);
256 }
257 config.sections.remove(&name);
258 },
259 None => bail!("Delete changer '{}' failed - no such entry", name),
260 }
261
262 let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?;
263 for drive in drive_list {
264 if let Some(changer) = drive.changer {
265 if changer == name {
266 bail!("Delete changer '{}' failed - used by drive '{}'", name, drive.name);
267 }
268 }
269 }
270
271 pbs_config::drive::save_config(&config)?;
272
273 Ok(())
274 }
275
276 const ITEM_ROUTER: Router = Router::new()
277 .get(&API_METHOD_GET_CONFIG)
278 .put(&API_METHOD_UPDATE_CHANGER)
279 .delete(&API_METHOD_DELETE_CHANGER);
280
281
282 pub const ROUTER: Router = Router::new()
283 .get(&API_METHOD_LIST_CHANGERS)
284 .post(&API_METHOD_CREATE_CHANGER)
285 .match_all("name", &ITEM_ROUTER);