1 use anyhow
::format_err
;
5 use serde
::{Deserialize, Serialize}
;
10 Userid
, Authid
, REMOTE_ID_SCHEMA
, DRIVE_NAME_SCHEMA
, MEDIA_POOL_NAME_SCHEMA
,
11 SINGLE_LINE_COMMENT_SCHEMA
, PROXMOX_SAFE_ID_FORMAT
, DATASTORE_SCHEMA
,
12 BACKUP_GROUP_SCHEMA
, BACKUP_TYPE_SCHEMA
,
17 /// Regex for verification jobs 'DATASTORE:ACTUAL_JOB_ID'
18 pub VERIFICATION_JOB_WORKER_ID_REGEX
= concat
!(r
"^(", PROXMOX_SAFE_ID_REGEX_STR
!(), r
"):");
19 /// Regex for sync jobs 'REMOTE:REMOTE_DATASTORE:LOCAL_DATASTORE:ACTUAL_JOB_ID'
20 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
"):");
23 pub const JOB_ID_SCHEMA
: Schema
= StringSchema
::new("Job ID.")
24 .format(&PROXMOX_SAFE_ID_FORMAT
)
29 pub const SYNC_SCHEDULE_SCHEMA
: Schema
= StringSchema
::new(
30 "Run sync job at specified schedule.")
31 .format(&ApiStringFormat
::VerifyFn(proxmox_time
::verify_calendar_event
))
32 .type_text("<calendar-event>")
35 pub const GC_SCHEDULE_SCHEMA
: Schema
= StringSchema
::new(
36 "Run garbage collection job at specified schedule.")
37 .format(&ApiStringFormat
::VerifyFn(proxmox_time
::verify_calendar_event
))
38 .type_text("<calendar-event>")
41 pub const PRUNE_SCHEDULE_SCHEMA
: Schema
= StringSchema
::new(
42 "Run prune job at specified schedule.")
43 .format(&ApiStringFormat
::VerifyFn(proxmox_time
::verify_calendar_event
))
44 .type_text("<calendar-event>")
47 pub const VERIFICATION_SCHEDULE_SCHEMA
: Schema
= StringSchema
::new(
48 "Run verify job at specified schedule.")
49 .format(&ApiStringFormat
::VerifyFn(proxmox_time
::verify_calendar_event
))
50 .type_text("<calendar-event>")
53 pub const REMOVE_VANISHED_BACKUPS_SCHEMA
: Schema
= BooleanSchema
::new(
54 "Delete vanished backups. This remove the local copy if the remote backup was deleted.")
61 description
: "Estimated time of the next run (UNIX epoch).",
66 description
: "Result of the last run.",
71 description
: "Task UPID of the last run.",
76 description
: "Endtime of the last run.",
82 #[derive(Serialize,Deserialize,Default)]
83 #[serde(rename_all="kebab-case")]
84 /// Job Scheduling Status
85 pub struct JobScheduleStatus
{
86 #[serde(skip_serializing_if="Option::is_none")]
87 pub next_run
: Option
<i64>,
88 #[serde(skip_serializing_if="Option::is_none")]
89 pub last_run_state
: Option
<String
>,
90 #[serde(skip_serializing_if="Option::is_none")]
91 pub last_run_upid
: Option
<String
>,
92 #[serde(skip_serializing_if="Option::is_none")]
93 pub last_run_endtime
: Option
<i64>,
97 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
98 #[serde(rename_all = "lowercase")]
99 /// When do we send notifications
101 /// Never send notification
103 /// Send notifications for failed and successful jobs
105 /// Send notifications for failed jobs only
125 #[derive(Debug, Serialize, Deserialize)]
126 /// Datastore notify settings
127 pub struct DatastoreNotify
{
128 /// Garbage collection settings
129 pub gc
: Option
<Notify
>,
130 /// Verify job setting
131 pub verify
: Option
<Notify
>,
133 pub sync
: Option
<Notify
>,
136 pub const DATASTORE_NOTIFY_STRING_SCHEMA
: Schema
= StringSchema
::new(
137 "Datastore notification setting")
138 .format(&ApiStringFormat
::PropertyString(&DatastoreNotify
::API_SCHEMA
))
141 pub const IGNORE_VERIFIED_BACKUPS_SCHEMA
: Schema
= BooleanSchema
::new(
142 "Do not verify backups that are already verified if their verification is not outdated.")
146 pub const VERIFICATION_OUTDATED_AFTER_SCHEMA
: Schema
= IntegerSchema
::new(
147 "Days after that a verification becomes outdated")
154 schema
: JOB_ID_SCHEMA
,
157 schema
: DATASTORE_SCHEMA
,
161 schema
: IGNORE_VERIFIED_BACKUPS_SCHEMA
,
165 schema
: VERIFICATION_OUTDATED_AFTER_SCHEMA
,
169 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
173 schema
: VERIFICATION_SCHEDULE_SCHEMA
,
177 #[derive(Serialize,Deserialize,Updater)]
178 #[serde(rename_all="kebab-case")]
180 pub struct VerificationJobConfig
{
181 /// unique ID to address this job
184 /// the datastore ID this verificaiton job affects
186 #[serde(skip_serializing_if="Option::is_none")]
187 /// if not set to false, check the age of the last snapshot verification to filter
188 /// out recent ones, depending on 'outdated_after' configuration.
189 pub ignore_verified
: Option
<bool
>,
190 #[serde(skip_serializing_if="Option::is_none")]
191 /// Reverify snapshots after X days, never if 0. Ignored if 'ignore_verified' is false.
192 pub outdated_after
: Option
<i64>,
193 #[serde(skip_serializing_if="Option::is_none")]
194 pub comment
: Option
<String
>,
195 #[serde(skip_serializing_if="Option::is_none")]
196 /// when to schedule this job in calendar event notation
197 pub schedule
: Option
<String
>,
203 type: VerificationJobConfig
,
206 type: JobScheduleStatus
,
210 #[derive(Serialize,Deserialize)]
211 #[serde(rename_all="kebab-case")]
212 /// Status of Verification Job
213 pub struct VerificationJobStatus
{
215 pub config
: VerificationJobConfig
,
217 pub status
: JobScheduleStatus
,
223 schema
: DATASTORE_SCHEMA
,
226 schema
: MEDIA_POOL_NAME_SCHEMA
,
229 schema
: DRIVE_NAME_SCHEMA
,
232 description
: "Eject media upon job completion.",
236 "export-media-set": {
237 description
: "Export media set upon job completion.",
242 description
: "Backup latest snapshots only.",
251 schema
: GROUP_FILTER_LIST_SCHEMA
,
256 #[derive(Serialize,Deserialize,Clone,Updater)]
257 #[serde(rename_all="kebab-case")]
258 /// Tape Backup Job Setup
259 pub struct TapeBackupJobSetup
{
263 #[serde(skip_serializing_if="Option::is_none")]
264 pub eject_media
: Option
<bool
>,
265 #[serde(skip_serializing_if="Option::is_none")]
266 pub export_media_set
: Option
<bool
>,
267 #[serde(skip_serializing_if="Option::is_none")]
268 pub latest_only
: Option
<bool
>,
269 /// Send job email notification to this user
270 #[serde(skip_serializing_if="Option::is_none")]
271 pub notify_user
: Option
<Userid
>,
272 #[serde(skip_serializing_if="Option::is_none")]
273 pub groups
: Option
<Vec
<GroupFilter
>>,
279 schema
: JOB_ID_SCHEMA
,
282 type: TapeBackupJobSetup
,
286 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
290 schema
: SYNC_SCHEDULE_SCHEMA
,
294 #[derive(Serialize,Deserialize,Clone,Updater)]
295 #[serde(rename_all="kebab-case")]
297 pub struct TapeBackupJobConfig
{
301 pub setup
: TapeBackupJobSetup
,
302 #[serde(skip_serializing_if="Option::is_none")]
303 pub comment
: Option
<String
>,
304 #[serde(skip_serializing_if="Option::is_none")]
305 pub schedule
: Option
<String
>,
311 type: TapeBackupJobConfig
,
314 type: JobScheduleStatus
,
318 #[derive(Serialize,Deserialize)]
319 #[serde(rename_all="kebab-case")]
320 /// Status of Tape Backup Job
321 pub struct TapeBackupJobStatus
{
323 pub config
: TapeBackupJobConfig
,
325 pub status
: JobScheduleStatus
,
326 /// Next tape used (best guess)
327 #[serde(skip_serializing_if="Option::is_none")]
328 pub next_media_label
: Option
<String
>,
331 #[derive(Clone, Debug)]
332 /// Filter for matching `BackupGroup`s, for use with `BackupGroup::filter`.
333 pub enum GroupFilter
{
334 /// BackupGroup type - either `vm`, `ct`, or `host`.
336 /// Full identifier of BackupGroup, including type
338 /// A regular expression matched against the full identifier of the BackupGroup
342 impl std
::str::FromStr
for GroupFilter
{
343 type Err
= anyhow
::Error
;
345 fn from_str(s
: &str) -> Result
<Self, Self::Err
> {
346 match s
.split_once(":") {
347 Some(("group", value
)) => parse_simple_value(value
, &BACKUP_GROUP_SCHEMA
).map(|_
| GroupFilter
::Group(value
.to_string())),
348 Some(("type", value
)) => parse_simple_value(value
, &BACKUP_TYPE_SCHEMA
).map(|_
| GroupFilter
::BackupType(value
.to_string())),
349 Some(("regex", value
)) => Ok(GroupFilter
::Regex(Regex
::new(value
)?
)),
350 Some((ty
, _value
)) => Err(format_err
!("expected 'group', 'type' or 'regex' prefix, got '{}'", ty
)),
351 None
=> Err(format_err
!("input doesn't match expected format '<group:GROUP||type:<vm|ct|host>|regex:REGEX>'")),
352 }.map_err(|err
| format_err
!("'{}' - {}", s
, err
))
356 // used for serializing below, caution!
357 impl std
::fmt
::Display
for GroupFilter
{
358 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
360 GroupFilter
::BackupType(backup_type
) => write
!(f
, "type:{}", backup_type
),
361 GroupFilter
::Group(backup_group
) => write
!(f
, "group:{}", backup_group
),
362 GroupFilter
::Regex(regex
) => write
!(f
, "regex:{}", regex
.as_str()),
367 proxmox
::forward_deserialize_to_from_str
!(GroupFilter
);
368 proxmox
::forward_serialize_to_display
!(GroupFilter
);
370 fn verify_group_filter(input
: &str) -> Result
<(), anyhow
::Error
> {
371 GroupFilter
::from_str(input
).map(|_
| ())
374 pub const GROUP_FILTER_SCHEMA
: Schema
= StringSchema
::new(
375 "Group filter based on group identifier ('group:GROUP'), group type ('type:<vm|ct|host>'), or regex ('regex:RE').")
376 .format(&ApiStringFormat
::VerifyFn(verify_group_filter
))
377 .type_text("<type:<vm|ct|host>|group:GROUP|regex:RE>")
380 pub const GROUP_FILTER_LIST_SCHEMA
: Schema
= ArraySchema
::new("List of group filters.", &GROUP_FILTER_SCHEMA
).schema();
385 schema
: JOB_ID_SCHEMA
,
388 schema
: DATASTORE_SCHEMA
,
395 schema
: REMOTE_ID_SCHEMA
,
398 schema
: DATASTORE_SCHEMA
,
401 schema
: REMOVE_VANISHED_BACKUPS_SCHEMA
,
406 schema
: SINGLE_LINE_COMMENT_SCHEMA
,
410 schema
: SYNC_SCHEDULE_SCHEMA
,
413 schema
: GROUP_FILTER_LIST_SCHEMA
,
418 #[derive(Serialize,Deserialize,Clone,Updater)]
419 #[serde(rename_all="kebab-case")]
421 pub struct SyncJobConfig
{
425 #[serde(skip_serializing_if="Option::is_none")]
426 pub owner
: Option
<Authid
>,
428 pub remote_store
: String
,
429 #[serde(skip_serializing_if="Option::is_none")]
430 pub remove_vanished
: Option
<bool
>,
431 #[serde(skip_serializing_if="Option::is_none")]
432 pub comment
: Option
<String
>,
433 #[serde(skip_serializing_if="Option::is_none")]
434 pub schedule
: Option
<String
>,
435 #[serde(skip_serializing_if="Option::is_none")]
436 pub groups
: Option
<Vec
<GroupFilter
>>,
445 type: JobScheduleStatus
,
450 #[derive(Serialize,Deserialize)]
451 #[serde(rename_all="kebab-case")]
452 /// Status of Sync Job
453 pub struct SyncJobStatus
{
455 pub config
: SyncJobConfig
,
457 pub status
: JobScheduleStatus
,