1 use anyhow
::{bail, format_err, Error}
;
3 use ::serde
::{Deserialize, Serialize}
;
5 use proxmox
::api
::{api, ApiMethod, Router, RpcEnvironment, Permission}
;
7 use proxmox
::tools
::fs
::open_file_locked
;
9 use crate::api2
::types
::*;
10 use crate::client
::{HttpClient, HttpClientOptions}
;
11 use crate::config
::cached_user_info
::CachedUserInfo
;
12 use crate::config
::remote
;
13 use crate::config
::acl
::{PRIV_REMOTE_AUDIT, PRIV_REMOTE_MODIFY}
;
20 description
: "The list of configured remotes (with config digest).",
22 items
: { type: remote::Remote }
,
25 description
: "List configured remotes filtered by Remote.Audit privileges",
26 permission
: &Permission
::Anybody
,
33 mut rpcenv
: &mut dyn RpcEnvironment
,
34 ) -> Result
<Vec
<remote
::Remote
>, Error
> {
35 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
36 let user_info
= CachedUserInfo
::new()?
;
38 let (config
, digest
) = remote
::config()?
;
40 let mut list
: Vec
<remote
::Remote
> = config
.convert_to_typed_array("remote")?
;
41 // don't return password in api
42 for remote
in &mut list
{
43 remote
.password
= "".to_string();
49 let privs
= user_info
.lookup_privs(&auth_id
, &["remote", &remote
.name
]);
50 privs
& PRIV_REMOTE_AUDIT
!= 0
54 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
63 schema
: REMOTE_ID_SCHEMA
,
67 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
70 schema
: DNS_NAME_OR_IP_SCHEMA
,
73 description
: "The (optional) port.",
82 schema
: remote
::REMOTE_PASSWORD_SCHEMA
,
86 schema
: CERT_FINGERPRINT_SHA256_SCHEMA
,
91 permission
: &Permission
::Privilege(&["remote"], PRIV_REMOTE_MODIFY
, false),
94 /// Create new remote.
95 pub fn create_remote(password
: String
, param
: Value
) -> Result
<(), Error
> {
97 let _lock
= open_file_locked(remote
::REMOTE_CFG_LOCKFILE
, std
::time
::Duration
::new(10, 0), true)?
;
100 data
["password"] = Value
::from(base64
::encode(password
.as_bytes()));
101 let remote
: remote
::Remote
= serde_json
::from_value(data
)?
;
103 let (mut config
, _digest
) = remote
::config()?
;
105 if config
.sections
.get(&remote
.name
).is_some() {
106 bail
!("remote '{}' already exists.", remote
.name
);
109 config
.set_data(&remote
.name
, "remote", &remote
)?
;
111 remote
::save_config(&config
)?
;
120 schema
: REMOTE_ID_SCHEMA
,
124 returns
: { type: remote::Remote }
,
126 permission
: &Permission
::Privilege(&["remote", "{name}"], PRIV_REMOTE_AUDIT
, false),
129 /// Read remote configuration data.
133 mut rpcenv
: &mut dyn RpcEnvironment
,
134 ) -> Result
<remote
::Remote
, Error
> {
135 let (config
, digest
) = remote
::config()?
;
136 let mut data
: remote
::Remote
= config
.lookup("remote", &name
)?
;
137 data
.password
= "".to_string(); // do not return password in api
138 rpcenv
["digest"] = proxmox
::tools
::digest_to_hex(&digest
).into();
143 #[derive(Serialize, Deserialize)]
144 #[allow(non_camel_case_types)]
145 /// Deletable property name
146 pub enum DeletableProperty
{
147 /// Delete the comment property.
149 /// Delete the fingerprint property.
151 /// Delete the port property.
160 schema
: REMOTE_ID_SCHEMA
,
164 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
168 schema
: DNS_NAME_OR_IP_SCHEMA
,
171 description
: "The (optional) port.",
181 schema
: remote
::REMOTE_PASSWORD_SCHEMA
,
185 schema
: CERT_FINGERPRINT_SHA256_SCHEMA
,
188 description
: "List of properties to delete.",
192 type: DeletableProperty
,
197 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
202 permission
: &Permission
::Privilege(&["remote", "{name}"], PRIV_REMOTE_MODIFY
, false),
205 /// Update remote configuration.
206 #[allow(clippy::too_many_arguments)]
207 pub fn update_remote(
209 comment
: Option
<String
>,
210 host
: Option
<String
>,
212 auth_id
: Option
<Authid
>,
213 password
: Option
<String
>,
214 fingerprint
: Option
<String
>,
215 delete
: Option
<Vec
<DeletableProperty
>>,
216 digest
: Option
<String
>,
217 ) -> Result
<(), Error
> {
219 let _lock
= open_file_locked(remote
::REMOTE_CFG_LOCKFILE
, std
::time
::Duration
::new(10, 0), true)?
;
221 let (mut config
, expected_digest
) = remote
::config()?
;
223 if let Some(ref digest
) = digest
{
224 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
225 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
228 let mut data
: remote
::Remote
= config
.lookup("remote", &name
)?
;
230 if let Some(delete
) = delete
{
231 for delete_prop
in delete
{
233 DeletableProperty
::comment
=> { data.comment = None; }
,
234 DeletableProperty
::fingerprint
=> { data.fingerprint = None; }
,
235 DeletableProperty
::port
=> { data.port = None; }
,
240 if let Some(comment
) = comment
{
241 let comment
= comment
.trim().to_string();
242 if comment
.is_empty() {
245 data
.comment
= Some(comment
);
248 if let Some(host
) = host { data.host = host; }
249 if port
.is_some() { data.port = port; }
250 if let Some(auth_id
) = auth_id { data.auth_id = auth_id; }
251 if let Some(password
) = password { data.password = password; }
253 if let Some(fingerprint
) = fingerprint { data.fingerprint = Some(fingerprint); }
255 config
.set_data(&name
, "remote", &data
)?
;
257 remote
::save_config(&config
)?
;
267 schema
: REMOTE_ID_SCHEMA
,
271 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
276 permission
: &Permission
::Privilege(&["remote", "{name}"], PRIV_REMOTE_MODIFY
, false),
279 /// Remove a remote from the configuration file.
280 pub fn delete_remote(name
: String
, digest
: Option
<String
>) -> Result
<(), Error
> {
282 use crate::config
::sync
::{self, SyncJobConfig}
;
284 let (sync_jobs
, _
) = sync
::config()?
;
286 let job_list
: Vec
<SyncJobConfig
> = sync_jobs
.convert_to_typed_array("sync")?
;
287 for job
in job_list
{
288 if job
.remote
== name
{
289 bail
!("remote '{}' is used by sync job '{}' (datastore '{}')", name
, job
.id
, job
.store
);
293 let _lock
= open_file_locked(remote
::REMOTE_CFG_LOCKFILE
, std
::time
::Duration
::new(10, 0), true)?
;
295 let (mut config
, expected_digest
) = remote
::config()?
;
297 if let Some(ref digest
) = digest
{
298 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
299 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
302 match config
.sections
.get(&name
) {
303 Some(_
) => { config.sections.remove(&name); }
,
304 None
=> bail
!("remote '{}' does not exist.", name
),
307 remote
::save_config(&config
)?
;
312 /// Helper to get client for remote.cfg entry
313 pub async
fn remote_client(remote
: remote
::Remote
) -> Result
<HttpClient
, Error
> {
314 let options
= HttpClientOptions
::new_non_interactive(remote
.password
.clone(), remote
.fingerprint
.clone());
316 let client
= HttpClient
::new(
318 remote
.port
.unwrap_or(8007),
321 let _auth_info
= client
.login() // make sure we can auth
323 .map_err(|err
| format_err
!("remote connection to '{}' failed - {}", remote
.host
, err
))?
;
333 schema
: REMOTE_ID_SCHEMA
,
338 permission
: &Permission
::Privilege(&["remote", "{name}"], PRIV_REMOTE_AUDIT
, false),
341 description
: "List the accessible datastores.",
343 items
: { type: DataStoreListItem }
,
346 /// List datastores of a remote.cfg entry
347 pub async
fn scan_remote_datastores(name
: String
) -> Result
<Vec
<DataStoreListItem
>, Error
> {
348 let (remote_config
, _digest
) = remote
::config()?
;
349 let remote
: remote
::Remote
= remote_config
.lookup("remote", &name
)?
;
351 let map_remote_err
= |api_err
| {
352 http_err
!(INTERNAL_SERVER_ERROR
,
353 "failed to scan remote '{}' - {}",
358 let client
= remote_client(remote
)
360 .map_err(map_remote_err
)?
;
362 .get("api2/json/admin/datastore", None
)
364 .map_err(map_remote_err
)?
;
365 let parse_res
= match api_res
.get("data") {
366 Some(data
) => serde_json
::from_value
::<Vec
<DataStoreListItem
>>(data
.to_owned()),
367 None
=> bail
!("remote {} did not return any datastore list data", &name
),
371 Ok(parsed
) => Ok(parsed
),
372 Err(_
) => bail
!("Failed to parse remote scan api result."),
376 const SCAN_ROUTER
: Router
= Router
::new()
377 .get(&API_METHOD_SCAN_REMOTE_DATASTORES
);
379 const ITEM_ROUTER
: Router
= Router
::new()
380 .get(&API_METHOD_READ_REMOTE
)
381 .put(&API_METHOD_UPDATE_REMOTE
)
382 .delete(&API_METHOD_DELETE_REMOTE
)
383 .subdirs(&[("scan", &SCAN_ROUTER
)]);
385 pub const ROUTER
: Router
= Router
::new()
386 .get(&API_METHOD_LIST_REMOTES
)
387 .post(&API_METHOD_CREATE_REMOTE
)
388 .match_all("name", &ITEM_ROUTER
);