]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/pull.rs
d/control: add ',' after qrencode dependency
[proxmox-backup.git] / src / api2 / pull.rs
CommitLineData
eb506c83 1//! Sync datastore from remote server
268687dd 2use std::sync::{Arc};
eb506c83 3
07ad6470 4use anyhow::{format_err, Error};
02543a5c 5use futures::{select, future::FutureExt};
de8ec041
DM
6
7use proxmox::api::api;
404d78c4 8use proxmox::api::{ApiMethod, Router, RpcEnvironment, Permission};
de8ec041
DM
9
10use crate::server::{WorkerTask};
07ad6470
DM
11use crate::backup::DataStore;
12use crate::client::{HttpClient, HttpClientOptions, BackupRepository, pull::pull_store};
de8ec041 13use crate::api2::types::*;
07ad6470
DM
14use crate::config::{
15 remote,
42b68f72 16 sync::SyncJobConfig,
02543a5c 17 jobstate::Job,
07ad6470
DM
18 acl::{PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_PRUNE, PRIV_REMOTE_READ},
19 cached_user_info::CachedUserInfo,
20};
de8ec041 21
268687dd
DC
22
23pub fn check_pull_privs(
e7cb4dc5 24 userid: &Userid,
268687dd
DC
25 store: &str,
26 remote: &str,
27 remote_store: &str,
28 delete: bool,
29) -> Result<(), Error> {
30
31 let user_info = CachedUserInfo::new()?;
32
e7cb4dc5
WB
33 user_info.check_privs(userid, &["datastore", store], PRIV_DATASTORE_BACKUP, false)?;
34 user_info.check_privs(userid, &["remote", remote, remote_store], PRIV_REMOTE_READ, false)?;
268687dd
DC
35
36 if delete {
e7cb4dc5 37 user_info.check_privs(userid, &["datastore", store], PRIV_DATASTORE_PRUNE, false)?;
268687dd
DC
38 }
39
40 Ok(())
41}
42
43pub async fn get_pull_parameters(
44 store: &str,
45 remote: &str,
46 remote_store: &str,
47) -> Result<(HttpClient, BackupRepository, Arc<DataStore>), Error> {
48
49 let tgt_store = DataStore::lookup_datastore(store)?;
50
51 let (remote_config, _digest) = remote::config()?;
52 let remote: remote::Remote = remote_config.lookup("remote", remote)?;
53
54 let options = HttpClientOptions::new()
55 .password(Some(remote.password.clone()))
56 .fingerprint(remote.fingerprint.clone());
57
38d46759
DC
58 let src_repo = BackupRepository::new(Some(remote.userid.clone()), Some(remote.host.clone()), remote.port, remote_store.to_string());
59
60 let client = HttpClient::new(&src_repo.host(), src_repo.port(), &src_repo.user(), options)?;
268687dd
DC
61 let _auth_info = client.login() // make sure we can auth
62 .await
63 .map_err(|err| format_err!("remote connection to '{}' failed - {}", remote.host, err))?;
64
268687dd
DC
65
66 Ok((client, src_repo, tgt_store))
67}
68
42b68f72 69pub fn do_sync_job(
713b66b6 70 mut job: Job,
42b68f72
DC
71 sync_job: SyncJobConfig,
72 userid: &Userid,
02543a5c 73 schedule: Option<String>,
42b68f72
DC
74) -> Result<String, Error> {
75
713b66b6
DC
76 let job_id = job.jobname().to_string();
77 let worker_type = job.jobtype().to_string();
42b68f72 78
02543a5c 79 let upid_str = WorkerTask::spawn(
713b66b6
DC
80 &worker_type,
81 Some(job.jobname().to_string()),
02543a5c
DC
82 userid.clone(),
83 false,
84 move |worker| async move {
42b68f72 85
02543a5c 86 job.start(&worker.upid().to_string())?;
42b68f72 87
02543a5c 88 let worker2 = worker.clone();
42b68f72 89
02543a5c 90 let worker_future = async move {
42b68f72 91
02543a5c
DC
92 let delete = sync_job.remove_vanished.unwrap_or(true);
93 let (client, src_repo, tgt_store) = get_pull_parameters(&sync_job.store, &sync_job.remote, &sync_job.remote_store).await?;
94
95 worker.log(format!("Starting datastore sync job '{}'", job_id));
96 if let Some(event_str) = schedule {
97 worker.log(format!("task triggered by schedule '{}'", event_str));
98 }
99 worker.log(format!("Sync datastore '{}' from '{}/{}'",
100 sync_job.store, sync_job.remote, sync_job.remote_store));
101
102 crate::client::pull::pull_store(&worker, &client, &src_repo, tgt_store.clone(), delete, Userid::backup_userid().clone()).await?;
103
104 worker.log(format!("sync job '{}' end", &job_id));
105
106 Ok(())
107 };
108
109 let mut abort_future = worker2.abort_future().map(|_| Err(format_err!("sync aborted")));
110
111 let res = select!{
112 worker = worker_future.fuse() => worker,
113 abort = abort_future => abort,
114 };
115
116 let status = worker2.create_state(&res);
117
118 match job.finish(status) {
119 Ok(_) => {},
120 Err(err) => {
121 eprintln!("could not finish job state: {}", err);
122 }
123 }
124
125 res
126 })?;
42b68f72
DC
127
128 Ok(upid_str)
129}
130
de8ec041
DM
131#[api(
132 input: {
133 properties: {
134 store: {
135 schema: DATASTORE_SCHEMA,
136 },
94609e23
DM
137 remote: {
138 schema: REMOTE_ID_SCHEMA,
de8ec041
DM
139 },
140 "remote-store": {
141 schema: DATASTORE_SCHEMA,
142 },
b4900286
DM
143 "remove-vanished": {
144 schema: REMOVE_VANISHED_BACKUPS_SCHEMA,
4b4eba0b 145 optional: true,
4b4eba0b 146 },
de8ec041
DM
147 },
148 },
404d78c4 149 access: {
365f0f72 150 // Note: used parameters are no uri parameters, so we need to test inside function body
54552dda 151 description: r###"The user needs Datastore.Backup privilege on '/datastore/{store}',
8247db5b 152and needs to own the backup group. Remote.Read is required on '/remote/{remote}/{remote-store}'.
ec67af9a 153The delete flag additionally requires the Datastore.Prune privilege on '/datastore/{store}'.
54552dda 154"###,
365f0f72 155 permission: &Permission::Anybody,
404d78c4 156 },
de8ec041 157)]
eb506c83
DM
158/// Sync store from other repository
159async fn pull (
de8ec041 160 store: String,
94609e23 161 remote: String,
de8ec041 162 remote_store: String,
b4900286 163 remove_vanished: Option<bool>,
de8ec041
DM
164 _info: &ApiMethod,
165 rpcenv: &mut dyn RpcEnvironment,
166) -> Result<String, Error> {
167
e7cb4dc5 168 let userid: Userid = rpcenv.get_user().unwrap().parse()?;
b4900286 169 let delete = remove_vanished.unwrap_or(true);
4b4eba0b 170
e7cb4dc5 171 check_pull_privs(&userid, &store, &remote, &remote_store, delete)?;
de8ec041 172
268687dd 173 let (client, src_repo, tgt_store) = get_pull_parameters(&store, &remote, &remote_store).await?;
de8ec041
DM
174
175 // fixme: set to_stdout to false?
e7cb4dc5 176 let upid_str = WorkerTask::spawn("sync", Some(store.clone()), userid.clone(), true, move |worker| async move {
de8ec041
DM
177
178 worker.log(format!("sync datastore '{}' start", store));
179
36700a0a
DC
180 let pull_future = pull_store(&worker, &client, &src_repo, tgt_store.clone(), delete, userid);
181 let future = select!{
182 success = pull_future.fuse() => success,
183 abort = worker.abort_future().map(|_| Err(format_err!("pull aborted"))) => abort,
184 };
185
186 let _ = future?;
de8ec041
DM
187
188 worker.log(format!("sync datastore '{}' end", store));
189
190 Ok(())
191 })?;
192
193 Ok(upid_str)
194}
195
196pub const ROUTER: Router = Router::new()
eb506c83 197 .post(&API_METHOD_PULL);