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 let Some(_
) = config
.sections
.get(&remote
.name
) {
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 pub fn update_remote(
208 comment
: Option
<String
>,
209 host
: Option
<String
>,
211 auth_id
: Option
<Authid
>,
212 password
: Option
<String
>,
213 fingerprint
: Option
<String
>,
214 delete
: Option
<Vec
<DeletableProperty
>>,
215 digest
: Option
<String
>,
216 ) -> Result
<(), Error
> {
218 let _lock
= open_file_locked(remote
::REMOTE_CFG_LOCKFILE
, std
::time
::Duration
::new(10, 0), true)?
;
220 let (mut config
, expected_digest
) = remote
::config()?
;
222 if let Some(ref digest
) = digest
{
223 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
224 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
227 let mut data
: remote
::Remote
= config
.lookup("remote", &name
)?
;
229 if let Some(delete
) = delete
{
230 for delete_prop
in delete
{
232 DeletableProperty
::comment
=> { data.comment = None; }
,
233 DeletableProperty
::fingerprint
=> { data.fingerprint = None; }
,
234 DeletableProperty
::port
=> { data.port = None; }
,
239 if let Some(comment
) = comment
{
240 let comment
= comment
.trim().to_string();
241 if comment
.is_empty() {
244 data
.comment
= Some(comment
);
247 if let Some(host
) = host { data.host = host; }
248 if port
.is_some() { data.port = port; }
249 if let Some(auth_id
) = auth_id { data.auth_id = auth_id; }
250 if let Some(password
) = password { data.password = password; }
252 if let Some(fingerprint
) = fingerprint { data.fingerprint = Some(fingerprint); }
254 config
.set_data(&name
, "remote", &data
)?
;
256 remote
::save_config(&config
)?
;
266 schema
: REMOTE_ID_SCHEMA
,
270 schema
: PROXMOX_CONFIG_DIGEST_SCHEMA
,
275 permission
: &Permission
::Privilege(&["remote", "{name}"], PRIV_REMOTE_MODIFY
, false),
278 /// Remove a remote from the configuration file.
279 pub fn delete_remote(name
: String
, digest
: Option
<String
>) -> Result
<(), Error
> {
281 use crate::config
::sync
::{self, SyncJobConfig}
;
283 let (sync_jobs
, _
) = sync
::config()?
;
285 let job_list
: Vec
<SyncJobConfig
> = sync_jobs
.convert_to_typed_array("sync")?
;
286 for job
in job_list
{
287 if job
.remote
== name
{
288 bail
!("remote '{}' is used by sync job '{}' (datastore '{}')", name
, job
.id
, job
.store
);
292 let _lock
= open_file_locked(remote
::REMOTE_CFG_LOCKFILE
, std
::time
::Duration
::new(10, 0), true)?
;
294 let (mut config
, expected_digest
) = remote
::config()?
;
296 if let Some(ref digest
) = digest
{
297 let digest
= proxmox
::tools
::hex_to_digest(digest
)?
;
298 crate::tools
::detect_modified_configuration_file(&digest
, &expected_digest
)?
;
301 match config
.sections
.get(&name
) {
302 Some(_
) => { config.sections.remove(&name); }
,
303 None
=> bail
!("remote '{}' does not exist.", name
),
306 remote
::save_config(&config
)?
;
311 /// Helper to get client for remote.cfg entry
312 pub async
fn remote_client(remote
: remote
::Remote
) -> Result
<HttpClient
, Error
> {
313 let options
= HttpClientOptions
::new()
314 .password(Some(remote
.password
.clone()))
315 .fingerprint(remote
.fingerprint
.clone());
317 let client
= HttpClient
::new(
319 remote
.port
.unwrap_or(8007),
322 let _auth_info
= client
.login() // make sure we can auth
324 .map_err(|err
| format_err
!("remote connection to '{}' failed - {}", remote
.host
, err
))?
;
334 schema
: REMOTE_ID_SCHEMA
,
339 permission
: &Permission
::Privilege(&["remote", "{name}"], PRIV_REMOTE_AUDIT
, false),
342 description
: "List the accessible datastores.",
344 items
: { type: DataStoreListItem }
,
347 /// List datastores of a remote.cfg entry
348 pub async
fn scan_remote_datastores(name
: String
) -> Result
<Vec
<DataStoreListItem
>, Error
> {
349 let (remote_config
, _digest
) = remote
::config()?
;
350 let remote
: remote
::Remote
= remote_config
.lookup("remote", &name
)?
;
352 let map_remote_err
= |api_err
| {
353 http_err
!(INTERNAL_SERVER_ERROR
,
354 "failed to scan remote '{}' - {}",
359 let client
= remote_client(remote
)
361 .map_err(map_remote_err
)?
;
363 .get("api2/json/admin/datastore", None
)
365 .map_err(map_remote_err
)?
;
366 let parse_res
= match api_res
.get("data") {
367 Some(data
) => serde_json
::from_value
::<Vec
<DataStoreListItem
>>(data
.to_owned()),
368 None
=> bail
!("remote {} did not return any datastore list data", &name
),
372 Ok(parsed
) => Ok(parsed
),
373 Err(_
) => bail
!("Failed to parse remote scan api result."),
377 const SCAN_ROUTER
: Router
= Router
::new()
378 .get(&API_METHOD_SCAN_REMOTE_DATASTORES
);
380 const ITEM_ROUTER
: Router
= Router
::new()
381 .get(&API_METHOD_READ_REMOTE
)
382 .put(&API_METHOD_UPDATE_REMOTE
)
383 .delete(&API_METHOD_DELETE_REMOTE
)
384 .subdirs(&[("scan", &SCAN_ROUTER
)]);
386 pub const ROUTER
: Router
= Router
::new()
387 .get(&API_METHOD_LIST_REMOTES
)
388 .post(&API_METHOD_CREATE_REMOTE
)
389 .match_all("name", &ITEM_ROUTER
);