]>
Commit | Line | Data |
---|---|---|
eb506c83 | 1 | //! Sync datastore from remote server |
268687dd | 2 | use std::sync::{Arc}; |
eb506c83 | 3 | |
07ad6470 | 4 | use anyhow::{format_err, Error}; |
02543a5c | 5 | use futures::{select, future::FutureExt}; |
de8ec041 DM |
6 | |
7 | use proxmox::api::api; | |
404d78c4 | 8 | use proxmox::api::{ApiMethod, Router, RpcEnvironment, Permission}; |
de8ec041 DM |
9 | |
10 | use crate::server::{WorkerTask}; | |
07ad6470 DM |
11 | use crate::backup::DataStore; |
12 | use crate::client::{HttpClient, HttpClientOptions, BackupRepository, pull::pull_store}; | |
de8ec041 | 13 | use crate::api2::types::*; |
07ad6470 DM |
14 | use 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 | |
23 | pub 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 | ||
43 | pub 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 | 69 | pub 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 | 152 | and needs to own the backup group. Remote.Read is required on '/remote/{remote}/{remote-store}'. |
ec67af9a | 153 | The 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 |
159 | async 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 | ||
196 | pub const ROUTER: Router = Router::new() | |
eb506c83 | 197 | .post(&API_METHOD_PULL); |