]>
Commit | Line | Data |
---|---|---|
eb506c83 | 1 | //! Sync datastore from remote server |
6e9e6c7a | 2 | use std::convert::TryFrom; |
eb506c83 | 3 | |
07ad6470 | 4 | use anyhow::{format_err, Error}; |
dc7a5b34 | 5 | use futures::{future::FutureExt, select}; |
de8ec041 | 6 | |
dc7a5b34 | 7 | use proxmox_router::{ApiMethod, Permission, Router, RpcEnvironment}; |
6ef1b649 | 8 | use proxmox_schema::api; |
d5790a9f | 9 | use proxmox_sys::task_log; |
de8ec041 | 10 | |
6afdda88 | 11 | use pbs_api_types::{ |
c06c1b4b FG |
12 | Authid, BackupNamespace, GroupFilter, RateLimitConfig, SyncJobConfig, DATASTORE_SCHEMA, |
13 | GROUP_FILTER_LIST_SCHEMA, NS_MAX_DEPTH_SCHEMA, PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_PRUNE, | |
14 | PRIV_REMOTE_READ, REMOTE_ID_SCHEMA, REMOVE_VANISHED_BACKUPS_SCHEMA, | |
6afdda88 | 15 | }; |
1ec0d70d | 16 | use pbs_config::CachedUserInfo; |
dc7a5b34 | 17 | use proxmox_rest_server::WorkerTask; |
2b7f8dd5 | 18 | |
6e9e6c7a | 19 | use crate::server::jobstate::Job; |
dc7a5b34 | 20 | use crate::server::pull::{pull_store, PullParameters}; |
de8ec041 | 21 | |
268687dd | 22 | pub fn check_pull_privs( |
e6dc35ac | 23 | auth_id: &Authid, |
268687dd | 24 | store: &str, |
c06c1b4b | 25 | ns: Option<&str>, |
268687dd DC |
26 | remote: &str, |
27 | remote_store: &str, | |
28 | delete: bool, | |
29 | ) -> Result<(), Error> { | |
268687dd DC |
30 | let user_info = CachedUserInfo::new()?; |
31 | ||
c06c1b4b FG |
32 | let local_store_ns_acl_path = match ns { |
33 | Some(ns) => vec!["datastore", store, ns], | |
34 | None => vec!["datastore", store], | |
35 | }; | |
36 | ||
37 | user_info.check_privs( | |
38 | auth_id, | |
39 | &local_store_ns_acl_path, | |
40 | PRIV_DATASTORE_BACKUP, | |
41 | false, | |
42 | )?; | |
dc7a5b34 TL |
43 | user_info.check_privs( |
44 | auth_id, | |
45 | &["remote", remote, remote_store], | |
46 | PRIV_REMOTE_READ, | |
47 | false, | |
48 | )?; | |
268687dd DC |
49 | |
50 | if delete { | |
c06c1b4b FG |
51 | user_info.check_privs( |
52 | auth_id, | |
53 | &local_store_ns_acl_path, | |
54 | PRIV_DATASTORE_PRUNE, | |
55 | false, | |
56 | )?; | |
268687dd DC |
57 | } |
58 | ||
59 | Ok(()) | |
60 | } | |
61 | ||
6e9e6c7a FG |
62 | impl TryFrom<&SyncJobConfig> for PullParameters { |
63 | type Error = Error; | |
64 | ||
65 | fn try_from(sync_job: &SyncJobConfig) -> Result<Self, Self::Error> { | |
66 | PullParameters::new( | |
67 | &sync_job.store, | |
c06c1b4b | 68 | sync_job.ns.clone().unwrap_or_default(), |
6e9e6c7a FG |
69 | &sync_job.remote, |
70 | &sync_job.remote_store, | |
c06c1b4b | 71 | sync_job.remote_ns.clone().unwrap_or_default(), |
dc7a5b34 TL |
72 | sync_job |
73 | .owner | |
74 | .as_ref() | |
75 | .unwrap_or_else(|| Authid::root_auth_id()) | |
76 | .clone(), | |
6e9e6c7a | 77 | sync_job.remove_vanished, |
c06c1b4b | 78 | sync_job.max_depth, |
062edce2 | 79 | sync_job.group_filter.clone(), |
2d5287fb | 80 | sync_job.limit.clone(), |
6e9e6c7a FG |
81 | ) |
82 | } | |
268687dd DC |
83 | } |
84 | ||
42b68f72 | 85 | pub fn do_sync_job( |
713b66b6 | 86 | mut job: Job, |
42b68f72 | 87 | sync_job: SyncJobConfig, |
e6dc35ac | 88 | auth_id: &Authid, |
02543a5c | 89 | schedule: Option<String>, |
bfa942c0 | 90 | to_stdout: bool, |
42b68f72 | 91 | ) -> Result<String, Error> { |
dc7a5b34 | 92 | let job_id = format!( |
c06c1b4b | 93 | "{}:{}:{}:{}:{}", |
dc7a5b34 TL |
94 | sync_job.remote, |
95 | sync_job.remote_store, | |
96 | sync_job.store, | |
c06c1b4b | 97 | sync_job.ns.clone().unwrap_or_default(), |
dc7a5b34 TL |
98 | job.jobname() |
99 | ); | |
713b66b6 | 100 | let worker_type = job.jobtype().to_string(); |
42b68f72 | 101 | |
f47c1d3a | 102 | let (email, notify) = crate::server::lookup_datastore_notify_settings(&sync_job.store); |
9e733dae | 103 | |
02543a5c | 104 | let upid_str = WorkerTask::spawn( |
713b66b6 | 105 | &worker_type, |
dbd45a72 | 106 | Some(job_id.clone()), |
049a22a3 | 107 | auth_id.to_string(), |
bfa942c0 | 108 | to_stdout, |
02543a5c | 109 | move |worker| async move { |
02543a5c | 110 | job.start(&worker.upid().to_string())?; |
42b68f72 | 111 | |
02543a5c | 112 | let worker2 = worker.clone(); |
9e733dae | 113 | let sync_job2 = sync_job.clone(); |
42b68f72 | 114 | |
02543a5c | 115 | let worker_future = async move { |
6e9e6c7a FG |
116 | let pull_params = PullParameters::try_from(&sync_job)?; |
117 | let client = pull_params.client().await?; | |
02543a5c | 118 | |
1ec0d70d | 119 | task_log!(worker, "Starting datastore sync job '{}'", job_id); |
02543a5c | 120 | if let Some(event_str) = schedule { |
1ec0d70d | 121 | task_log!(worker, "task triggered by schedule '{}'", event_str); |
02543a5c | 122 | } |
1ec0d70d DM |
123 | task_log!( |
124 | worker, | |
125 | "sync datastore '{}' from '{}/{}'", | |
126 | sync_job.store, | |
127 | sync_job.remote, | |
128 | sync_job.remote_store, | |
129 | ); | |
02543a5c | 130 | |
d9aad37f | 131 | pull_store(&worker, &client, pull_params).await?; |
02543a5c | 132 | |
1ec0d70d | 133 | task_log!(worker, "sync job '{}' end", &job_id); |
02543a5c DC |
134 | |
135 | Ok(()) | |
136 | }; | |
137 | ||
dc7a5b34 TL |
138 | let mut abort_future = worker2 |
139 | .abort_future() | |
140 | .map(|_| Err(format_err!("sync aborted"))); | |
02543a5c | 141 | |
dc7a5b34 | 142 | let result = select! { |
02543a5c DC |
143 | worker = worker_future.fuse() => worker, |
144 | abort = abort_future => abort, | |
145 | }; | |
146 | ||
9e733dae | 147 | let status = worker2.create_state(&result); |
02543a5c DC |
148 | |
149 | match job.finish(status) { | |
dc7a5b34 | 150 | Ok(_) => {} |
02543a5c DC |
151 | Err(err) => { |
152 | eprintln!("could not finish job state: {}", err); | |
153 | } | |
154 | } | |
155 | ||
9e733dae | 156 | if let Some(email) = email { |
dc7a5b34 TL |
157 | if let Err(err) = |
158 | crate::server::send_sync_status(&email, notify, &sync_job2, &result) | |
159 | { | |
9e733dae DM |
160 | eprintln!("send sync notification failed: {}", err); |
161 | } | |
162 | } | |
163 | ||
164 | result | |
dc7a5b34 TL |
165 | }, |
166 | )?; | |
42b68f72 DC |
167 | |
168 | Ok(upid_str) | |
169 | } | |
170 | ||
de8ec041 DM |
171 | #[api( |
172 | input: { | |
173 | properties: { | |
174 | store: { | |
175 | schema: DATASTORE_SCHEMA, | |
176 | }, | |
c06c1b4b FG |
177 | ns: { |
178 | type: BackupNamespace, | |
179 | optional: true, | |
180 | }, | |
94609e23 DM |
181 | remote: { |
182 | schema: REMOTE_ID_SCHEMA, | |
de8ec041 DM |
183 | }, |
184 | "remote-store": { | |
185 | schema: DATASTORE_SCHEMA, | |
186 | }, | |
c06c1b4b FG |
187 | "remote-ns": { |
188 | type: BackupNamespace, | |
189 | optional: true, | |
190 | }, | |
b4900286 DM |
191 | "remove-vanished": { |
192 | schema: REMOVE_VANISHED_BACKUPS_SCHEMA, | |
4b4eba0b | 193 | optional: true, |
4b4eba0b | 194 | }, |
c06c1b4b FG |
195 | "max-depth": { |
196 | schema: NS_MAX_DEPTH_SCHEMA, | |
197 | optional: true, | |
198 | }, | |
062edce2 | 199 | "group-filter": { |
71e53463 FG |
200 | schema: GROUP_FILTER_LIST_SCHEMA, |
201 | optional: true, | |
202 | }, | |
2d5287fb DM |
203 | limit: { |
204 | type: RateLimitConfig, | |
205 | flatten: true, | |
206 | } | |
de8ec041 DM |
207 | }, |
208 | }, | |
404d78c4 | 209 | access: { |
365f0f72 | 210 | // Note: used parameters are no uri parameters, so we need to test inside function body |
54552dda | 211 | description: r###"The user needs Datastore.Backup privilege on '/datastore/{store}', |
8247db5b | 212 | and needs to own the backup group. Remote.Read is required on '/remote/{remote}/{remote-store}'. |
ec67af9a | 213 | The delete flag additionally requires the Datastore.Prune privilege on '/datastore/{store}'. |
54552dda | 214 | "###, |
365f0f72 | 215 | permission: &Permission::Anybody, |
404d78c4 | 216 | }, |
de8ec041 | 217 | )] |
eb506c83 | 218 | /// Sync store from other repository |
dc7a5b34 | 219 | async fn pull( |
de8ec041 | 220 | store: String, |
c06c1b4b | 221 | ns: Option<BackupNamespace>, |
94609e23 | 222 | remote: String, |
de8ec041 | 223 | remote_store: String, |
c06c1b4b | 224 | remote_ns: Option<BackupNamespace>, |
b4900286 | 225 | remove_vanished: Option<bool>, |
c06c1b4b | 226 | max_depth: Option<usize>, |
062edce2 | 227 | group_filter: Option<Vec<GroupFilter>>, |
2d5287fb | 228 | limit: RateLimitConfig, |
de8ec041 DM |
229 | _info: &ApiMethod, |
230 | rpcenv: &mut dyn RpcEnvironment, | |
231 | ) -> Result<String, Error> { | |
e6dc35ac | 232 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
61ef4ae8 | 233 | let delete = remove_vanished.unwrap_or(false); |
4b4eba0b | 234 | |
c06c1b4b | 235 | let ns = ns.unwrap_or_default(); |
c06c1b4b FG |
236 | let ns_str = if ns.is_root() { |
237 | None | |
238 | } else { | |
239 | Some(ns.to_string()) | |
240 | }; | |
241 | ||
242 | check_pull_privs( | |
243 | &auth_id, | |
244 | &store, | |
245 | ns_str.as_deref(), | |
246 | &remote, | |
247 | &remote_store, | |
248 | delete, | |
249 | )?; | |
de8ec041 | 250 | |
6e9e6c7a FG |
251 | let pull_params = PullParameters::new( |
252 | &store, | |
c06c1b4b | 253 | ns, |
6e9e6c7a FG |
254 | &remote, |
255 | &remote_store, | |
c06c1b4b | 256 | remote_ns.unwrap_or_default(), |
6e9e6c7a FG |
257 | auth_id.clone(), |
258 | remove_vanished, | |
c06c1b4b | 259 | max_depth, |
062edce2 | 260 | group_filter, |
2d5287fb | 261 | limit, |
6e9e6c7a FG |
262 | )?; |
263 | let client = pull_params.client().await?; | |
de8ec041 DM |
264 | |
265 | // fixme: set to_stdout to false? | |
dc7a5b34 TL |
266 | let upid_str = WorkerTask::spawn( |
267 | "sync", | |
268 | Some(store.clone()), | |
269 | auth_id.to_string(), | |
270 | true, | |
271 | move |worker| async move { | |
c06c1b4b FG |
272 | task_log!( |
273 | worker, | |
274 | "pull datastore '{}' from '{}/{}'", | |
275 | store, | |
276 | remote, | |
277 | remote_store, | |
278 | ); | |
de8ec041 | 279 | |
d9aad37f | 280 | let pull_future = pull_store(&worker, &client, pull_params); |
dc7a5b34 TL |
281 | let future = select! { |
282 | success = pull_future.fuse() => success, | |
283 | abort = worker.abort_future().map(|_| Err(format_err!("pull aborted"))) => abort, | |
284 | }; | |
36700a0a | 285 | |
dc7a5b34 | 286 | let _ = future?; |
de8ec041 | 287 | |
c06c1b4b | 288 | task_log!(worker, "pull datastore '{}' end", store); |
de8ec041 | 289 | |
dc7a5b34 TL |
290 | Ok(()) |
291 | }, | |
292 | )?; | |
de8ec041 DM |
293 | |
294 | Ok(upid_str) | |
295 | } | |
296 | ||
dc7a5b34 | 297 | pub const ROUTER: Router = Router::new().post(&API_METHOD_PULL); |