1 use anyhow
::format_err
;
5 use serde
::{Deserialize, Serialize}
;
10 Userid
, Authid
, RateLimitConfig
,
11 REMOTE_ID_SCHEMA
, DRIVE_NAME_SCHEMA
, MEDIA_POOL_NAME_SCHEMA
,
12 SINGLE_LINE_COMMENT_SCHEMA
, PROXMOX_SAFE_ID_FORMAT
, DATASTORE_SCHEMA
,
13 BACKUP_GROUP_SCHEMA
, BACKUP_TYPE_SCHEMA
,
18 /// Regex for verification jobs 'DATASTORE:ACTUAL_JOB_ID'
19 pub VERIFICATION_JOB_WORKER_ID_REGEX
= concat
!(r
"^(", PROXMOX_SAFE_ID_REGEX_STR
!(), r
"):");
20 /// Regex for sync jobs 'REMOTE:REMOTE_DATASTORE:LOCAL_DATASTORE: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
"):");
24 pub const JOB_ID_SCHEMA
: Schema
= StringSchema
::new("Job ID.")
25 .format(&PROXMOX_SAFE_ID_FORMAT
)
30 pub const SYNC_SCHEDULE_SCHEMA
: Schema
= StringSchema
::new(
31 "Run sync job at specified schedule.")
32 .format(&ApiStringFormat
::VerifyFn(proxmox_time
::verify_calendar_event
))
33 .type_text("<calendar-event>")
36 pub const GC_SCHEDULE_SCHEMA
: Schema
= StringSchema
::new(
37 "Run garbage collection job at specified schedule.")
38 .format(&ApiStringFormat
::VerifyFn(proxmox_time
::verify_calendar_event
))
39 .type_text("<calendar-event>")
42 pub const PRUNE_SCHEDULE_SCHEMA
: Schema
= StringSchema
::new(
43 "Run prune job at specified schedule.")
44 .format(&ApiStringFormat
::VerifyFn(proxmox_time
::verify_calendar_event
))
45 .type_text("<calendar-event>")
48 pub const VERIFICATION_SCHEDULE_SCHEMA
: Schema
= StringSchema
::new(
49 "Run verify job at specified schedule.")
50 .format(&ApiStringFormat
::VerifyFn(proxmox_time
::verify_calendar_event
))
51 .type_text("<calendar-event>")
54 pub const REMOVE_VANISHED_BACKUPS_SCHEMA
: Schema
= BooleanSchema
::new(
55 "Delete vanished backups. This remove the local copy if the remote backup was deleted.")
62 description
: "Estimated time of the next run (UNIX epoch).",
67 description
: "Result of the last run.",
72 description
: "Task UPID of the last run.",
77 description
: "Endtime of the last run.",
83 #[derive(Serialize,Deserialize,Default)]
84 #[serde(rename_all="kebab-case")]
85 /// Job Scheduling Status
86 pub struct JobScheduleStatus
{
87 #[serde(skip_serializing_if="Option::is_none")]
88 pub next_run
: Option
<i64>,
89 #[serde(skip_serializing_if="Option::is_none")]
90 pub last_run_state
: Option
<String
>,
91 #[serde(skip_serializing_if="Option::is_none")]
92 pub last_run_upid
: Option
<String
>,
93 #[serde(skip_serializing_if="Option::is_none")]
94 pub last_run_endtime
: Option
<i64>,
98 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
99 #[serde(rename_all = "lowercase")]
100 /// When do we send notifications
102 /// Never send notification
104 /// Send notifications for failed and successful jobs
106 /// Send notifications for failed jobs only
126 #[derive(Debug, Serialize, Deserialize)]
127 /// Datastore notify settings
128 pub struct DatastoreNotify
{
129 /// Garbage collection settings
130 pub gc
: Option
<Notify
>,
131 /// Verify job setting
132 pub verify
: Option
<Notify
>,
134 pub sync
: Option
<Notify
>,
137 pub const DATASTORE_NOTIFY_STRING_SCHEMA
: Schema
= StringSchema
::new(
138 "Datastore notification setting")
139 .format(&ApiStringFormat
::PropertyString(&DatastoreNotify
::API_SCHEMA
))
142 pub const IGNORE_VERIFIED_BACKUPS_SCHEMA
: Schema
= BooleanSchema
::new(
143 "Do not verify backups that are already verified if their verification is not outdated.")
147 pub const VERIFICATION_OUTDATED_AFTER_SCHEMA
: Schema
= IntegerSchema
::new(
148 "Days after that a verification becomes outdated")
155 schema
: JOB_ID_SCHEMA
,
158 schema
: DATASTORE_SCHEMA
,
162 schema
: IGNORE_VERIFIED_BACKUPS_SCHEMA
,
166 schema
: VERIFICATION_OUTDATED_AFTER_SCHEMA
,
170 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
174 schema
: VERIFICATION_SCHEDULE_SCHEMA
,
178 #[derive(Serialize,Deserialize,Updater)]
179 #[serde(rename_all="kebab-case")]
181 pub struct VerificationJobConfig
{
182 /// unique ID to address this job
185 /// the datastore ID this verificaiton job affects
187 #[serde(skip_serializing_if="Option::is_none")]
188 /// if not set to false, check the age of the last snapshot verification to filter
189 /// out recent ones, depending on 'outdated_after' configuration.
190 pub ignore_verified
: Option
<bool
>,
191 #[serde(skip_serializing_if="Option::is_none")]
192 /// Reverify snapshots after X days, never if 0. Ignored if 'ignore_verified' is false.
193 pub outdated_after
: Option
<i64>,
194 #[serde(skip_serializing_if="Option::is_none")]
195 pub comment
: Option
<String
>,
196 #[serde(skip_serializing_if="Option::is_none")]
197 /// when to schedule this job in calendar event notation
198 pub schedule
: Option
<String
>,
204 type: VerificationJobConfig
,
207 type: JobScheduleStatus
,
211 #[derive(Serialize,Deserialize)]
212 #[serde(rename_all="kebab-case")]
213 /// Status of Verification Job
214 pub struct VerificationJobStatus
{
216 pub config
: VerificationJobConfig
,
218 pub status
: JobScheduleStatus
,
224 schema
: DATASTORE_SCHEMA
,
227 schema
: MEDIA_POOL_NAME_SCHEMA
,
230 schema
: DRIVE_NAME_SCHEMA
,
233 description
: "Eject media upon job completion.",
237 "export-media-set": {
238 description
: "Export media set upon job completion.",
243 description
: "Backup latest snapshots only.",
252 schema
: GROUP_FILTER_LIST_SCHEMA
,
257 #[derive(Serialize,Deserialize,Clone,Updater)]
258 #[serde(rename_all="kebab-case")]
259 /// Tape Backup Job Setup
260 pub struct TapeBackupJobSetup
{
264 #[serde(skip_serializing_if="Option::is_none")]
265 pub eject_media
: Option
<bool
>,
266 #[serde(skip_serializing_if="Option::is_none")]
267 pub export_media_set
: Option
<bool
>,
268 #[serde(skip_serializing_if="Option::is_none")]
269 pub latest_only
: Option
<bool
>,
270 /// Send job email notification to this user
271 #[serde(skip_serializing_if="Option::is_none")]
272 pub notify_user
: Option
<Userid
>,
273 #[serde(skip_serializing_if="Option::is_none")]
274 pub group_filter
: Option
<Vec
<GroupFilter
>>,
280 schema
: JOB_ID_SCHEMA
,
283 type: TapeBackupJobSetup
,
287 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
291 schema
: SYNC_SCHEDULE_SCHEMA
,
295 #[derive(Serialize,Deserialize,Clone,Updater)]
296 #[serde(rename_all="kebab-case")]
298 pub struct TapeBackupJobConfig
{
302 pub setup
: TapeBackupJobSetup
,
303 #[serde(skip_serializing_if="Option::is_none")]
304 pub comment
: Option
<String
>,
305 #[serde(skip_serializing_if="Option::is_none")]
306 pub schedule
: Option
<String
>,
312 type: TapeBackupJobConfig
,
315 type: JobScheduleStatus
,
319 #[derive(Serialize,Deserialize)]
320 #[serde(rename_all="kebab-case")]
321 /// Status of Tape Backup Job
322 pub struct TapeBackupJobStatus
{
324 pub config
: TapeBackupJobConfig
,
326 pub status
: JobScheduleStatus
,
327 /// Next tape used (best guess)
328 #[serde(skip_serializing_if="Option::is_none")]
329 pub next_media_label
: Option
<String
>,
332 #[derive(Clone, Debug)]
333 /// Filter for matching `BackupGroup`s, for use with `BackupGroup::filter`.
334 pub enum GroupFilter
{
335 /// BackupGroup type - either `vm`, `ct`, or `host`.
337 /// Full identifier of BackupGroup, including type
339 /// A regular expression matched against the full identifier of the BackupGroup
343 impl std
::str::FromStr
for GroupFilter
{
344 type Err
= anyhow
::Error
;
346 fn from_str(s
: &str) -> Result
<Self, Self::Err
> {
347 match s
.split_once(":") {
348 Some(("group", value
)) => BACKUP_GROUP_SCHEMA
.parse_simple_value(value
).map(|_
| GroupFilter
::Group(value
.to_string())),
349 Some(("type", value
)) => BACKUP_TYPE_SCHEMA
.parse_simple_value(value
).map(|_
| GroupFilter
::BackupType(value
.to_string())),
350 Some(("regex", value
)) => Ok(GroupFilter
::Regex(Regex
::new(value
)?
)),
351 Some((ty
, _value
)) => Err(format_err
!("expected 'group', 'type' or 'regex' prefix, got '{}'", ty
)),
352 None
=> Err(format_err
!("input doesn't match expected format '<group:GROUP||type:<vm|ct|host>|regex:REGEX>'")),
353 }.map_err(|err
| format_err
!("'{}' - {}", s
, err
))
357 // used for serializing below, caution!
358 impl std
::fmt
::Display
for GroupFilter
{
359 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
361 GroupFilter
::BackupType(backup_type
) => write
!(f
, "type:{}", backup_type
),
362 GroupFilter
::Group(backup_group
) => write
!(f
, "group:{}", backup_group
),
363 GroupFilter
::Regex(regex
) => write
!(f
, "regex:{}", regex
.as_str()),
368 proxmox_serde
::forward_deserialize_to_from_str
!(GroupFilter
);
369 proxmox_serde
::forward_serialize_to_display
!(GroupFilter
);
371 fn verify_group_filter(input
: &str) -> Result
<(), anyhow
::Error
> {
372 GroupFilter
::from_str(input
).map(|_
| ())
375 pub const GROUP_FILTER_SCHEMA
: Schema
= StringSchema
::new(
376 "Group filter based on group identifier ('group:GROUP'), group type ('type:<vm|ct|host>'), or regex ('regex:RE').")
377 .format(&ApiStringFormat
::VerifyFn(verify_group_filter
))
378 .type_text("<type:<vm|ct|host>|group:GROUP|regex:RE>")
381 pub const GROUP_FILTER_LIST_SCHEMA
: Schema
= ArraySchema
::new("List of group filters.", &GROUP_FILTER_SCHEMA
).schema();
386 schema
: JOB_ID_SCHEMA
,
389 schema
: DATASTORE_SCHEMA
,
396 schema
: REMOTE_ID_SCHEMA
,
399 schema
: DATASTORE_SCHEMA
,
402 schema
: REMOVE_VANISHED_BACKUPS_SCHEMA
,
407 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
410 type: RateLimitConfig
,
414 schema
: SYNC_SCHEDULE_SCHEMA
,
417 schema
: GROUP_FILTER_LIST_SCHEMA
,
422 #[derive(Serialize,Deserialize,Clone,Updater)]
423 #[serde(rename_all="kebab-case")]
425 pub struct SyncJobConfig
{
429 #[serde(skip_serializing_if="Option::is_none")]
430 pub owner
: Option
<Authid
>,
432 pub remote_store
: String
,
433 #[serde(skip_serializing_if="Option::is_none")]
434 pub remove_vanished
: Option
<bool
>,
435 #[serde(skip_serializing_if="Option::is_none")]
436 pub comment
: Option
<String
>,
437 #[serde(skip_serializing_if="Option::is_none")]
438 pub schedule
: Option
<String
>,
439 #[serde(skip_serializing_if="Option::is_none")]
440 pub group_filter
: Option
<Vec
<GroupFilter
>>,
442 pub limit
: RateLimitConfig
,
451 type: JobScheduleStatus
,
456 #[derive(Serialize,Deserialize)]
457 #[serde(rename_all="kebab-case")]
458 /// Status of Sync Job
459 pub struct SyncJobStatus
{
461 pub config
: SyncJobConfig
,
463 pub status
: JobScheduleStatus
,