]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/config/sync.rs
move jobstate to server
[proxmox-backup.git] / src / api2 / config / sync.rs
CommitLineData
b1d4edc7
DM
1use anyhow::{bail, Error};
2use serde_json::Value;
3use ::serde::{Deserialize, Serialize};
4
5use proxmox::api::{api, Router, RpcEnvironment};
98c259b4 6use proxmox::tools::fs::open_file_locked;
b1d4edc7
DM
7
8use crate::api2::types::*;
6f652b1b 9use crate::config::sync::{self, SyncJobConfig};
b1d4edc7
DM
10
11// fixme: add access permissions
12
13#[api(
14 input: {
15 properties: {},
16 },
17 returns: {
18 description: "List configured jobs.",
19 type: Array,
6f652b1b 20 items: { type: sync::SyncJobConfig },
b1d4edc7
DM
21 },
22)]
6f652b1b
DM
23/// List all sync jobs
24pub fn list_sync_jobs(
b1d4edc7
DM
25 _param: Value,
26 mut rpcenv: &mut dyn RpcEnvironment,
6f652b1b 27) -> Result<Vec<SyncJobConfig>, Error> {
b1d4edc7 28
6f652b1b 29 let (config, digest) = sync::config()?;
b1d4edc7 30
6f652b1b 31 let list = config.convert_to_typed_array("sync")?;
b1d4edc7
DM
32
33 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
34
35 Ok(list)
36}
37
38#[api(
39 protected: true,
40 input: {
41 properties: {
42 id: {
43 schema: JOB_ID_SCHEMA,
44 },
45 store: {
46 schema: DATASTORE_SCHEMA,
47 },
48 remote: {
49 schema: REMOTE_ID_SCHEMA,
50 },
51 "remote-store": {
52 schema: DATASTORE_SCHEMA,
53 },
54 "remove-vanished": {
55 schema: REMOVE_VANISHED_BACKUPS_SCHEMA,
56 optional: true,
57 },
58 comment: {
59 optional: true,
60 schema: SINGLE_LINE_COMMENT_SCHEMA,
61 },
62 schedule: {
63 optional: true,
2888b27f 64 schema: SYNC_SCHEDULE_SCHEMA,
b1d4edc7
DM
65 },
66 },
67 },
68)]
6f652b1b
DM
69/// Create a new sync job.
70pub fn create_sync_job(param: Value) -> Result<(), Error> {
b1d4edc7 71
b56c111e 72 let _lock = open_file_locked(sync::SYNC_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
b1d4edc7 73
6f652b1b 74 let sync_job: sync::SyncJobConfig = serde_json::from_value(param.clone())?;
b1d4edc7 75
6f652b1b 76 let (mut config, _digest) = sync::config()?;
b1d4edc7 77
6f652b1b
DM
78 if let Some(_) = config.sections.get(&sync_job.id) {
79 bail!("job '{}' already exists.", sync_job.id);
b1d4edc7
DM
80 }
81
6f652b1b 82 config.set_data(&sync_job.id, "sync", &sync_job)?;
b1d4edc7 83
6f652b1b 84 sync::save_config(&config)?;
b1d4edc7 85
1298618a 86 crate::server::jobstate::create_state_file("syncjob", &sync_job.id)?;
93bb51fe 87
b1d4edc7
DM
88 Ok(())
89}
90
91#[api(
92 input: {
93 properties: {
94 id: {
95 schema: JOB_ID_SCHEMA,
96 },
97 },
98 },
99 returns: {
6f652b1b
DM
100 description: "The sync job configuration.",
101 type: sync::SyncJobConfig,
b1d4edc7
DM
102 },
103)]
6f652b1b
DM
104/// Read a sync job configuration.
105pub fn read_sync_job(
b1d4edc7
DM
106 id: String,
107 mut rpcenv: &mut dyn RpcEnvironment,
6f652b1b
DM
108) -> Result<SyncJobConfig, Error> {
109 let (config, digest) = sync::config()?;
b1d4edc7 110
6f652b1b 111 let sync_job = config.lookup("sync", &id)?;
b1d4edc7
DM
112 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
113
6f652b1b 114 Ok(sync_job)
b1d4edc7
DM
115}
116
117#[api()]
118#[derive(Serialize, Deserialize)]
119#[serde(rename_all="kebab-case")]
120#[allow(non_camel_case_types)]
121/// Deletable property name
122pub enum DeletableProperty {
123 /// Delete the comment property.
124 comment,
125 /// Delete the job schedule.
126 schedule,
127 /// Delete the remove-vanished flag.
128 remove_vanished,
129}
130
131#[api(
132 protected: true,
133 input: {
134 properties: {
135 id: {
136 schema: JOB_ID_SCHEMA,
137 },
138 store: {
139 schema: DATASTORE_SCHEMA,
140 optional: true,
141 },
142 remote: {
143 schema: REMOTE_ID_SCHEMA,
144 optional: true,
145 },
146 "remote-store": {
147 schema: DATASTORE_SCHEMA,
148 optional: true,
149 },
150 "remove-vanished": {
151 schema: REMOVE_VANISHED_BACKUPS_SCHEMA,
152 optional: true,
153 },
154 comment: {
155 optional: true,
156 schema: SINGLE_LINE_COMMENT_SCHEMA,
157 },
158 schedule: {
159 optional: true,
2888b27f 160 schema: SYNC_SCHEDULE_SCHEMA,
b1d4edc7
DM
161 },
162 delete: {
163 description: "List of properties to delete.",
164 type: Array,
165 optional: true,
166 items: {
167 type: DeletableProperty,
168 }
169 },
170 digest: {
171 optional: true,
172 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
173 },
174 },
175 },
176)]
6f652b1b
DM
177/// Update sync job config.
178pub fn update_sync_job(
b1d4edc7
DM
179 id: String,
180 store: Option<String>,
181 remote: Option<String>,
182 remote_store: Option<String>,
183 remove_vanished: Option<bool>,
184 comment: Option<String>,
185 schedule: Option<String>,
186 delete: Option<Vec<DeletableProperty>>,
187 digest: Option<String>,
188) -> Result<(), Error> {
189
b56c111e 190 let _lock = open_file_locked(sync::SYNC_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
b1d4edc7
DM
191
192 // pass/compare digest
6f652b1b 193 let (mut config, expected_digest) = sync::config()?;
b1d4edc7
DM
194
195 if let Some(ref digest) = digest {
196 let digest = proxmox::tools::hex_to_digest(digest)?;
197 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
198 }
199
6f652b1b 200 let mut data: sync::SyncJobConfig = config.lookup("sync", &id)?;
b1d4edc7
DM
201
202 if let Some(delete) = delete {
203 for delete_prop in delete {
204 match delete_prop {
205 DeletableProperty::comment => { data.comment = None; },
206 DeletableProperty::schedule => { data.schedule = None; },
207 DeletableProperty::remove_vanished => { data.remove_vanished = None; },
208 }
209 }
210 }
211
212 if let Some(comment) = comment {
213 let comment = comment.trim().to_string();
214 if comment.is_empty() {
215 data.comment = None;
216 } else {
217 data.comment = Some(comment);
218 }
219 }
220
221 if let Some(store) = store { data.store = store; }
222 if let Some(remote) = remote { data.remote = remote; }
223 if let Some(remote_store) = remote_store { data.remote_store = remote_store; }
224
225
226 if schedule.is_some() { data.schedule = schedule; }
227 if remove_vanished.is_some() { data.remove_vanished = remove_vanished; }
228
6f652b1b 229 config.set_data(&id, "sync", &data)?;
b1d4edc7 230
6f652b1b 231 sync::save_config(&config)?;
b1d4edc7
DM
232
233 Ok(())
234}
235
236#[api(
237 protected: true,
238 input: {
239 properties: {
240 id: {
241 schema: JOB_ID_SCHEMA,
242 },
243 digest: {
244 optional: true,
245 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
246 },
247 },
248 },
249)]
6f652b1b
DM
250/// Remove a sync job configuration
251pub fn delete_sync_job(id: String, digest: Option<String>) -> Result<(), Error> {
b1d4edc7 252
b56c111e 253 let _lock = open_file_locked(sync::SYNC_CFG_LOCKFILE, std::time::Duration::new(10, 0), true)?;
b1d4edc7 254
6f652b1b 255 let (mut config, expected_digest) = sync::config()?;
b1d4edc7
DM
256
257 if let Some(ref digest) = digest {
258 let digest = proxmox::tools::hex_to_digest(digest)?;
259 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
260 }
261
262 match config.sections.get(&id) {
263 Some(_) => { config.sections.remove(&id); },
264 None => bail!("job '{}' does not exist.", id),
265 }
266
6f652b1b 267 sync::save_config(&config)?;
b1d4edc7 268
1298618a 269 crate::server::jobstate::remove_state_file("syncjob", &id)?;
664d8a27 270
b1d4edc7
DM
271 Ok(())
272}
273
274const ITEM_ROUTER: Router = Router::new()
6f652b1b
DM
275 .get(&API_METHOD_READ_SYNC_JOB)
276 .put(&API_METHOD_UPDATE_SYNC_JOB)
277 .delete(&API_METHOD_DELETE_SYNC_JOB);
b1d4edc7
DM
278
279pub const ROUTER: Router = Router::new()
6f652b1b
DM
280 .get(&API_METHOD_LIST_SYNC_JOBS)
281 .post(&API_METHOD_CREATE_SYNC_JOB)
426c1e35 282 .match_all("id", &ITEM_ROUTER);