]> git.proxmox.com Git - proxmox-backup.git/blame - pbs-api-types/src/jobs.rs
api-types: doc improvements
[proxmox-backup.git] / pbs-api-types / src / jobs.rs
CommitLineData
3e276f6f
FG
1use std::str::FromStr;
2
4f0dd337 3use anyhow::bail;
3e276f6f 4use regex::Regex;
e3619d41
DM
5use serde::{Deserialize, Serialize};
6
6ef1b649 7use proxmox_schema::*;
e3619d41
DM
8
9use crate::{
abd82485
FG
10 Authid, BackupNamespace, BackupType, RateLimitConfig, Userid, BACKUP_GROUP_SCHEMA,
11 BACKUP_NAMESPACE_SCHEMA, DATASTORE_SCHEMA, DRIVE_NAME_SCHEMA, MEDIA_POOL_NAME_SCHEMA,
12 NS_MAX_DEPTH_REDUCED_SCHEMA, PROXMOX_SAFE_ID_FORMAT, REMOTE_ID_SCHEMA,
e40c7fb9 13 SINGLE_LINE_COMMENT_SCHEMA,
e3619d41
DM
14};
15
b22d785c 16const_regex! {
e3619d41
DM
17
18 /// Regex for verification jobs 'DATASTORE:ACTUAL_JOB_ID'
19 pub VERIFICATION_JOB_WORKER_ID_REGEX = concat!(r"^(", PROXMOX_SAFE_ID_REGEX_STR!(), r"):");
4ec73327
HL
20 /// Regex for sync jobs '(REMOTE|\-):REMOTE_DATASTORE:LOCAL_DATASTORE:(?:LOCAL_NS_ANCHOR:)ACTUAL_JOB_ID'
21 pub SYNC_JOB_WORKER_ID_REGEX = concat!(r"^(", PROXMOX_SAFE_ID_REGEX_STR!(), r"|\-):(", PROXMOX_SAFE_ID_REGEX_STR!(), r"):(", PROXMOX_SAFE_ID_REGEX_STR!(), r")(?::(", BACKUP_NS_RE!(), r"))?:");
e3619d41
DM
22}
23
e3619d41
DM
24pub const JOB_ID_SCHEMA: Schema = StringSchema::new("Job ID.")
25 .format(&PROXMOX_SAFE_ID_FORMAT)
26 .min_length(3)
27 .max_length(32)
28 .schema();
29
b22d785c
TL
30pub const SYNC_SCHEDULE_SCHEMA: Schema = StringSchema::new("Run sync job at specified schedule.")
31 .format(&ApiStringFormat::VerifyFn(
32 proxmox_time::verify_calendar_event,
33 ))
e3619d41
DM
34 .type_text("<calendar-event>")
35 .schema();
36
b22d785c
TL
37pub const GC_SCHEDULE_SCHEMA: Schema =
38 StringSchema::new("Run garbage collection job at specified schedule.")
39 .format(&ApiStringFormat::VerifyFn(
40 proxmox_time::verify_calendar_event,
41 ))
42 .type_text("<calendar-event>")
43 .schema();
44
45pub const PRUNE_SCHEDULE_SCHEMA: Schema = StringSchema::new("Run prune job at specified schedule.")
46 .format(&ApiStringFormat::VerifyFn(
47 proxmox_time::verify_calendar_event,
48 ))
e3619d41
DM
49 .type_text("<calendar-event>")
50 .schema();
51
b22d785c
TL
52pub const VERIFICATION_SCHEDULE_SCHEMA: Schema =
53 StringSchema::new("Run verify job at specified schedule.")
54 .format(&ApiStringFormat::VerifyFn(
55 proxmox_time::verify_calendar_event,
56 ))
57 .type_text("<calendar-event>")
58 .schema();
e3619d41
DM
59
60pub const REMOVE_VANISHED_BACKUPS_SCHEMA: Schema = BooleanSchema::new(
b22d785c
TL
61 "Delete vanished backups. This remove the local copy if the remote backup was deleted.",
62)
63.default(false)
64.schema();
e3619d41
DM
65
66#[api(
67 properties: {
68 "next-run": {
69 description: "Estimated time of the next run (UNIX epoch).",
70 optional: true,
71 type: Integer,
72 },
73 "last-run-state": {
74 description: "Result of the last run.",
75 optional: true,
76 type: String,
77 },
78 "last-run-upid": {
79 description: "Task UPID of the last run.",
80 optional: true,
81 type: String,
82 },
83 "last-run-endtime": {
84 description: "Endtime of the last run.",
85 optional: true,
86 type: Integer,
87 },
88 }
89)]
aca9222e 90#[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
b22d785c 91#[serde(rename_all = "kebab-case")]
e3619d41
DM
92/// Job Scheduling Status
93pub struct JobScheduleStatus {
b22d785c 94 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41 95 pub next_run: Option<i64>,
b22d785c 96 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41 97 pub last_run_state: Option<String>,
b22d785c 98 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41 99 pub last_run_upid: Option<String>,
b22d785c 100 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41
DM
101 pub last_run_endtime: Option<i64>,
102}
103
104#[api()]
f680e72f 105#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
e3619d41
DM
106#[serde(rename_all = "lowercase")]
107/// When do we send notifications
108pub enum Notify {
109 /// Never send notification
110 Never,
111 /// Send notifications for failed and successful jobs
112 Always,
113 /// Send notifications for failed jobs only
114 Error,
115}
116
117#[api(
118 properties: {
119 gc: {
120 type: Notify,
121 optional: true,
122 },
123 verify: {
124 type: Notify,
125 optional: true,
126 },
127 sync: {
128 type: Notify,
129 optional: true,
130 },
cf91a072
DC
131 prune: {
132 type: Notify,
133 optional: true,
134 },
e3619d41
DM
135 },
136)]
137#[derive(Debug, Serialize, Deserialize)]
138/// Datastore notify settings
139pub struct DatastoreNotify {
140 /// Garbage collection settings
ded3a888 141 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41
DM
142 pub gc: Option<Notify>,
143 /// Verify job setting
ded3a888 144 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41
DM
145 pub verify: Option<Notify>,
146 /// Sync job setting
ded3a888 147 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41 148 pub sync: Option<Notify>,
cf91a072 149 /// Prune job setting
ded3a888 150 #[serde(skip_serializing_if = "Option::is_none")]
cf91a072 151 pub prune: Option<Notify>,
e3619d41
DM
152}
153
dfe17914
MS
154pub const DATASTORE_NOTIFY_STRING_SCHEMA: Schema = StringSchema::new(
155 "Datastore notification setting, enum can be one of 'always', 'never', or 'error'.",
156)
157.format(&ApiStringFormat::PropertyString(
158 &DatastoreNotify::API_SCHEMA,
159))
160.schema();
e3619d41
DM
161
162pub const IGNORE_VERIFIED_BACKUPS_SCHEMA: Schema = BooleanSchema::new(
b22d785c
TL
163 "Do not verify backups that are already verified if their verification is not outdated.",
164)
165.default(true)
166.schema();
e3619d41 167
b22d785c 168pub const VERIFICATION_OUTDATED_AFTER_SCHEMA: Schema =
7f3b4a94
TL
169 IntegerSchema::new("Days after that a verification becomes outdated. (0 is deprecated)'")
170 .minimum(0)
b22d785c 171 .schema();
e3619d41
DM
172
173#[api(
174 properties: {
175 id: {
176 schema: JOB_ID_SCHEMA,
177 },
178 store: {
179 schema: DATASTORE_SCHEMA,
180 },
181 "ignore-verified": {
182 optional: true,
183 schema: IGNORE_VERIFIED_BACKUPS_SCHEMA,
184 },
185 "outdated-after": {
186 optional: true,
187 schema: VERIFICATION_OUTDATED_AFTER_SCHEMA,
188 },
189 comment: {
190 optional: true,
191 schema: SINGLE_LINE_COMMENT_SCHEMA,
192 },
193 schedule: {
194 optional: true,
195 schema: VERIFICATION_SCHEDULE_SCHEMA,
196 },
59229bd7
TL
197 ns: {
198 optional: true,
199 schema: BACKUP_NAMESPACE_SCHEMA,
200 },
0b1edf29
TL
201 "max-depth": {
202 optional: true,
203 schema: crate::NS_MAX_DEPTH_SCHEMA,
204 },
e3619d41
DM
205 }
206)]
65c9e406 207#[derive(Serialize, Deserialize, Updater, Clone, PartialEq)]
b22d785c 208#[serde(rename_all = "kebab-case")]
e3619d41
DM
209/// Verification Job
210pub struct VerificationJobConfig {
211 /// unique ID to address this job
ffa403b5 212 #[updater(skip)]
e3619d41 213 pub id: String,
8772ca72 214 /// the datastore ID this verification job affects
e3619d41 215 pub store: String,
b22d785c 216 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41
DM
217 /// if not set to false, check the age of the last snapshot verification to filter
218 /// out recent ones, depending on 'outdated_after' configuration.
219 pub ignore_verified: Option<bool>,
b22d785c 220 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41
DM
221 /// Reverify snapshots after X days, never if 0. Ignored if 'ignore_verified' is false.
222 pub outdated_after: Option<i64>,
b22d785c 223 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41 224 pub comment: Option<String>,
b22d785c 225 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41
DM
226 /// when to schedule this job in calendar event notation
227 pub schedule: Option<String>,
59229bd7
TL
228 #[serde(skip_serializing_if = "Option::is_none", default)]
229 /// on which backup namespace to run the verification recursively
230 pub ns: Option<BackupNamespace>,
0b1edf29
TL
231 #[serde(skip_serializing_if = "Option::is_none", default)]
232 /// how deep the verify should go from the `ns` level downwards. Passing 0 verifies only the
233 /// snapshots on the same level as the passed `ns`, or the datastore root if none.
234 pub max_depth: Option<usize>,
e3619d41
DM
235}
236
0aa5815f 237impl VerificationJobConfig {
abd82485
FG
238 pub fn acl_path(&self) -> Vec<&str> {
239 match self.ns.as_ref() {
240 Some(ns) => ns.acl_path(&self.store),
241 None => vec!["datastore", &self.store],
0aa5815f
FG
242 }
243 }
244}
245
e3619d41
DM
246#[api(
247 properties: {
248 config: {
249 type: VerificationJobConfig,
250 },
251 status: {
252 type: JobScheduleStatus,
253 },
254 },
255)]
65c9e406 256#[derive(Serialize, Deserialize, Clone, PartialEq)]
b22d785c 257#[serde(rename_all = "kebab-case")]
e3619d41
DM
258/// Status of Verification Job
259pub struct VerificationJobStatus {
260 #[serde(flatten)]
261 pub config: VerificationJobConfig,
262 #[serde(flatten)]
263 pub status: JobScheduleStatus,
264}
265
266#[api(
267 properties: {
268 store: {
269 schema: DATASTORE_SCHEMA,
270 },
271 pool: {
272 schema: MEDIA_POOL_NAME_SCHEMA,
273 },
274 drive: {
275 schema: DRIVE_NAME_SCHEMA,
276 },
277 "eject-media": {
278 description: "Eject media upon job completion.",
279 type: bool,
280 optional: true,
281 },
282 "export-media-set": {
283 description: "Export media set upon job completion.",
284 type: bool,
285 optional: true,
286 },
287 "latest-only": {
288 description: "Backup latest snapshots only.",
289 type: bool,
290 optional: true,
291 },
292 "notify-user": {
293 optional: true,
294 type: Userid,
295 },
062edce2 296 "group-filter": {
91357c20
DC
297 schema: GROUP_FILTER_LIST_SCHEMA,
298 optional: true,
299 },
999293bb
DC
300 ns: {
301 type: BackupNamespace,
302 optional: true,
303 },
12d33461 304 "max-depth": {
999293bb
DC
305 schema: crate::NS_MAX_DEPTH_SCHEMA,
306 optional: true,
307 },
e3619d41
DM
308 }
309)]
65c9e406 310#[derive(Serialize, Deserialize, Clone, Updater, PartialEq)]
b22d785c 311#[serde(rename_all = "kebab-case")]
e3619d41
DM
312/// Tape Backup Job Setup
313pub struct TapeBackupJobSetup {
314 pub store: String,
315 pub pool: String,
316 pub drive: String,
b22d785c 317 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41 318 pub eject_media: Option<bool>,
b22d785c 319 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41 320 pub export_media_set: Option<bool>,
b22d785c 321 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41
DM
322 pub latest_only: Option<bool>,
323 /// Send job email notification to this user
b22d785c 324 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41 325 pub notify_user: Option<Userid>,
b22d785c 326 #[serde(skip_serializing_if = "Option::is_none")]
062edce2 327 pub group_filter: Option<Vec<GroupFilter>>,
999293bb
DC
328 #[serde(skip_serializing_if = "Option::is_none", default)]
329 pub ns: Option<BackupNamespace>,
330 #[serde(skip_serializing_if = "Option::is_none", default)]
12d33461 331 pub max_depth: Option<usize>,
e3619d41
DM
332}
333
334#[api(
335 properties: {
336 id: {
337 schema: JOB_ID_SCHEMA,
338 },
339 setup: {
340 type: TapeBackupJobSetup,
341 },
342 comment: {
343 optional: true,
344 schema: SINGLE_LINE_COMMENT_SCHEMA,
345 },
346 schedule: {
347 optional: true,
348 schema: SYNC_SCHEDULE_SCHEMA,
349 },
350 }
351)]
65c9e406 352#[derive(Serialize, Deserialize, Clone, Updater, PartialEq)]
b22d785c 353#[serde(rename_all = "kebab-case")]
e3619d41
DM
354/// Tape Backup Job
355pub struct TapeBackupJobConfig {
cdc83c4e 356 #[updater(skip)]
e3619d41
DM
357 pub id: String,
358 #[serde(flatten)]
359 pub setup: TapeBackupJobSetup,
b22d785c 360 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41 361 pub comment: Option<String>,
b22d785c 362 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41
DM
363 pub schedule: Option<String>,
364}
365
366#[api(
367 properties: {
368 config: {
369 type: TapeBackupJobConfig,
370 },
371 status: {
372 type: JobScheduleStatus,
373 },
374 },
375)]
65c9e406 376#[derive(Serialize, Deserialize, Clone, PartialEq)]
b22d785c 377#[serde(rename_all = "kebab-case")]
e3619d41
DM
378/// Status of Tape Backup Job
379pub struct TapeBackupJobStatus {
380 #[serde(flatten)]
381 pub config: TapeBackupJobConfig,
382 #[serde(flatten)]
383 pub status: JobScheduleStatus,
384 /// Next tape used (best guess)
b22d785c 385 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41
DM
386 pub next_media_label: Option<String>,
387}
388
3e276f6f
FG
389#[derive(Clone, Debug)]
390/// Filter for matching `BackupGroup`s, for use with `BackupGroup::filter`.
59c92736 391pub enum FilterType {
3e276f6f 392 /// BackupGroup type - either `vm`, `ct`, or `host`.
38aa71fc 393 BackupType(BackupType),
3e276f6f
FG
394 /// Full identifier of BackupGroup, including type
395 Group(String),
396 /// A regular expression matched against the full identifier of the BackupGroup
397 Regex(Regex),
398}
399
59c92736 400impl PartialEq for FilterType {
aca9222e
DM
401 fn eq(&self, other: &Self) -> bool {
402 match (self, other) {
403 (Self::BackupType(a), Self::BackupType(b)) => a == b,
404 (Self::Group(a), Self::Group(b)) => a == b,
405 (Self::Regex(a), Self::Regex(b)) => a.as_str() == b.as_str(),
406 _ => false,
407 }
408 }
409}
410
4f0dd337
WB
411impl std::str::FromStr for FilterType {
412 type Err = anyhow::Error;
413
414 fn from_str(s: &str) -> Result<Self, Self::Err> {
415 Ok(match s.split_once(':') {
416 Some(("group", value)) => BACKUP_GROUP_SCHEMA.parse_simple_value(value).map(|_| FilterType::Group(value.to_string()))?,
417 Some(("type", value)) => FilterType::BackupType(value.parse()?),
418 Some(("regex", value)) => FilterType::Regex(Regex::new(value)?),
419 Some((ty, _value)) => bail!("expected 'group', 'type' or 'regex' prefix, got '{}'", ty),
420 None => bail!("input doesn't match expected format '<group:GROUP||type:<vm|ct|host>|regex:REGEX>'"),
421 })
422 }
423}
424
126cf33c
WB
425// used for serializing below, caution!
426impl std::fmt::Display for FilterType {
427 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
428 match self {
429 FilterType::BackupType(backup_type) => write!(f, "type:{}", backup_type),
430 FilterType::Group(backup_group) => write!(f, "group:{}", backup_group),
431 FilterType::Regex(regex) => write!(f, "regex:{}", regex.as_str()),
432 }
433 }
434}
435
59c92736
PH
436#[derive(Clone, Debug)]
437pub struct GroupFilter {
438 pub is_exclude: bool,
439 pub filter_type: FilterType,
440}
441
442impl PartialEq for GroupFilter {
443 fn eq(&self, other: &Self) -> bool {
444 self.filter_type == other.filter_type && self.is_exclude == other.is_exclude
445 }
446}
447
448impl Eq for GroupFilter {}
449
3e276f6f
FG
450impl std::str::FromStr for GroupFilter {
451 type Err = anyhow::Error;
452
453 fn from_str(s: &str) -> Result<Self, Self::Err> {
59c92736
PH
454 let (is_exclude, type_str) = match s.split_once(':') {
455 Some(("include", value)) => (false, value),
456 Some(("exclude", value)) => (true, value),
457 _ => (false, s),
458 };
459
59c92736
PH
460 Ok(GroupFilter {
461 is_exclude,
4f0dd337 462 filter_type: type_str.parse()?,
59c92736 463 })
3e276f6f
FG
464 }
465}
466
467// used for serializing below, caution!
468impl std::fmt::Display for GroupFilter {
469 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126cf33c
WB
470 if self.is_exclude {
471 f.write_str("exclude:")?;
3e276f6f 472 }
126cf33c 473 std::fmt::Display::fmt(&self.filter_type, f)
3e276f6f
FG
474 }
475}
476
25877d05
DM
477proxmox_serde::forward_deserialize_to_from_str!(GroupFilter);
478proxmox_serde::forward_serialize_to_display!(GroupFilter);
3e276f6f
FG
479
480fn verify_group_filter(input: &str) -> Result<(), anyhow::Error> {
481 GroupFilter::from_str(input).map(|_| ())
482}
483
484pub const GROUP_FILTER_SCHEMA: Schema = StringSchema::new(
64dec8d6 485 "Group filter based on group identifier ('group:GROUP'), group type ('type:<vm|ct|host>'), or regex ('regex:RE'). Can be inverted by prepending 'exclude:'.")
3e276f6f 486 .format(&ApiStringFormat::VerifyFn(verify_group_filter))
59c92736 487 .type_text("[<exclude:|include:>]<type:<vm|ct|host>|group:GROUP|regex:RE>")
3e276f6f
FG
488 .schema();
489
b22d785c
TL
490pub const GROUP_FILTER_LIST_SCHEMA: Schema =
491 ArraySchema::new("List of group filters.", &GROUP_FILTER_SCHEMA).schema();
3e276f6f 492
9b67352a
SH
493pub const TRANSFER_LAST_SCHEMA: Schema =
494 IntegerSchema::new("Limit transfer to last N snapshots (per group), skipping others")
495 .minimum(1)
496 .schema();
497
e3619d41
DM
498#[api(
499 properties: {
500 id: {
501 schema: JOB_ID_SCHEMA,
502 },
503 store: {
504 schema: DATASTORE_SCHEMA,
505 },
c06c1b4b
FG
506 ns: {
507 type: BackupNamespace,
508 optional: true,
509 },
e3619d41
DM
510 "owner": {
511 type: Authid,
512 optional: true,
513 },
514 remote: {
515 schema: REMOTE_ID_SCHEMA,
4ec73327 516 optional: true,
e3619d41
DM
517 },
518 "remote-store": {
519 schema: DATASTORE_SCHEMA,
520 },
c06c1b4b
FG
521 "remote-ns": {
522 type: BackupNamespace,
523 optional: true,
524 },
e3619d41
DM
525 "remove-vanished": {
526 schema: REMOVE_VANISHED_BACKUPS_SCHEMA,
527 optional: true,
528 },
c06c1b4b 529 "max-depth": {
e40c7fb9 530 schema: NS_MAX_DEPTH_REDUCED_SCHEMA,
c06c1b4b
FG
531 optional: true,
532 },
e3619d41
DM
533 comment: {
534 optional: true,
535 schema: SINGLE_LINE_COMMENT_SCHEMA,
536 },
6eb756bc
DM
537 limit: {
538 type: RateLimitConfig,
539 },
e3619d41
DM
540 schedule: {
541 optional: true,
542 schema: SYNC_SCHEDULE_SCHEMA,
543 },
062edce2 544 "group-filter": {
5f83d3f6
FG
545 schema: GROUP_FILTER_LIST_SCHEMA,
546 optional: true,
547 },
9b67352a
SH
548 "transfer-last": {
549 schema: TRANSFER_LAST_SCHEMA,
550 optional: true,
551 },
e3619d41
DM
552 }
553)]
aca9222e 554#[derive(Serialize, Deserialize, Clone, Updater, PartialEq)]
b22d785c 555#[serde(rename_all = "kebab-case")]
e3619d41
DM
556/// Sync Job
557pub struct SyncJobConfig {
5bd77f00 558 #[updater(skip)]
e3619d41
DM
559 pub id: String,
560 pub store: String,
b22d785c 561 #[serde(skip_serializing_if = "Option::is_none")]
c06c1b4b
FG
562 pub ns: Option<BackupNamespace>,
563 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41 564 pub owner: Option<Authid>,
4ec73327
HL
565 #[serde(skip_serializing_if = "Option::is_none")]
566 /// None implies local sync.
567 pub remote: Option<String>,
e3619d41 568 pub remote_store: String,
b22d785c 569 #[serde(skip_serializing_if = "Option::is_none")]
c06c1b4b
FG
570 pub remote_ns: Option<BackupNamespace>,
571 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41 572 pub remove_vanished: Option<bool>,
b9310489
FG
573 #[serde(skip_serializing_if = "Option::is_none")]
574 pub max_depth: Option<usize>,
b22d785c 575 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41 576 pub comment: Option<String>,
b22d785c 577 #[serde(skip_serializing_if = "Option::is_none")]
e3619d41 578 pub schedule: Option<String>,
b22d785c 579 #[serde(skip_serializing_if = "Option::is_none")]
062edce2 580 pub group_filter: Option<Vec<GroupFilter>>,
6eb756bc
DM
581 #[serde(flatten)]
582 pub limit: RateLimitConfig,
9b67352a
SH
583 #[serde(skip_serializing_if = "Option::is_none")]
584 pub transfer_last: Option<usize>,
e3619d41
DM
585}
586
83e30003 587impl SyncJobConfig {
abd82485
FG
588 pub fn acl_path(&self) -> Vec<&str> {
589 match self.ns.as_ref() {
590 Some(ns) => ns.acl_path(&self.store),
591 None => vec!["datastore", &self.store],
83e30003
FG
592 }
593 }
594}
595
e3619d41
DM
596#[api(
597 properties: {
598 config: {
599 type: SyncJobConfig,
600 },
601 status: {
602 type: JobScheduleStatus,
603 },
604 },
605)]
aca9222e 606#[derive(Serialize, Deserialize, Clone, PartialEq)]
b22d785c 607#[serde(rename_all = "kebab-case")]
e3619d41
DM
608/// Status of Sync Job
609pub struct SyncJobStatus {
610 #[serde(flatten)]
611 pub config: SyncJobConfig,
612 #[serde(flatten)]
613 pub status: JobScheduleStatus,
614}
5557af0e
WB
615
616/// These are used separately without `ns`/`max-depth` sometimes in the API, specifically in the API
617/// call to prune a specific group, where `max-depth` makes no sense.
618#[api(
619 properties: {
620 "keep-last": {
621 schema: crate::PRUNE_SCHEMA_KEEP_LAST,
622 optional: true,
623 },
624 "keep-hourly": {
625 schema: crate::PRUNE_SCHEMA_KEEP_HOURLY,
626 optional: true,
627 },
628 "keep-daily": {
629 schema: crate::PRUNE_SCHEMA_KEEP_DAILY,
630 optional: true,
631 },
632 "keep-weekly": {
633 schema: crate::PRUNE_SCHEMA_KEEP_WEEKLY,
634 optional: true,
635 },
636 "keep-monthly": {
637 schema: crate::PRUNE_SCHEMA_KEEP_MONTHLY,
638 optional: true,
639 },
640 "keep-yearly": {
641 schema: crate::PRUNE_SCHEMA_KEEP_YEARLY,
642 optional: true,
643 },
644 }
645)]
aca9222e 646#[derive(Serialize, Deserialize, Default, Updater, Clone, PartialEq)]
5557af0e
WB
647#[serde(rename_all = "kebab-case")]
648/// Common pruning options
649pub struct KeepOptions {
650 #[serde(skip_serializing_if = "Option::is_none")]
651 pub keep_last: Option<u64>,
652 #[serde(skip_serializing_if = "Option::is_none")]
653 pub keep_hourly: Option<u64>,
654 #[serde(skip_serializing_if = "Option::is_none")]
655 pub keep_daily: Option<u64>,
656 #[serde(skip_serializing_if = "Option::is_none")]
657 pub keep_weekly: Option<u64>,
658 #[serde(skip_serializing_if = "Option::is_none")]
659 pub keep_monthly: Option<u64>,
660 #[serde(skip_serializing_if = "Option::is_none")]
661 pub keep_yearly: Option<u64>,
662}
663
664impl KeepOptions {
665 pub fn keeps_something(&self) -> bool {
666 self.keep_last.unwrap_or(0)
667 + self.keep_hourly.unwrap_or(0)
668 + self.keep_daily.unwrap_or(0)
a8d3f194 669 + self.keep_weekly.unwrap_or(0)
5557af0e
WB
670 + self.keep_monthly.unwrap_or(0)
671 + self.keep_yearly.unwrap_or(0)
672 > 0
673 }
674}
675
676#[api(
677 properties: {
678 keep: {
679 type: KeepOptions,
680 },
681 ns: {
682 type: BackupNamespace,
683 optional: true,
684 },
685 "max-depth": {
686 schema: NS_MAX_DEPTH_REDUCED_SCHEMA,
687 optional: true,
688 },
689 }
690)]
65c9e406 691#[derive(Serialize, Deserialize, Default, Updater, Clone, PartialEq)]
5557af0e
WB
692#[serde(rename_all = "kebab-case")]
693/// Common pruning options
694pub struct PruneJobOptions {
695 #[serde(flatten)]
696 pub keep: KeepOptions,
697
698 /// The (optional) recursion depth
699 #[serde(skip_serializing_if = "Option::is_none")]
700 pub max_depth: Option<usize>,
701
702 #[serde(skip_serializing_if = "Option::is_none")]
703 pub ns: Option<BackupNamespace>,
704}
705
706impl PruneJobOptions {
707 pub fn keeps_something(&self) -> bool {
708 self.keep.keeps_something()
709 }
710
711 pub fn acl_path<'a>(&'a self, store: &'a str) -> Vec<&'a str> {
712 match &self.ns {
713 Some(ns) => ns.acl_path(store),
714 None => vec!["datastore", store],
715 }
716 }
717}
718
719#[api(
720 properties: {
721 disable: {
722 type: Boolean,
723 optional: true,
724 default: false,
725 },
726 id: {
727 schema: JOB_ID_SCHEMA,
728 },
729 store: {
730 schema: DATASTORE_SCHEMA,
731 },
732 schedule: {
733 schema: PRUNE_SCHEDULE_SCHEMA,
5557af0e
WB
734 },
735 comment: {
736 optional: true,
737 schema: SINGLE_LINE_COMMENT_SCHEMA,
738 },
739 options: {
740 type: PruneJobOptions,
741 },
742 },
743)]
65c9e406 744#[derive(Deserialize, Serialize, Updater, Clone, PartialEq)]
5557af0e
WB
745#[serde(rename_all = "kebab-case")]
746/// Prune configuration.
747pub struct PruneJobConfig {
748 /// unique ID to address this job
749 #[updater(skip)]
750 pub id: String,
751
752 pub store: String,
753
754 /// Disable this job.
755 #[serde(default, skip_serializing_if = "is_false")]
756 #[updater(serde(skip_serializing_if = "Option::is_none"))]
757 pub disable: bool,
758
759 pub schedule: String,
760
761 #[serde(skip_serializing_if = "Option::is_none")]
762 pub comment: Option<String>,
763
764 #[serde(flatten)]
765 pub options: PruneJobOptions,
766}
767
768impl PruneJobConfig {
769 pub fn acl_path(&self) -> Vec<&str> {
770 self.options.acl_path(&self.store)
771 }
772}
773
774fn is_false(b: &bool) -> bool {
775 !b
776}
777
778#[api(
779 properties: {
780 config: {
781 type: PruneJobConfig,
782 },
783 status: {
784 type: JobScheduleStatus,
785 },
786 },
787)]
65c9e406 788#[derive(Serialize, Deserialize, Clone, PartialEq)]
5557af0e
WB
789#[serde(rename_all = "kebab-case")]
790/// Status of prune job
791pub struct PruneJobStatus {
792 #[serde(flatten)]
793 pub config: PruneJobConfig,
794 #[serde(flatten)]
795 pub status: JobScheduleStatus,
796}