]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/types/mod.rs
api2/admin/datastore: refactor list_dir_content in catalog_reader
[proxmox-backup.git] / src / api2 / types / mod.rs
CommitLineData
bf78f708
DM
1//! API Type Definitions
2
e7cb4dc5
WB
3use anyhow::bail;
4use serde::{Deserialize, Serialize};
4ebf0eab 5
9ea4bce4
WB
6use proxmox::api::{api, schema::*};
7use proxmox::const_regex;
60b9b48e 8use proxmox::{IPRE, IPRE_BRACKET, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32};
255f378a 9
bf78f708
DM
10use crate::{
11 backup::{
12 CryptMode,
13 Fingerprint,
14 BACKUP_ID_REGEX,
227501c0
DC
15 DirEntryAttribute,
16 CatalogEntryType,
bf78f708
DM
17 },
18 server::UPID,
19 config::acl::Role,
20};
f28d9088 21
e7cb4dc5
WB
22#[macro_use]
23mod macros;
24
25#[macro_use]
26mod userid;
27pub use userid::{Realm, RealmRef};
e0538349 28pub use userid::{Tokenname, TokennameRef};
e7cb4dc5
WB
29pub use userid::{Username, UsernameRef};
30pub use userid::Userid;
e0538349
FG
31pub use userid::Authid;
32pub use userid::{PROXMOX_TOKEN_ID_SCHEMA, PROXMOX_TOKEN_NAME_SCHEMA, PROXMOX_GROUP_ID_SCHEMA};
e7cb4dc5 33
a0765714
DM
34mod tape;
35pub use tape::*;
36
255f378a
DM
37// File names: may not contain slashes, may not start with "."
38pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| {
39 if name.starts_with('.') {
40 bail!("file names may not start with '.'");
41 }
42 if name.contains('/') {
43 bail!("file names may not contain slashes");
44 }
45 Ok(())
46});
47
b25f313d 48macro_rules! DNS_LABEL { () => (r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") }
4c95d58c 49macro_rules! DNS_NAME { () => (concat!(r"(?:(?:", DNS_LABEL!() , r"\.)*", DNS_LABEL!(), ")")) }
255f378a 50
76cf5208
DM
51macro_rules! CIDR_V4_REGEX_STR { () => (concat!(r"(?:", IPV4RE!(), r"/\d{1,2})$")) }
52macro_rules! CIDR_V6_REGEX_STR { () => (concat!(r"(?:", IPV6RE!(), r"/\d{1,3})$")) }
53
255f378a 54const_regex!{
76cf5208
DM
55 pub IP_V4_REGEX = concat!(r"^", IPV4RE!(), r"$");
56 pub IP_V6_REGEX = concat!(r"^", IPV6RE!(), r"$");
57 pub IP_REGEX = concat!(r"^", IPRE!(), r"$");
58 pub CIDR_V4_REGEX = concat!(r"^", CIDR_V4_REGEX_STR!(), r"$");
59 pub CIDR_V6_REGEX = concat!(r"^", CIDR_V6_REGEX_STR!(), r"$");
60 pub CIDR_REGEX = concat!(r"^(?:", CIDR_V4_REGEX_STR!(), "|", CIDR_V6_REGEX_STR!(), r")$");
61
255f378a
DM
62 pub SHA256_HEX_REGEX = r"^[a-f0-9]{64}$"; // fixme: define in common_regex ?
63 pub SYSTEMD_DATETIME_REGEX = r"^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$"; // fixme: define in common_regex ?
d0adf270 64
da4a15a3
DM
65 pub PASSWORD_REGEX = r"^[[:^cntrl:]]*$"; // everything but control characters
66
d0adf270
DM
67 /// Regex for safe identifiers.
68 ///
69 /// This
70 /// [article](https://dwheeler.com/essays/fixing-unix-linux-filenames.html)
71 /// contains further information why it is reasonable to restict
72 /// names this way. This is not only useful for filenames, but for
73 /// any identifier command line tools work with.
163dc16c 74 pub PROXMOX_SAFE_ID_REGEX = concat!(r"^", PROXMOX_SAFE_ID_REGEX_STR!(), r"$");
454c13ed 75
dbd45a72
FG
76 /// Regex for verification jobs 'DATASTORE:ACTUAL_JOB_ID'
77 pub VERIFICATION_JOB_WORKER_ID_REGEX = concat!(r"^(", PROXMOX_SAFE_ID_REGEX_STR!(), r"):");
78 /// Regex for sync jobs 'REMOTE:REMOTE_DATASTORE:LOCAL_DATASTORE:ACTUAL_JOB_ID'
79 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"):");
80
454c13ed 81 pub SINGLE_LINE_COMMENT_REGEX = r"^[[:^cntrl:]]*$";
b25f313d
DM
82
83 pub HOSTNAME_REGEX = r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)$";
84
ae62c4fe 85 pub DNS_NAME_REGEX = concat!(r"^", DNS_NAME!(), r"$");
b25f313d 86
4c95d58c 87 pub DNS_NAME_OR_IP_REGEX = concat!(r"^(?:", DNS_NAME!(), "|", IPRE!(), r")$");
163dc16c 88
34aa8e13 89 pub BACKUP_REPO_URL_REGEX = concat!(r"^^(?:(?:(", USER_ID_REGEX_STR!(), "|", APITOKEN_ID_REGEX_STR!(), ")@)?(", DNS_NAME!(), "|", IPRE_BRACKET!() ,"):)?(?:([0-9]{1,5}):)?(", PROXMOX_SAFE_ID_REGEX_STR!(), r")$");
090decbe 90
d5a48b5c 91 pub FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$";
ed3e60ae 92
9765092e 93 pub ACL_PATH_REGEX = concat!(r"^(?:/|", r"(?:/", PROXMOX_SAFE_ID_REGEX_STR!(), ")+", r")$");
9069debc 94
926d2531
TL
95 pub SUBSCRIPTION_KEY_REGEX = concat!(r"^pbs(?:[cbsp])-[0-9a-f]{10}$");
96
9069debc 97 pub BLOCKDEVICE_NAME_REGEX = r"^(:?(:?h|s|x?v)d[a-z]+)|(:?nvme\d+n\d+)$";
7957fabf
DC
98
99 pub ZPOOL_NAME_REGEX = r"^[a-zA-Z][a-z0-9A-Z\-_.:]+$";
bc228e5e
DM
100
101 pub UUID_REGEX = r"^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$";
255f378a 102}
4ebf0eab 103
255f378a
DM
104pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat =
105 ApiStringFormat::Pattern(&SYSTEMD_DATETIME_REGEX);
4ebf0eab 106
76cf5208
DM
107pub const IP_V4_FORMAT: ApiStringFormat =
108 ApiStringFormat::Pattern(&IP_V4_REGEX);
109
110pub const IP_V6_FORMAT: ApiStringFormat =
111 ApiStringFormat::Pattern(&IP_V6_REGEX);
112
255f378a 113pub const IP_FORMAT: ApiStringFormat =
76cf5208 114 ApiStringFormat::Pattern(&IP_REGEX);
bbf9e7e9 115
255f378a
DM
116pub const PVE_CONFIG_DIGEST_FORMAT: ApiStringFormat =
117 ApiStringFormat::Pattern(&SHA256_HEX_REGEX);
118
d5a48b5c
DM
119pub const FINGERPRINT_SHA256_FORMAT: ApiStringFormat =
120 ApiStringFormat::Pattern(&FINGERPRINT_SHA256_REGEX);
dcb8db66 121
d0adf270
DM
122pub const PROXMOX_SAFE_ID_FORMAT: ApiStringFormat =
123 ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
124
038ee599
DM
125pub const BACKUP_ID_FORMAT: ApiStringFormat =
126 ApiStringFormat::Pattern(&BACKUP_ID_REGEX);
127
bc228e5e
DM
128pub const UUID_FORMAT: ApiStringFormat =
129 ApiStringFormat::Pattern(&UUID_REGEX);
130
454c13ed
DM
131pub const SINGLE_LINE_COMMENT_FORMAT: ApiStringFormat =
132 ApiStringFormat::Pattern(&SINGLE_LINE_COMMENT_REGEX);
133
b25f313d
DM
134pub const HOSTNAME_FORMAT: ApiStringFormat =
135 ApiStringFormat::Pattern(&HOSTNAME_REGEX);
136
137pub const DNS_NAME_FORMAT: ApiStringFormat =
138 ApiStringFormat::Pattern(&DNS_NAME_REGEX);
139
140pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat =
141 ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX);
142
7e7b781a
DM
143pub const PASSWORD_FORMAT: ApiStringFormat =
144 ApiStringFormat::Pattern(&PASSWORD_REGEX);
145
ed3e60ae
DM
146pub const ACL_PATH_FORMAT: ApiStringFormat =
147 ApiStringFormat::Pattern(&ACL_PATH_REGEX);
148
68da20bf
DM
149pub const NETWORK_INTERFACE_FORMAT: ApiStringFormat =
150 ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
454c13ed 151
76cf5208
DM
152pub const CIDR_V4_FORMAT: ApiStringFormat =
153 ApiStringFormat::Pattern(&CIDR_V4_REGEX);
154
155pub const CIDR_V6_FORMAT: ApiStringFormat =
156 ApiStringFormat::Pattern(&CIDR_V6_REGEX);
157
158pub const CIDR_FORMAT: ApiStringFormat =
159 ApiStringFormat::Pattern(&CIDR_REGEX);
160
926d2531
TL
161pub const SUBSCRIPTION_KEY_FORMAT: ApiStringFormat =
162 ApiStringFormat::Pattern(&SUBSCRIPTION_KEY_REGEX);
163
9069debc
DM
164pub const BLOCKDEVICE_NAME_FORMAT: ApiStringFormat =
165 ApiStringFormat::Pattern(&BLOCKDEVICE_NAME_REGEX);
76cf5208 166
685e1334
DM
167pub const PASSWORD_SCHEMA: Schema = StringSchema::new("Password.")
168 .format(&PASSWORD_FORMAT)
169 .min_length(1)
b88f9c5b 170 .max_length(1024)
685e1334
DM
171 .schema();
172
173pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.")
174 .format(&PASSWORD_FORMAT)
175 .min_length(5)
176 .max_length(64)
177 .schema();
dcb8db66
DM
178
179pub const CERT_FINGERPRINT_SHA256_SCHEMA: Schema = StringSchema::new(
180 "X509 certificate fingerprint (sha256)."
181)
d5a48b5c 182 .format(&FINGERPRINT_SHA256_FORMAT)
dcb8db66
DM
183 .schema();
184
d5a48b5c
DM
185pub const TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA: Schema = StringSchema::new(
186 "Tape encryption key fingerprint (sha256)."
255f378a 187)
d5a48b5c 188 .format(&FINGERPRINT_SHA256_FORMAT)
255f378a
DM
189 .schema();
190
d5a48b5c
DM
191pub const PROXMOX_CONFIG_DIGEST_SCHEMA: Schema = StringSchema::new(
192 "Prevent changes if current configuration file has different \
193 SHA256 digest. This can be used to prevent concurrent \
194 modifications."
195)
196 .format(&PVE_CONFIG_DIGEST_FORMAT) .schema();
197
255f378a
DM
198
199pub const CHUNK_DIGEST_FORMAT: ApiStringFormat =
200 ApiStringFormat::Pattern(&SHA256_HEX_REGEX);
201
202pub const CHUNK_DIGEST_SCHEMA: Schema = StringSchema::new("Chunk digest (SHA256).")
203 .format(&CHUNK_DIGEST_FORMAT)
204 .schema();
205
206pub const NODE_SCHEMA: Schema = StringSchema::new("Node name (or 'localhost')")
207 .format(&ApiStringFormat::VerifyFn(|node| {
208 if node == "localhost" || node == proxmox::tools::nodename() {
209 Ok(())
210 } else {
211 bail!("no such node '{}'", node);
212 }
213 }))
214 .schema();
215
216pub const SEARCH_DOMAIN_SCHEMA: Schema =
217 StringSchema::new("Search domain for host-name lookup.").schema();
218
219pub const FIRST_DNS_SERVER_SCHEMA: Schema =
220 StringSchema::new("First name server IP address.")
221 .format(&IP_FORMAT)
222 .schema();
223
224pub const SECOND_DNS_SERVER_SCHEMA: Schema =
225 StringSchema::new("Second name server IP address.")
226 .format(&IP_FORMAT)
227 .schema();
228
229pub const THIRD_DNS_SERVER_SCHEMA: Schema =
230 StringSchema::new("Third name server IP address.")
231 .format(&IP_FORMAT)
232 .schema();
233
76cf5208
DM
234pub const IP_V4_SCHEMA: Schema =
235 StringSchema::new("IPv4 address.")
236 .format(&IP_V4_FORMAT)
237 .max_length(15)
238 .schema();
239
240pub const IP_V6_SCHEMA: Schema =
241 StringSchema::new("IPv6 address.")
242 .format(&IP_V6_FORMAT)
243 .max_length(39)
244 .schema();
245
246pub const IP_SCHEMA: Schema =
247 StringSchema::new("IP (IPv4 or IPv6) address.")
248 .format(&IP_FORMAT)
249 .max_length(39)
250 .schema();
251
252pub const CIDR_V4_SCHEMA: Schema =
253 StringSchema::new("IPv4 address with netmask (CIDR notation).")
254 .format(&CIDR_V4_FORMAT)
255 .max_length(18)
256 .schema();
257
258pub const CIDR_V6_SCHEMA: Schema =
259 StringSchema::new("IPv6 address with netmask (CIDR notation).")
260 .format(&CIDR_V6_FORMAT)
261 .max_length(43)
262 .schema();
263
264pub const CIDR_SCHEMA: Schema =
265 StringSchema::new("IP address (IPv4 or IPv6) with netmask (CIDR notation).")
266 .format(&CIDR_FORMAT)
267 .max_length(43)
268 .schema();
269
4b40148c
DM
270pub const TIME_ZONE_SCHEMA: Schema = StringSchema::new(
271 "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.")
272 .format(&SINGLE_LINE_COMMENT_FORMAT)
273 .min_length(2)
274 .max_length(64)
275 .schema();
276
ca257c80
DM
277pub const ACL_PATH_SCHEMA: Schema = StringSchema::new(
278 "Access control path.")
279 .format(&ACL_PATH_FORMAT)
280 .min_length(1)
281 .max_length(128)
282 .schema();
283
284pub const ACL_PROPAGATE_SCHEMA: Schema = BooleanSchema::new(
285 "Allow to propagate (inherit) permissions.")
286 .default(true)
287 .schema();
288
289pub const ACL_UGID_TYPE_SCHEMA: Schema = StringSchema::new(
290 "Type of 'ugid' property.")
ca257c80 291 .format(&ApiStringFormat::Enum(&[
bc0d0388
DM
292 EnumEntry::new("user", "User"),
293 EnumEntry::new("group", "Group")]))
ca257c80
DM
294 .schema();
295
bf78f708
DM
296#[api(
297 properties: {
298 propagate: {
299 schema: ACL_PROPAGATE_SCHEMA,
300 },
301 path: {
302 schema: ACL_PATH_SCHEMA,
303 },
304 ugid_type: {
305 schema: ACL_UGID_TYPE_SCHEMA,
306 },
307 ugid: {
308 type: String,
309 description: "User or Group ID.",
310 },
311 roleid: {
312 type: Role,
313 }
314 }
315)]
316#[derive(Serialize, Deserialize)]
317/// ACL list entry.
318pub struct AclListItem {
319 pub path: String,
320 pub ugid: String,
321 pub ugid_type: String,
322 pub propagate: bool,
323 pub roleid: String,
324}
325
255f378a
DM
326pub const BACKUP_ARCHIVE_NAME_SCHEMA: Schema =
327 StringSchema::new("Backup archive name.")
1ae5677d 328 .format(&PROXMOX_SAFE_ID_FORMAT)
255f378a
DM
329 .schema();
330
331pub const BACKUP_TYPE_SCHEMA: Schema =
332 StringSchema::new("Backup type.")
bc0d0388
DM
333 .format(&ApiStringFormat::Enum(&[
334 EnumEntry::new("vm", "Virtual Machine Backup"),
335 EnumEntry::new("ct", "Container Backup"),
336 EnumEntry::new("host", "Host Backup")]))
255f378a
DM
337 .schema();
338
339pub const BACKUP_ID_SCHEMA: Schema =
340 StringSchema::new("Backup ID.")
038ee599 341 .format(&BACKUP_ID_FORMAT)
255f378a
DM
342 .schema();
343
344pub const BACKUP_TIME_SCHEMA: Schema =
345 IntegerSchema::new("Backup time (Unix epoch.)")
346 .minimum(1_547_797_308)
347 .schema();
5830c205
DM
348
349pub const UPID_SCHEMA: Schema = StringSchema::new("Unique Process/Task ID.")
350 .max_length(256)
351 .schema();
66c49c21
DM
352
353pub const DATASTORE_SCHEMA: Schema = StringSchema::new("Datastore name.")
d0adf270 354 .format(&PROXMOX_SAFE_ID_FORMAT)
688fbe07 355 .min_length(3)
66c49c21
DM
356 .max_length(32)
357 .schema();
fc189b19 358
bc228e5e 359pub const MEDIA_SET_UUID_SCHEMA: Schema =
f490dda0 360 StringSchema::new("MediaSet Uuid (We use the all-zero Uuid to reseve an empty media for a specific pool).")
bc228e5e
DM
361 .format(&UUID_FORMAT)
362 .schema();
363
364pub const MEDIA_UUID_SCHEMA: Schema =
365 StringSchema::new("Media Uuid.")
366 .format(&UUID_FORMAT)
367 .schema();
368
2888b27f
DC
369pub const SYNC_SCHEDULE_SCHEMA: Schema = StringSchema::new(
370 "Run sync job at specified schedule.")
371 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
8f02db04 372 .type_text("<calendar-event>")
2888b27f
DC
373 .schema();
374
42fdbe51
DM
375pub const GC_SCHEDULE_SCHEMA: Schema = StringSchema::new(
376 "Run garbage collection job at specified schedule.")
377 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
8f02db04 378 .type_text("<calendar-event>")
42fdbe51
DM
379 .schema();
380
67f7ffd0
DM
381pub const PRUNE_SCHEDULE_SCHEMA: Schema = StringSchema::new(
382 "Run prune job at specified schedule.")
383 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
8f02db04 384 .type_text("<calendar-event>")
67f7ffd0
DM
385 .schema();
386
78efafc2 387pub const VERIFICATION_SCHEDULE_SCHEMA: Schema = StringSchema::new(
f37ef25b
HL
388 "Run verify job at specified schedule.")
389 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
8f02db04 390 .type_text("<calendar-event>")
f37ef25b
HL
391 .schema();
392
167971ed
DM
393pub const REMOTE_ID_SCHEMA: Schema = StringSchema::new("Remote ID.")
394 .format(&PROXMOX_SAFE_ID_FORMAT)
395 .min_length(3)
396 .max_length(32)
397 .schema();
398
b4900286
DM
399pub const JOB_ID_SCHEMA: Schema = StringSchema::new("Job ID.")
400 .format(&PROXMOX_SAFE_ID_FORMAT)
401 .min_length(3)
402 .max_length(32)
403 .schema();
404
405pub const REMOVE_VANISHED_BACKUPS_SCHEMA: Schema = BooleanSchema::new(
406 "Delete vanished backups. This remove the local copy if the remote backup was deleted.")
407 .default(true)
408 .schema();
409
9b2bad7a
HL
410pub const IGNORE_VERIFIED_BACKUPS_SCHEMA: Schema = BooleanSchema::new(
411 "Do not verify backups that are already verified if their verification is not outdated.")
412 .default(true)
413 .schema();
414
415pub const VERIFICATION_OUTDATED_AFTER_SCHEMA: Schema = IntegerSchema::new(
416 "Days after that a verification becomes outdated")
417 .minimum(1)
418 .schema();
419
454c13ed
DM
420pub const SINGLE_LINE_COMMENT_SCHEMA: Schema = StringSchema::new("Comment (single line).")
421 .format(&SINGLE_LINE_COMMENT_FORMAT)
422 .schema();
fc189b19 423
b25f313d
DM
424pub const HOSTNAME_SCHEMA: Schema = StringSchema::new("Hostname (as defined in RFC1123).")
425 .format(&HOSTNAME_FORMAT)
426 .schema();
427
428pub const DNS_NAME_OR_IP_SCHEMA: Schema = StringSchema::new("DNS name or IP address.")
429 .format(&DNS_NAME_OR_IP_FORMAT)
430 .schema();
431
926d2531
TL
432pub const SUBSCRIPTION_KEY_SCHEMA: Schema = StringSchema::new("Proxmox Backup Server subscription key.")
433 .format(&SUBSCRIPTION_KEY_FORMAT)
434 .min_length(15)
435 .max_length(16)
436 .schema();
437
9069debc
DM
438pub const BLOCKDEVICE_NAME_SCHEMA: Schema = StringSchema::new("Block device name (/sys/block/<name>).")
439 .format(&BLOCKDEVICE_NAME_FORMAT)
440 .min_length(3)
441 .max_length(64)
442 .schema();
fc189b19
DM
443
444// Complex type definitions
445
455e5f71
FG
446#[api(
447 properties: {
448 store: {
449 schema: DATASTORE_SCHEMA,
450 },
451 comment: {
452 optional: true,
453 schema: SINGLE_LINE_COMMENT_SCHEMA,
454 },
455 },
456)]
457#[derive(Serialize, Deserialize)]
458#[serde(rename_all="kebab-case")]
459/// Basic information about a datastore.
460pub struct DataStoreListItem {
461 pub store: String,
462 pub comment: Option<String>,
463}
464
b31c8019
DM
465#[api(
466 properties: {
467 "backup-type": {
468 schema: BACKUP_TYPE_SCHEMA,
469 },
470 "backup-id": {
471 schema: BACKUP_ID_SCHEMA,
472 },
473 "last-backup": {
474 schema: BACKUP_TIME_SCHEMA,
475 },
476 "backup-count": {
477 type: Integer,
478 },
479 files: {
480 items: {
481 schema: BACKUP_ARCHIVE_NAME_SCHEMA
482 },
483 },
e7cb4dc5 484 owner: {
e6dc35ac 485 type: Authid,
e7cb4dc5
WB
486 optional: true,
487 },
b31c8019
DM
488 },
489)]
490#[derive(Serialize, Deserialize)]
491#[serde(rename_all="kebab-case")]
492/// Basic information about a backup group.
493pub struct GroupListItem {
494 pub backup_type: String, // enum
495 pub backup_id: String,
496 pub last_backup: i64,
497 /// Number of contained snapshots
498 pub backup_count: u64,
499 /// List of contained archive files.
500 pub files: Vec<String>,
04b0ca8b
DC
501 /// The owner of group
502 #[serde(skip_serializing_if="Option::is_none")]
e6dc35ac 503 pub owner: Option<Authid>,
b31c8019
DM
504}
505
d10332a1
SR
506#[api()]
507#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
508#[serde(rename_all = "lowercase")]
509/// Result of a verify operation.
510pub enum VerifyState {
511 /// Verification was successful
512 Ok,
513 /// Verification reported one or more errors
514 Failed,
515}
516
3b2046d2
TL
517#[api(
518 properties: {
519 upid: {
520 schema: UPID_SCHEMA
521 },
522 state: {
d10332a1 523 type: VerifyState
3b2046d2
TL
524 },
525 },
526)]
527#[derive(Serialize, Deserialize)]
528/// Task properties.
529pub struct SnapshotVerifyState {
530 /// UPID of the verify task
531 pub upid: UPID,
d10332a1
SR
532 /// State of the verification. Enum.
533 pub state: VerifyState,
3b2046d2
TL
534}
535
fc189b19 536#[api(
fc189b19
DM
537 properties: {
538 "backup-type": {
539 schema: BACKUP_TYPE_SCHEMA,
540 },
541 "backup-id": {
542 schema: BACKUP_ID_SCHEMA,
543 },
544 "backup-time": {
545 schema: BACKUP_TIME_SCHEMA,
546 },
5255e641
TL
547 comment: {
548 schema: SINGLE_LINE_COMMENT_SCHEMA,
549 optional: true,
550 },
3b2046d2
TL
551 verification: {
552 type: SnapshotVerifyState,
553 optional: true,
554 },
035c40e6
FG
555 fingerprint: {
556 type: String,
557 optional: true,
558 },
71da3d6a
DM
559 files: {
560 items: {
561 schema: BACKUP_ARCHIVE_NAME_SCHEMA
562 },
563 },
e7cb4dc5 564 owner: {
e6dc35ac 565 type: Authid,
e7cb4dc5
WB
566 optional: true,
567 },
fc189b19
DM
568 },
569)]
570#[derive(Serialize, Deserialize)]
571#[serde(rename_all="kebab-case")]
71da3d6a 572/// Basic information about backup snapshot.
fc189b19
DM
573pub struct SnapshotListItem {
574 pub backup_type: String, // enum
575 pub backup_id: String,
576 pub backup_time: i64,
70030b43
DM
577 /// The first line from manifest "notes"
578 #[serde(skip_serializing_if="Option::is_none")]
579 pub comment: Option<String>,
3b2046d2
TL
580 /// The result of the last run verify task
581 #[serde(skip_serializing_if="Option::is_none")]
582 pub verification: Option<SnapshotVerifyState>,
035c40e6
FG
583 /// Fingerprint of encryption key
584 #[serde(skip_serializing_if="Option::is_none")]
585 pub fingerprint: Option<Fingerprint>,
71da3d6a 586 /// List of contained archive files.
1c090810 587 pub files: Vec<BackupContent>,
71da3d6a 588 /// Overall snapshot size (sum of all archive sizes).
fc189b19
DM
589 #[serde(skip_serializing_if="Option::is_none")]
590 pub size: Option<u64>,
04b0ca8b
DC
591 /// The owner of the snapshots group
592 #[serde(skip_serializing_if="Option::is_none")]
e6dc35ac 593 pub owner: Option<Authid>,
fc189b19 594}
ff620a3d 595
db1e061d
DM
596#[api(
597 properties: {
598 "backup-type": {
599 schema: BACKUP_TYPE_SCHEMA,
600 },
601 "backup-id": {
602 schema: BACKUP_ID_SCHEMA,
603 },
604 "backup-time": {
605 schema: BACKUP_TIME_SCHEMA,
606 },
607 },
608)]
609#[derive(Serialize, Deserialize)]
610#[serde(rename_all="kebab-case")]
611/// Prune result.
612pub struct PruneListItem {
613 pub backup_type: String, // enum
614 pub backup_id: String,
615 pub backup_time: i64,
616 /// Keep snapshot
617 pub keep: bool,
618}
619
49ff1092
DM
620pub const PRUNE_SCHEMA_KEEP_DAILY: Schema = IntegerSchema::new(
621 "Number of daily backups to keep.")
622 .minimum(1)
623 .schema();
624
625pub const PRUNE_SCHEMA_KEEP_HOURLY: Schema = IntegerSchema::new(
626 "Number of hourly backups to keep.")
627 .minimum(1)
628 .schema();
629
630pub const PRUNE_SCHEMA_KEEP_LAST: Schema = IntegerSchema::new(
631 "Number of backups to keep.")
632 .minimum(1)
633 .schema();
634
635pub const PRUNE_SCHEMA_KEEP_MONTHLY: Schema = IntegerSchema::new(
636 "Number of monthly backups to keep.")
637 .minimum(1)
638 .schema();
639
640pub const PRUNE_SCHEMA_KEEP_WEEKLY: Schema = IntegerSchema::new(
641 "Number of weekly backups to keep.")
642 .minimum(1)
643 .schema();
644
645pub const PRUNE_SCHEMA_KEEP_YEARLY: Schema = IntegerSchema::new(
646 "Number of yearly backups to keep.")
647 .minimum(1)
648 .schema();
649
09b1f7b2
DM
650#[api(
651 properties: {
652 "filename": {
653 schema: BACKUP_ARCHIVE_NAME_SCHEMA,
654 },
f28d9088
WB
655 "crypt-mode": {
656 type: CryptMode,
657 optional: true,
658 },
09b1f7b2
DM
659 },
660)]
661#[derive(Serialize, Deserialize)]
662#[serde(rename_all="kebab-case")]
663/// Basic information about archive files inside a backup snapshot.
664pub struct BackupContent {
665 pub filename: String,
f28d9088 666 /// Info if file is encrypted, signed, or neither.
e181d2f6 667 #[serde(skip_serializing_if="Option::is_none")]
f28d9088 668 pub crypt_mode: Option<CryptMode>,
09b1f7b2
DM
669 /// Archive size (from backup manifest).
670 #[serde(skip_serializing_if="Option::is_none")]
671 pub size: Option<u64>,
672}
673
a92830dc
DM
674#[api(
675 properties: {
676 "upid": {
677 optional: true,
678 schema: UPID_SCHEMA,
679 },
680 },
681)]
682#[derive(Clone, Serialize, Deserialize)]
683#[serde(rename_all="kebab-case")]
684/// Garbage collection status.
685pub struct GarbageCollectionStatus {
686 pub upid: Option<String>,
687 /// Number of processed index files.
688 pub index_file_count: usize,
689 /// Sum of bytes referred by index files.
690 pub index_data_bytes: u64,
691 /// Bytes used on disk.
692 pub disk_bytes: u64,
693 /// Chunks used on disk.
694 pub disk_chunks: usize,
695 /// Sum of removed bytes.
696 pub removed_bytes: u64,
697 /// Number of removed chunks.
698 pub removed_chunks: usize,
cf459b19
DM
699 /// Sum of pending bytes (pending removal - kept for safety).
700 pub pending_bytes: u64,
701 /// Number of pending chunks (pending removal - kept for safety).
702 pub pending_chunks: usize,
a9767cf7
SR
703 /// Number of chunks marked as .bad by verify that have been removed by GC.
704 pub removed_bad: usize,
a2285525
DC
705 /// Number of chunks still marked as .bad after garbage collection.
706 pub still_bad: usize,
a92830dc
DM
707}
708
709impl Default for GarbageCollectionStatus {
710 fn default() -> Self {
711 GarbageCollectionStatus {
712 upid: None,
713 index_file_count: 0,
714 index_data_bytes: 0,
715 disk_bytes: 0,
716 disk_chunks: 0,
717 removed_bytes: 0,
718 removed_chunks: 0,
cf459b19
DM
719 pending_bytes: 0,
720 pending_chunks: 0,
a9767cf7 721 removed_bad: 0,
a2285525 722 still_bad: 0,
a92830dc
DM
723 }
724 }
725}
726
727
1dc117bb
DM
728#[api()]
729#[derive(Serialize, Deserialize)]
730/// Storage space usage information.
731pub struct StorageStatus {
732 /// Total space (bytes).
733 pub total: u64,
734 /// Used space (bytes).
735 pub used: u64,
736 /// Available space (bytes).
737 pub avail: u64,
738}
ff620a3d 739
14e08625
DC
740#[api()]
741#[derive(Serialize, Deserialize, Default)]
742/// Backup Type group/snapshot counts.
743pub struct TypeCounts {
744 /// The number of groups of the type.
745 pub groups: u64,
746 /// The number of snapshots of the type.
747 pub snapshots: u64,
748}
749
750#[api(
751 properties: {
752 ct: {
753 type: TypeCounts,
754 optional: true,
755 },
756 host: {
757 type: TypeCounts,
758 optional: true,
759 },
760 vm: {
761 type: TypeCounts,
762 optional: true,
763 },
764 other: {
765 type: TypeCounts,
766 optional: true,
767 },
768 },
769)]
fdfcb74d 770#[derive(Serialize, Deserialize, Default)]
14e08625
DC
771/// Counts of groups/snapshots per BackupType.
772pub struct Counts {
773 /// The counts for CT backups
774 pub ct: Option<TypeCounts>,
775 /// The counts for Host backups
776 pub host: Option<TypeCounts>,
777 /// The counts for VM backups
778 pub vm: Option<TypeCounts>,
779 /// The counts for other backup types
780 pub other: Option<TypeCounts>,
781}
782
783#[api(
784 properties: {
98afc7b1
FG
785 "gc-status": {
786 type: GarbageCollectionStatus,
787 optional: true,
788 },
789 counts: {
790 type: Counts,
791 optional: true,
792 },
14e08625
DC
793 },
794)]
795#[derive(Serialize, Deserialize)]
796#[serde(rename_all="kebab-case")]
797/// Overall Datastore status and useful information.
798pub struct DataStoreStatus {
799 /// Total space (bytes).
800 pub total: u64,
801 /// Used space (bytes).
802 pub used: u64,
803 /// Available space (bytes).
804 pub avail: u64,
805 /// Status of last GC
98afc7b1
FG
806 #[serde(skip_serializing_if="Option::is_none")]
807 pub gc_status: Option<GarbageCollectionStatus>,
14e08625 808 /// Group/Snapshot counts
98afc7b1
FG
809 #[serde(skip_serializing_if="Option::is_none")]
810 pub counts: Option<Counts>,
14e08625
DC
811}
812
99384f79
DM
813#[api(
814 properties: {
e7cb4dc5 815 upid: { schema: UPID_SCHEMA },
16245d54 816 user: { type: Authid },
99384f79
DM
817 },
818)]
819#[derive(Serialize, Deserialize)]
820/// Task properties.
821pub struct TaskListItem {
822 pub upid: String,
823 /// The node name where the task is running on.
824 pub node: String,
825 /// The Unix PID
826 pub pid: i64,
827 /// The task start time (Epoch)
828 pub pstart: u64,
829 /// The task start time (Epoch)
830 pub starttime: i64,
831 /// Worker type (arbitrary ASCII string)
832 pub worker_type: String,
833 /// Worker ID (arbitrary ASCII string)
834 pub worker_id: Option<String>,
e6dc35ac 835 /// The authenticated entity who started the task
16245d54 836 pub user: Authid,
99384f79
DM
837 /// The task end time (Epoch)
838 #[serde(skip_serializing_if="Option::is_none")]
839 pub endtime: Option<i64>,
840 /// Task end status
841 #[serde(skip_serializing_if="Option::is_none")]
842 pub status: Option<String>,
843}
844
df528ee6
DC
845impl From<crate::server::TaskListInfo> for TaskListItem {
846 fn from(info: crate::server::TaskListInfo) -> Self {
847 let (endtime, status) = info
848 .state
77bd2a46 849 .map_or_else(|| (None, None), |a| (Some(a.endtime()), Some(a.to_string())));
df528ee6
DC
850
851 TaskListItem {
852 upid: info.upid_str,
853 node: "localhost".to_string(),
854 pid: info.upid.pid as i64,
855 pstart: info.upid.pstart,
856 starttime: info.upid.starttime,
857 worker_type: info.upid.worker_type,
858 worker_id: info.upid.worker_id,
16245d54 859 user: info.upid.auth_id,
df528ee6
DC
860 endtime,
861 status,
862 }
863 }
864}
865
5976c392
DC
866#[api()]
867#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
868#[serde(rename_all = "lowercase")]
869pub enum TaskStateType {
870 /// Ok
871 OK,
872 /// Warning
873 Warning,
874 /// Error
875 Error,
876 /// Unknown
877 Unknown,
878}
879
ed751dc2
DM
880#[api()]
881#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
882#[serde(rename_all = "lowercase")]
883/// Node Power command type.
884pub enum NodePowerCommand {
885 /// Restart the server
886 Reboot,
887 /// Shutdown the server
888 Shutdown,
889}
890
c357260d
DM
891#[api()]
892#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
893#[serde(rename_all = "lowercase")]
894/// Interface configuration method
895pub enum NetworkConfigMethod {
896 /// Configuration is done manually using other tools
897 Manual,
898 /// Define interfaces with statically allocated addresses.
899 Static,
900 /// Obtain an address via DHCP
901 DHCP,
902 /// Define the loopback interface.
903 Loopback,
904}
905
bab5d18c
DM
906#[api()]
907#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
908#[serde(rename_all = "kebab-case")]
909#[allow(non_camel_case_types)]
910#[repr(u8)]
911/// Linux Bond Mode
912pub enum LinuxBondMode {
913 /// Round-robin policy
914 balance_rr = 0,
915 /// Active-backup policy
916 active_backup = 1,
917 /// XOR policy
918 balance_xor = 2,
919 /// Broadcast policy
920 broadcast = 3,
921 /// IEEE 802.3ad Dynamic link aggregation
8f2f3dd7 922 #[serde(rename = "802.3ad")]
bab5d18c
DM
923 ieee802_3ad = 4,
924 /// Adaptive transmit load balancing
925 balance_tlb = 5,
926 /// Adaptive load balancing
927 balance_alb = 6,
928}
929
8f2f3dd7
DC
930#[api()]
931#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
932#[serde(rename_all = "kebab-case")]
933#[allow(non_camel_case_types)]
934#[repr(u8)]
935/// Bond Transmit Hash Policy for LACP (802.3ad)
936pub enum BondXmitHashPolicy {
937 /// Layer 2
938 layer2 = 0,
939 /// Layer 2+3
940 #[serde(rename = "layer2+3")]
941 layer2_3 = 1,
942 /// Layer 3+4
943 #[serde(rename = "layer3+4")]
944 layer3_4 = 2,
945}
946
02269f3d
DM
947#[api()]
948#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
949#[serde(rename_all = "lowercase")]
950/// Network interface type
951pub enum NetworkInterfaceType {
952 /// Loopback
953 Loopback,
954 /// Physical Ethernet device
7b22acd0 955 Eth,
02269f3d
DM
956 /// Linux Bridge
957 Bridge,
958 /// Linux Bond
959 Bond,
960 /// Linux VLAN (eth.10)
961 Vlan,
962 /// Interface Alias (eth:1)
963 Alias,
964 /// Unknown interface type
965 Unknown,
966}
967
68da20bf
DM
968pub const NETWORK_INTERFACE_NAME_SCHEMA: Schema = StringSchema::new("Network interface name.")
969 .format(&NETWORK_INTERFACE_FORMAT)
970 .min_length(1)
971 .max_length(libc::IFNAMSIZ-1)
972 .schema();
973
3aedb738 974pub const NETWORK_INTERFACE_ARRAY_SCHEMA: Schema = ArraySchema::new(
1d9a68c2
DM
975 "Network interface list.", &NETWORK_INTERFACE_NAME_SCHEMA)
976 .schema();
977
3aedb738
DM
978pub const NETWORK_INTERFACE_LIST_SCHEMA: Schema = StringSchema::new(
979 "A list of network devices, comma separated.")
980 .format(&ApiStringFormat::PropertyString(&NETWORK_INTERFACE_ARRAY_SCHEMA))
981 .schema();
982
c357260d
DM
983#[api(
984 properties: {
985 name: {
68da20bf 986 schema: NETWORK_INTERFACE_NAME_SCHEMA,
c357260d 987 },
7b22acd0 988 "type": {
02269f3d
DM
989 type: NetworkInterfaceType,
990 },
7b22acd0 991 method: {
c357260d
DM
992 type: NetworkConfigMethod,
993 optional: true,
994 },
7b22acd0 995 method6: {
c357260d
DM
996 type: NetworkConfigMethod,
997 optional: true,
998 },
7b22acd0
DM
999 cidr: {
1000 schema: CIDR_V4_SCHEMA,
1001 optional: true,
1002 },
1003 cidr6: {
1004 schema: CIDR_V6_SCHEMA,
1005 optional: true,
1006 },
1007 gateway: {
1008 schema: IP_V4_SCHEMA,
1009 optional: true,
1010 },
1011 gateway6: {
1012 schema: IP_V6_SCHEMA,
1013 optional: true,
1014 },
1015 options: {
c357260d
DM
1016 description: "Option list (inet)",
1017 type: Array,
1018 items: {
68da20bf 1019 description: "Optional attribute line.",
c357260d
DM
1020 type: String,
1021 },
1022 },
7b22acd0 1023 options6: {
c357260d
DM
1024 description: "Option list (inet6)",
1025 type: Array,
1026 items: {
68da20bf 1027 description: "Optional attribute line.",
c357260d
DM
1028 type: String,
1029 },
1030 },
7b22acd0 1031 comments: {
8a6b86b8
DM
1032 description: "Comments (inet, may span multiple lines)",
1033 type: String,
1034 optional: true,
5f60a58f 1035 },
7b22acd0 1036 comments6: {
8a6b86b8
DM
1037 description: "Comments (inet6, may span multiple lines)",
1038 type: String,
1039 optional: true,
5f60a58f 1040 },
1d9a68c2 1041 bridge_ports: {
3aedb738 1042 schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
1d9a68c2
DM
1043 optional: true,
1044 },
bab5d18c 1045 slaves: {
3aedb738 1046 schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
42fbe91a
DM
1047 optional: true,
1048 },
bab5d18c
DM
1049 bond_mode: {
1050 type: LinuxBondMode,
1051 optional: true,
85959a99
DC
1052 },
1053 "bond-primary": {
1054 schema: NETWORK_INTERFACE_NAME_SCHEMA,
1055 optional: true,
1056 },
8f2f3dd7
DC
1057 bond_xmit_hash_policy: {
1058 type: BondXmitHashPolicy,
1059 optional: true,
1060 },
c357260d
DM
1061 }
1062)]
1063#[derive(Debug, Serialize, Deserialize)]
1064/// Network Interface configuration
1065pub struct Interface {
1066 /// Autostart interface
7b22acd0
DM
1067 #[serde(rename = "autostart")]
1068 pub autostart: bool,
c357260d
DM
1069 /// Interface is active (UP)
1070 pub active: bool,
1071 /// Interface name
1072 pub name: String,
02269f3d 1073 /// Interface type
7b22acd0 1074 #[serde(rename = "type")]
02269f3d 1075 pub interface_type: NetworkInterfaceType,
c357260d 1076 #[serde(skip_serializing_if="Option::is_none")]
7b22acd0 1077 pub method: Option<NetworkConfigMethod>,
c357260d 1078 #[serde(skip_serializing_if="Option::is_none")]
7b22acd0 1079 pub method6: Option<NetworkConfigMethod>,
c357260d 1080 #[serde(skip_serializing_if="Option::is_none")]
8b57cd44 1081 /// IPv4 address with netmask
7b22acd0 1082 pub cidr: Option<String>,
c357260d
DM
1083 #[serde(skip_serializing_if="Option::is_none")]
1084 /// IPv4 gateway
7b22acd0 1085 pub gateway: Option<String>,
c357260d 1086 #[serde(skip_serializing_if="Option::is_none")]
8b57cd44 1087 /// IPv6 address with netmask
7b22acd0 1088 pub cidr6: Option<String>,
c357260d
DM
1089 #[serde(skip_serializing_if="Option::is_none")]
1090 /// IPv6 gateway
7b22acd0 1091 pub gateway6: Option<String>,
3fce3bc3 1092
c357260d 1093 #[serde(skip_serializing_if="Vec::is_empty")]
7b22acd0 1094 pub options: Vec<String>,
c357260d 1095 #[serde(skip_serializing_if="Vec::is_empty")]
7b22acd0 1096 pub options6: Vec<String>,
2c18efd9 1097
8a6b86b8 1098 #[serde(skip_serializing_if="Option::is_none")]
7b22acd0 1099 pub comments: Option<String>,
8a6b86b8 1100 #[serde(skip_serializing_if="Option::is_none")]
7b22acd0 1101 pub comments6: Option<String>,
5f60a58f 1102
2c18efd9
DM
1103 #[serde(skip_serializing_if="Option::is_none")]
1104 /// Maximum Transmission Unit
1105 pub mtu: Option<u64>,
1d9a68c2
DM
1106
1107 #[serde(skip_serializing_if="Option::is_none")]
1108 pub bridge_ports: Option<Vec<String>>,
7b22acd0
DM
1109 /// Enable bridge vlan support.
1110 #[serde(skip_serializing_if="Option::is_none")]
1111 pub bridge_vlan_aware: Option<bool>,
42fbe91a
DM
1112
1113 #[serde(skip_serializing_if="Option::is_none")]
bab5d18c
DM
1114 pub slaves: Option<Vec<String>>,
1115 #[serde(skip_serializing_if="Option::is_none")]
1116 pub bond_mode: Option<LinuxBondMode>,
85959a99
DC
1117 #[serde(skip_serializing_if="Option::is_none")]
1118 #[serde(rename = "bond-primary")]
1119 pub bond_primary: Option<String>,
8f2f3dd7 1120 pub bond_xmit_hash_policy: Option<BondXmitHashPolicy>,
c357260d
DM
1121}
1122
ff620a3d
DM
1123// Regression tests
1124
dcb8db66 1125#[test]
ff329f97 1126fn test_cert_fingerprint_schema() -> Result<(), anyhow::Error> {
dcb8db66
DM
1127
1128 let schema = CERT_FINGERPRINT_SHA256_SCHEMA;
1129
1130 let invalid_fingerprints = [
1131 "86:88:7c:be:26:77:a5:62:67:d9:06:f5:e4::61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
1132 "88:7C:BE:26:77:a5:62:67:D9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
1133 "86:88:7c:be:26:77:a5:62:67:d9:06:f5:e4::14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8:ff",
1134 "XX:88:7c:be:26:77:a5:62:67:d9:06:f5:e4::14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
1135 "86:88:Y4:be:26:77:a5:62:67:d9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
1136 "86:88:0:be:26:77:a5:62:67:d9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
1137 ];
1138
1139 for fingerprint in invalid_fingerprints.iter() {
3984a5fd 1140 if parse_simple_value(fingerprint, &schema).is_ok() {
add5861e 1141 bail!("test fingerprint '{}' failed - got Ok() while exception an error.", fingerprint);
dcb8db66
DM
1142 }
1143 }
1144
1145 let valid_fingerprints = [
1146 "86:88:7c:be:26:77:a5:62:67:d9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
1147 "86:88:7C:BE:26:77:a5:62:67:D9:06:f5:e4:14:61:3e:20:dc:cd:43:92:07:7f:fb:65:54:6c:ff:d2:96:36:f8",
1148 ];
1149
1150 for fingerprint in valid_fingerprints.iter() {
1151 let v = match parse_simple_value(fingerprint, &schema) {
1152 Ok(v) => v,
1153 Err(err) => {
1154 bail!("unable to parse fingerprint '{}' - {}", fingerprint, err);
1155 }
1156 };
1157
1158 if v != serde_json::json!(fingerprint) {
1159 bail!("unable to parse fingerprint '{}' - got wrong value {:?}", fingerprint, v);
1160 }
1161 }
1162
1163 Ok(())
1164}
1165
ff620a3d 1166#[test]
ff329f97 1167fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
ff620a3d
DM
1168 let invalid_user_ids = [
1169 "x", // too short
1170 "xx", // too short
1171 "xxx", // no realm
1172 "xxx@", // no realm
1173 "xx x@test", // contains space
1174 "xx\nx@test", // contains control character
1175 "x:xx@test", // contains collon
1176 "xx/x@test", // contains slash
1177 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@test", // too long
1178 ];
1179
1180 for name in invalid_user_ids.iter() {
3984a5fd 1181 if parse_simple_value(name, &Userid::API_SCHEMA).is_ok() {
add5861e 1182 bail!("test userid '{}' failed - got Ok() while exception an error.", name);
ff620a3d
DM
1183 }
1184 }
1185
1186 let valid_user_ids = [
1187 "xxx@y",
1188 "name@y",
1189 "xxx@test-it.com",
1190 "xxx@_T_E_S_T-it.com",
1191 "x_x-x.x@test-it.com",
1192 ];
1193
1194 for name in valid_user_ids.iter() {
e7cb4dc5 1195 let v = match parse_simple_value(name, &Userid::API_SCHEMA) {
ff620a3d
DM
1196 Ok(v) => v,
1197 Err(err) => {
1198 bail!("unable to parse userid '{}' - {}", name, err);
1199 }
1200 };
1201
1202 if v != serde_json::json!(name) {
1203 bail!("unable to parse userid '{}' - got wrong value {:?}", name, v);
1204 }
1205 }
1206
1207 Ok(())
1208}
a2f862ee
DM
1209
1210#[api()]
1211#[derive(Copy, Clone, Serialize, Deserialize)]
1212#[serde(rename_all = "UPPERCASE")]
1213pub enum RRDMode {
1214 /// Maximum
1215 Max,
1216 /// Average
1217 Average,
1218}
1219
1220
1221#[api()]
1222#[repr(u64)]
1223#[derive(Copy, Clone, Serialize, Deserialize)]
1224#[serde(rename_all = "lowercase")]
1225pub enum RRDTimeFrameResolution {
1226 /// 1 min => last 70 minutes
1227 Hour = 60,
1228 /// 30 min => last 35 hours
1229 Day = 60*30,
1230 /// 3 hours => about 8 days
1231 Week = 60*180,
1232 /// 12 hours => last 35 days
1233 Month = 60*720,
1234 /// 1 week => last 490 days
1235 Year = 60*10080,
1236}
a4e86972
SR
1237
1238#[api()]
ed2beb33 1239#[derive(Debug, Clone, Serialize, Deserialize)]
a4e86972
SR
1240#[serde(rename_all = "PascalCase")]
1241/// Describes a package for which an update is available.
1242pub struct APTUpdateInfo {
1243 /// Package name
1244 pub package: String,
1245 /// Package title
1246 pub title: String,
1247 /// Package architecture
1248 pub arch: String,
1249 /// Human readable package description
1250 pub description: String,
1251 /// New version to be updated to
1252 pub version: String,
1253 /// Old version currently installed
1254 pub old_version: String,
1255 /// Package origin
1256 pub origin: String,
1257 /// Package priority in human-readable form
1258 pub priority: String,
1259 /// Package section
1260 pub section: String,
1261 /// URL under which the package's changelog can be retrieved
1262 pub change_log_url: String,
6f0073bb
TL
1263 /// Custom extra field for additional package information
1264 #[serde(skip_serializing_if="Option::is_none")]
2decf85d 1265 pub extra_info: Option<String>,
a4e86972 1266}
6e545d00
DM
1267
1268#[api()]
1269#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1270#[serde(rename_all = "lowercase")]
1271/// When do we send notifications
1272pub enum Notify {
1273 /// Never send notification
1274 Never,
1275 /// Send notifications for failed and sucessful jobs
1276 Always,
1277 /// Send notifications for failed jobs only
1278 Error,
1279}
c26c9390
DM
1280
1281#[api(
1282 properties: {
1283 gc: {
1284 type: Notify,
1285 optional: true,
1286 },
1287 verify: {
1288 type: Notify,
1289 optional: true,
1290 },
1291 sync: {
1292 type: Notify,
1293 optional: true,
1294 },
1295 },
1296)]
1297#[derive(Debug, Serialize, Deserialize)]
1298/// Datastore notify settings
1299pub struct DatastoreNotify {
1300 /// Garbage collection settings
1301 pub gc: Option<Notify>,
1302 /// Verify job setting
1303 pub verify: Option<Notify>,
1304 /// Sync job setting
1305 pub sync: Option<Notify>,
1306}
1307
227501c0
DC
1308/// An entry in a hierarchy of files for restore and listing.
1309#[api()]
1310#[derive(Serialize, Deserialize)]
1311pub struct ArchiveEntry {
1312 /// Base64-encoded full path to the file, including the filename
1313 pub filepath: String,
1314 /// Displayable filename text for UIs
1315 pub text: String,
1316 /// File or directory type of this entry
1317 #[serde(rename = "type")]
1318 pub entry_type: String,
1319 /// Is this entry a leaf node, or does it have children (i.e. a directory)?
1320 pub leaf: bool,
1321 /// The file size, if entry_type is 'f' (file)
1322 #[serde(skip_serializing_if="Option::is_none")]
1323 pub size: Option<u64>,
1324 /// The file "last modified" time stamp, if entry_type is 'f' (file)
1325 #[serde(skip_serializing_if="Option::is_none")]
1326 pub mtime: Option<i64>,
1327}
1328
1329impl ArchiveEntry {
1330 pub fn new(filepath: &[u8], entry_type: &DirEntryAttribute) -> Self {
1331 Self {
1332 filepath: base64::encode(filepath),
1333 text: String::from_utf8_lossy(filepath.split(|x| *x == b'/').last().unwrap())
1334 .to_string(),
1335 entry_type: CatalogEntryType::from(entry_type).to_string(),
1336 leaf: matches!(entry_type, DirEntryAttribute::Directory { .. }),
1337 size: match entry_type {
1338 DirEntryAttribute::File { size, .. } => Some(*size),
1339 _ => None
1340 },
1341 mtime: match entry_type {
1342 DirEntryAttribute::File { mtime, .. } => Some(*mtime),
1343 _ => None
1344 },
1345 }
1346 }
1347}
1348
c26c9390
DM
1349pub const DATASTORE_NOTIFY_STRING_SCHEMA: Schema = StringSchema::new(
1350 "Datastore notification setting")
1351 .format(&ApiStringFormat::PropertyString(&DatastoreNotify::API_SCHEMA))
1352 .schema();
82a103c8
DM
1353
1354
1355pub const PASSWORD_HINT_SCHEMA: Schema = StringSchema::new("Password hint.")
1356 .format(&SINGLE_LINE_COMMENT_FORMAT)
1357 .min_length(1)
1358 .max_length(64)
1359 .schema();
69b8bc3b
DM
1360
1361#[api(default: "scrypt")]
1362#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
1363#[serde(rename_all = "lowercase")]
1364/// Key derivation function for password protected encryption keys.
1365pub enum Kdf {
1366 /// Do not encrypt the key.
1367 None,
1368 /// Encrypt they key with a password using SCrypt.
1369 Scrypt,
1370 /// Encrtypt the Key with a password using PBKDF2
1371 PBKDF2,
1372}
1373
1374impl Default for Kdf {
1375 #[inline]
1376 fn default() -> Self {
1377 Kdf::Scrypt
1378 }
1379}
1380
1381#[api(
1382 properties: {
1383 kdf: {
1384 type: Kdf,
1385 },
1386 fingerprint: {
1387 schema: CERT_FINGERPRINT_SHA256_SCHEMA,
1388 optional: true,
1389 },
1390 },
1391)]
1392#[derive(Deserialize, Serialize)]
1393/// Encryption Key Information
1394pub struct KeyInfo {
1395 /// Path to key (if stored in a file)
1396 #[serde(skip_serializing_if="Option::is_none")]
1397 pub path: Option<String>,
1398 pub kdf: Kdf,
1399 /// Key creation time
1400 pub created: i64,
1401 /// Key modification time
1402 pub modified: i64,
1403 /// Key fingerprint
1404 #[serde(skip_serializing_if="Option::is_none")]
1405 pub fingerprint: Option<String>,
1406 /// Password hint
1407 #[serde(skip_serializing_if="Option::is_none")]
1408 pub hint: Option<String>,
1409}
42c0f784
FG
1410
1411#[api]
1412#[derive(Deserialize, Serialize)]
1413/// RSA public key information
1414pub struct RsaPubKeyInfo {
1415 /// Path to key (if stored in a file)
1416 #[serde(skip_serializing_if="Option::is_none")]
1417 pub path: Option<String>,
1418 /// RSA exponent
1419 pub exponent: String,
1420 /// Hex-encoded RSA modulus
1421 pub modulus: String,
1422 /// Key (modulus) length in bits
1423 pub length: usize,
1424}
1425
1426impl std::convert::TryFrom<openssl::rsa::Rsa<openssl::pkey::Public>> for RsaPubKeyInfo {
1427 type Error = anyhow::Error;
1428
1429 fn try_from(value: openssl::rsa::Rsa<openssl::pkey::Public>) -> Result<Self, Self::Error> {
1430 let modulus = value.n().to_hex_str()?.to_string();
1431 let exponent = value.e().to_dec_str()?.to_string();
1432 let length = value.size() as usize * 8;
1433
1434 Ok(Self {
1435 path: None,
1436 exponent,
1437 modulus,
1438 length,
1439 })
1440 }
1441}