]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/types/mod.rs
introduce Username, Realm and Userid api types
[proxmox-backup.git] / src / api2 / types / mod.rs
1 use anyhow::bail;
2 use serde::{Deserialize, Serialize};
3
4 use proxmox::api::{api, schema::*};
5 use proxmox::const_regex;
6 use proxmox::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32};
7
8 use crate::backup::CryptMode;
9
10 #[macro_use]
11 mod macros;
12
13 #[macro_use]
14 mod userid;
15 pub use userid::{Realm, RealmRef};
16 pub use userid::{Username, UsernameRef};
17 pub use userid::Userid;
18 pub use userid::PROXMOX_GROUP_ID_SCHEMA;
19
20 // File names: may not contain slashes, may not start with "."
21 pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| {
22 if name.starts_with('.') {
23 bail!("file names may not start with '.'");
24 }
25 if name.contains('/') {
26 bail!("file names may not contain slashes");
27 }
28 Ok(())
29 });
30
31 macro_rules! DNS_LABEL { () => (r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") }
32 macro_rules! DNS_NAME { () => (concat!(r"(?:", DNS_LABEL!() , r"\.)*", DNS_LABEL!())) }
33
34 macro_rules! CIDR_V4_REGEX_STR { () => (concat!(r"(?:", IPV4RE!(), r"/\d{1,2})$")) }
35 macro_rules! CIDR_V6_REGEX_STR { () => (concat!(r"(?:", IPV6RE!(), r"/\d{1,3})$")) }
36
37 const_regex!{
38 pub IP_V4_REGEX = concat!(r"^", IPV4RE!(), r"$");
39 pub IP_V6_REGEX = concat!(r"^", IPV6RE!(), r"$");
40 pub IP_REGEX = concat!(r"^", IPRE!(), r"$");
41 pub CIDR_V4_REGEX = concat!(r"^", CIDR_V4_REGEX_STR!(), r"$");
42 pub CIDR_V6_REGEX = concat!(r"^", CIDR_V6_REGEX_STR!(), r"$");
43 pub CIDR_REGEX = concat!(r"^(?:", CIDR_V4_REGEX_STR!(), "|", CIDR_V6_REGEX_STR!(), r")$");
44
45 pub SHA256_HEX_REGEX = r"^[a-f0-9]{64}$"; // fixme: define in common_regex ?
46 pub SYSTEMD_DATETIME_REGEX = r"^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$"; // fixme: define in common_regex ?
47
48 pub PASSWORD_REGEX = r"^[[:^cntrl:]]*$"; // everything but control characters
49
50 /// Regex for safe identifiers.
51 ///
52 /// This
53 /// [article](https://dwheeler.com/essays/fixing-unix-linux-filenames.html)
54 /// contains further information why it is reasonable to restict
55 /// names this way. This is not only useful for filenames, but for
56 /// any identifier command line tools work with.
57 pub PROXMOX_SAFE_ID_REGEX = concat!(r"^", PROXMOX_SAFE_ID_REGEX_STR!(), r"$");
58
59 pub SINGLE_LINE_COMMENT_REGEX = r"^[[:^cntrl:]]*$";
60
61 pub HOSTNAME_REGEX = r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)$";
62
63 pub DNS_NAME_REGEX = concat!(r"^", DNS_NAME!(), r"$");
64
65 pub DNS_NAME_OR_IP_REGEX = concat!(r"^", DNS_NAME!(), "|", IPRE!(), r"$");
66
67 pub BACKUP_REPO_URL_REGEX = concat!(r"^^(?:(?:(", USER_ID_REGEX_STR!(), ")@)?(", DNS_NAME!(), "|", IPRE!() ,"):)?(", PROXMOX_SAFE_ID_REGEX_STR!(), r")$");
68
69 pub CERT_FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$";
70
71 pub ACL_PATH_REGEX = concat!(r"^(?:/|", r"(?:/", PROXMOX_SAFE_ID_REGEX_STR!(), ")+", r")$");
72
73 pub BLOCKDEVICE_NAME_REGEX = r"^(:?(:?h|s|x?v)d[a-z]+)|(:?nvme\d+n\d+)$";
74
75 pub ZPOOL_NAME_REGEX = r"^[a-zA-Z][a-z0-9A-Z\-_.:]+$";
76 }
77
78 pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat =
79 ApiStringFormat::Pattern(&SYSTEMD_DATETIME_REGEX);
80
81 pub const IP_V4_FORMAT: ApiStringFormat =
82 ApiStringFormat::Pattern(&IP_V4_REGEX);
83
84 pub const IP_V6_FORMAT: ApiStringFormat =
85 ApiStringFormat::Pattern(&IP_V6_REGEX);
86
87 pub const IP_FORMAT: ApiStringFormat =
88 ApiStringFormat::Pattern(&IP_REGEX);
89
90 pub const PVE_CONFIG_DIGEST_FORMAT: ApiStringFormat =
91 ApiStringFormat::Pattern(&SHA256_HEX_REGEX);
92
93 pub const CERT_FINGERPRINT_SHA256_FORMAT: ApiStringFormat =
94 ApiStringFormat::Pattern(&CERT_FINGERPRINT_SHA256_REGEX);
95
96 pub const PROXMOX_SAFE_ID_FORMAT: ApiStringFormat =
97 ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
98
99 pub const SINGLE_LINE_COMMENT_FORMAT: ApiStringFormat =
100 ApiStringFormat::Pattern(&SINGLE_LINE_COMMENT_REGEX);
101
102 pub const HOSTNAME_FORMAT: ApiStringFormat =
103 ApiStringFormat::Pattern(&HOSTNAME_REGEX);
104
105 pub const DNS_NAME_FORMAT: ApiStringFormat =
106 ApiStringFormat::Pattern(&DNS_NAME_REGEX);
107
108 pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat =
109 ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX);
110
111 pub const PASSWORD_FORMAT: ApiStringFormat =
112 ApiStringFormat::Pattern(&PASSWORD_REGEX);
113
114 pub const ACL_PATH_FORMAT: ApiStringFormat =
115 ApiStringFormat::Pattern(&ACL_PATH_REGEX);
116
117 pub const NETWORK_INTERFACE_FORMAT: ApiStringFormat =
118 ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
119
120 pub const CIDR_V4_FORMAT: ApiStringFormat =
121 ApiStringFormat::Pattern(&CIDR_V4_REGEX);
122
123 pub const CIDR_V6_FORMAT: ApiStringFormat =
124 ApiStringFormat::Pattern(&CIDR_V6_REGEX);
125
126 pub const CIDR_FORMAT: ApiStringFormat =
127 ApiStringFormat::Pattern(&CIDR_REGEX);
128
129 pub const BLOCKDEVICE_NAME_FORMAT: ApiStringFormat =
130 ApiStringFormat::Pattern(&BLOCKDEVICE_NAME_REGEX);
131
132 pub const PASSWORD_SCHEMA: Schema = StringSchema::new("Password.")
133 .format(&PASSWORD_FORMAT)
134 .min_length(1)
135 .max_length(1024)
136 .schema();
137
138 pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.")
139 .format(&PASSWORD_FORMAT)
140 .min_length(5)
141 .max_length(64)
142 .schema();
143
144 pub const CERT_FINGERPRINT_SHA256_SCHEMA: Schema = StringSchema::new(
145 "X509 certificate fingerprint (sha256)."
146 )
147 .format(&CERT_FINGERPRINT_SHA256_FORMAT)
148 .schema();
149
150 pub const PROXMOX_CONFIG_DIGEST_SCHEMA: Schema = StringSchema::new(r#"\
151 Prevent changes if current configuration file has different SHA256 digest.
152 This can be used to prevent concurrent modifications.
153 "#
154 )
155 .format(&PVE_CONFIG_DIGEST_FORMAT)
156 .schema();
157
158
159 pub const CHUNK_DIGEST_FORMAT: ApiStringFormat =
160 ApiStringFormat::Pattern(&SHA256_HEX_REGEX);
161
162 pub const CHUNK_DIGEST_SCHEMA: Schema = StringSchema::new("Chunk digest (SHA256).")
163 .format(&CHUNK_DIGEST_FORMAT)
164 .schema();
165
166 pub const NODE_SCHEMA: Schema = StringSchema::new("Node name (or 'localhost')")
167 .format(&ApiStringFormat::VerifyFn(|node| {
168 if node == "localhost" || node == proxmox::tools::nodename() {
169 Ok(())
170 } else {
171 bail!("no such node '{}'", node);
172 }
173 }))
174 .schema();
175
176 pub const SEARCH_DOMAIN_SCHEMA: Schema =
177 StringSchema::new("Search domain for host-name lookup.").schema();
178
179 pub const FIRST_DNS_SERVER_SCHEMA: Schema =
180 StringSchema::new("First name server IP address.")
181 .format(&IP_FORMAT)
182 .schema();
183
184 pub const SECOND_DNS_SERVER_SCHEMA: Schema =
185 StringSchema::new("Second name server IP address.")
186 .format(&IP_FORMAT)
187 .schema();
188
189 pub const THIRD_DNS_SERVER_SCHEMA: Schema =
190 StringSchema::new("Third name server IP address.")
191 .format(&IP_FORMAT)
192 .schema();
193
194 pub const IP_V4_SCHEMA: Schema =
195 StringSchema::new("IPv4 address.")
196 .format(&IP_V4_FORMAT)
197 .max_length(15)
198 .schema();
199
200 pub const IP_V6_SCHEMA: Schema =
201 StringSchema::new("IPv6 address.")
202 .format(&IP_V6_FORMAT)
203 .max_length(39)
204 .schema();
205
206 pub const IP_SCHEMA: Schema =
207 StringSchema::new("IP (IPv4 or IPv6) address.")
208 .format(&IP_FORMAT)
209 .max_length(39)
210 .schema();
211
212 pub const CIDR_V4_SCHEMA: Schema =
213 StringSchema::new("IPv4 address with netmask (CIDR notation).")
214 .format(&CIDR_V4_FORMAT)
215 .max_length(18)
216 .schema();
217
218 pub const CIDR_V6_SCHEMA: Schema =
219 StringSchema::new("IPv6 address with netmask (CIDR notation).")
220 .format(&CIDR_V6_FORMAT)
221 .max_length(43)
222 .schema();
223
224 pub const CIDR_SCHEMA: Schema =
225 StringSchema::new("IP address (IPv4 or IPv6) with netmask (CIDR notation).")
226 .format(&CIDR_FORMAT)
227 .max_length(43)
228 .schema();
229
230 pub const TIME_ZONE_SCHEMA: Schema = StringSchema::new(
231 "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.")
232 .format(&SINGLE_LINE_COMMENT_FORMAT)
233 .min_length(2)
234 .max_length(64)
235 .schema();
236
237 pub const ACL_PATH_SCHEMA: Schema = StringSchema::new(
238 "Access control path.")
239 .format(&ACL_PATH_FORMAT)
240 .min_length(1)
241 .max_length(128)
242 .schema();
243
244 pub const ACL_PROPAGATE_SCHEMA: Schema = BooleanSchema::new(
245 "Allow to propagate (inherit) permissions.")
246 .default(true)
247 .schema();
248
249 pub const ACL_UGID_TYPE_SCHEMA: Schema = StringSchema::new(
250 "Type of 'ugid' property.")
251 .format(&ApiStringFormat::Enum(&[
252 EnumEntry::new("user", "User"),
253 EnumEntry::new("group", "Group")]))
254 .schema();
255
256 pub const BACKUP_ARCHIVE_NAME_SCHEMA: Schema =
257 StringSchema::new("Backup archive name.")
258 .format(&PROXMOX_SAFE_ID_FORMAT)
259 .schema();
260
261 pub const BACKUP_TYPE_SCHEMA: Schema =
262 StringSchema::new("Backup type.")
263 .format(&ApiStringFormat::Enum(&[
264 EnumEntry::new("vm", "Virtual Machine Backup"),
265 EnumEntry::new("ct", "Container Backup"),
266 EnumEntry::new("host", "Host Backup")]))
267 .schema();
268
269 pub const BACKUP_ID_SCHEMA: Schema =
270 StringSchema::new("Backup ID.")
271 .format(&PROXMOX_SAFE_ID_FORMAT)
272 .schema();
273
274 pub const BACKUP_TIME_SCHEMA: Schema =
275 IntegerSchema::new("Backup time (Unix epoch.)")
276 .minimum(1_547_797_308)
277 .schema();
278
279 pub const UPID_SCHEMA: Schema = StringSchema::new("Unique Process/Task ID.")
280 .max_length(256)
281 .schema();
282
283 pub const DATASTORE_SCHEMA: Schema = StringSchema::new("Datastore name.")
284 .format(&PROXMOX_SAFE_ID_FORMAT)
285 .min_length(3)
286 .max_length(32)
287 .schema();
288
289 pub const SYNC_SCHEDULE_SCHEMA: Schema = StringSchema::new(
290 "Run sync job at specified schedule.")
291 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
292 .schema();
293
294 pub const GC_SCHEDULE_SCHEMA: Schema = StringSchema::new(
295 "Run garbage collection job at specified schedule.")
296 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
297 .schema();
298
299 pub const PRUNE_SCHEDULE_SCHEMA: Schema = StringSchema::new(
300 "Run prune job at specified schedule.")
301 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
302 .schema();
303
304 pub const REMOTE_ID_SCHEMA: Schema = StringSchema::new("Remote ID.")
305 .format(&PROXMOX_SAFE_ID_FORMAT)
306 .min_length(3)
307 .max_length(32)
308 .schema();
309
310 pub const JOB_ID_SCHEMA: Schema = StringSchema::new("Job ID.")
311 .format(&PROXMOX_SAFE_ID_FORMAT)
312 .min_length(3)
313 .max_length(32)
314 .schema();
315
316 pub const REMOVE_VANISHED_BACKUPS_SCHEMA: Schema = BooleanSchema::new(
317 "Delete vanished backups. This remove the local copy if the remote backup was deleted.")
318 .default(true)
319 .schema();
320
321 pub const SINGLE_LINE_COMMENT_SCHEMA: Schema = StringSchema::new("Comment (single line).")
322 .format(&SINGLE_LINE_COMMENT_FORMAT)
323 .schema();
324
325 pub const HOSTNAME_SCHEMA: Schema = StringSchema::new("Hostname (as defined in RFC1123).")
326 .format(&HOSTNAME_FORMAT)
327 .schema();
328
329 pub const DNS_NAME_OR_IP_SCHEMA: Schema = StringSchema::new("DNS name or IP address.")
330 .format(&DNS_NAME_OR_IP_FORMAT)
331 .schema();
332
333 pub const BLOCKDEVICE_NAME_SCHEMA: Schema = StringSchema::new("Block device name (/sys/block/<name>).")
334 .format(&BLOCKDEVICE_NAME_FORMAT)
335 .min_length(3)
336 .max_length(64)
337 .schema();
338
339 // Complex type definitions
340
341 #[api(
342 properties: {
343 "backup-type": {
344 schema: BACKUP_TYPE_SCHEMA,
345 },
346 "backup-id": {
347 schema: BACKUP_ID_SCHEMA,
348 },
349 "last-backup": {
350 schema: BACKUP_TIME_SCHEMA,
351 },
352 "backup-count": {
353 type: Integer,
354 },
355 files: {
356 items: {
357 schema: BACKUP_ARCHIVE_NAME_SCHEMA
358 },
359 },
360 owner: {
361 type: Userid,
362 optional: true,
363 },
364 },
365 )]
366 #[derive(Serialize, Deserialize)]
367 #[serde(rename_all="kebab-case")]
368 /// Basic information about a backup group.
369 pub struct GroupListItem {
370 pub backup_type: String, // enum
371 pub backup_id: String,
372 pub last_backup: i64,
373 /// Number of contained snapshots
374 pub backup_count: u64,
375 /// List of contained archive files.
376 pub files: Vec<String>,
377 /// The owner of group
378 #[serde(skip_serializing_if="Option::is_none")]
379 pub owner: Option<Userid>,
380 }
381
382 #[api(
383 properties: {
384 "backup-type": {
385 schema: BACKUP_TYPE_SCHEMA,
386 },
387 "backup-id": {
388 schema: BACKUP_ID_SCHEMA,
389 },
390 "backup-time": {
391 schema: BACKUP_TIME_SCHEMA,
392 },
393 files: {
394 items: {
395 schema: BACKUP_ARCHIVE_NAME_SCHEMA
396 },
397 },
398 owner: {
399 type: Userid,
400 optional: true,
401 },
402 },
403 )]
404 #[derive(Serialize, Deserialize)]
405 #[serde(rename_all="kebab-case")]
406 /// Basic information about backup snapshot.
407 pub struct SnapshotListItem {
408 pub backup_type: String, // enum
409 pub backup_id: String,
410 pub backup_time: i64,
411 /// The first line from manifest "notes"
412 #[serde(skip_serializing_if="Option::is_none")]
413 pub comment: Option<String>,
414 /// List of contained archive files.
415 pub files: Vec<BackupContent>,
416 /// Overall snapshot size (sum of all archive sizes).
417 #[serde(skip_serializing_if="Option::is_none")]
418 pub size: Option<u64>,
419 /// The owner of the snapshots group
420 #[serde(skip_serializing_if="Option::is_none")]
421 pub owner: Option<Userid>,
422 }
423
424 #[api(
425 properties: {
426 "backup-type": {
427 schema: BACKUP_TYPE_SCHEMA,
428 },
429 "backup-id": {
430 schema: BACKUP_ID_SCHEMA,
431 },
432 "backup-time": {
433 schema: BACKUP_TIME_SCHEMA,
434 },
435 },
436 )]
437 #[derive(Serialize, Deserialize)]
438 #[serde(rename_all="kebab-case")]
439 /// Prune result.
440 pub struct PruneListItem {
441 pub backup_type: String, // enum
442 pub backup_id: String,
443 pub backup_time: i64,
444 /// Keep snapshot
445 pub keep: bool,
446 }
447
448 pub const PRUNE_SCHEMA_KEEP_DAILY: Schema = IntegerSchema::new(
449 "Number of daily backups to keep.")
450 .minimum(1)
451 .schema();
452
453 pub const PRUNE_SCHEMA_KEEP_HOURLY: Schema = IntegerSchema::new(
454 "Number of hourly backups to keep.")
455 .minimum(1)
456 .schema();
457
458 pub const PRUNE_SCHEMA_KEEP_LAST: Schema = IntegerSchema::new(
459 "Number of backups to keep.")
460 .minimum(1)
461 .schema();
462
463 pub const PRUNE_SCHEMA_KEEP_MONTHLY: Schema = IntegerSchema::new(
464 "Number of monthly backups to keep.")
465 .minimum(1)
466 .schema();
467
468 pub const PRUNE_SCHEMA_KEEP_WEEKLY: Schema = IntegerSchema::new(
469 "Number of weekly backups to keep.")
470 .minimum(1)
471 .schema();
472
473 pub const PRUNE_SCHEMA_KEEP_YEARLY: Schema = IntegerSchema::new(
474 "Number of yearly backups to keep.")
475 .minimum(1)
476 .schema();
477
478 #[api(
479 properties: {
480 "filename": {
481 schema: BACKUP_ARCHIVE_NAME_SCHEMA,
482 },
483 "crypt-mode": {
484 type: CryptMode,
485 optional: true,
486 },
487 },
488 )]
489 #[derive(Serialize, Deserialize)]
490 #[serde(rename_all="kebab-case")]
491 /// Basic information about archive files inside a backup snapshot.
492 pub struct BackupContent {
493 pub filename: String,
494 /// Info if file is encrypted, signed, or neither.
495 #[serde(skip_serializing_if="Option::is_none")]
496 pub crypt_mode: Option<CryptMode>,
497 /// Archive size (from backup manifest).
498 #[serde(skip_serializing_if="Option::is_none")]
499 pub size: Option<u64>,
500 }
501
502 #[api(
503 properties: {
504 "upid": {
505 optional: true,
506 schema: UPID_SCHEMA,
507 },
508 },
509 )]
510 #[derive(Clone, Serialize, Deserialize)]
511 #[serde(rename_all="kebab-case")]
512 /// Garbage collection status.
513 pub struct GarbageCollectionStatus {
514 pub upid: Option<String>,
515 /// Number of processed index files.
516 pub index_file_count: usize,
517 /// Sum of bytes referred by index files.
518 pub index_data_bytes: u64,
519 /// Bytes used on disk.
520 pub disk_bytes: u64,
521 /// Chunks used on disk.
522 pub disk_chunks: usize,
523 /// Sum of removed bytes.
524 pub removed_bytes: u64,
525 /// Number of removed chunks.
526 pub removed_chunks: usize,
527 /// Sum of pending bytes (pending removal - kept for safety).
528 pub pending_bytes: u64,
529 /// Number of pending chunks (pending removal - kept for safety).
530 pub pending_chunks: usize,
531 }
532
533 impl Default for GarbageCollectionStatus {
534 fn default() -> Self {
535 GarbageCollectionStatus {
536 upid: None,
537 index_file_count: 0,
538 index_data_bytes: 0,
539 disk_bytes: 0,
540 disk_chunks: 0,
541 removed_bytes: 0,
542 removed_chunks: 0,
543 pending_bytes: 0,
544 pending_chunks: 0,
545 }
546 }
547 }
548
549
550 #[api()]
551 #[derive(Serialize, Deserialize)]
552 /// Storage space usage information.
553 pub struct StorageStatus {
554 /// Total space (bytes).
555 pub total: u64,
556 /// Used space (bytes).
557 pub used: u64,
558 /// Available space (bytes).
559 pub avail: u64,
560 }
561
562 #[api(
563 properties: {
564 upid: { schema: UPID_SCHEMA },
565 user: { type: Userid },
566 },
567 )]
568 #[derive(Serialize, Deserialize)]
569 /// Task properties.
570 pub struct TaskListItem {
571 pub upid: String,
572 /// The node name where the task is running on.
573 pub node: String,
574 /// The Unix PID
575 pub pid: i64,
576 /// The task start time (Epoch)
577 pub pstart: u64,
578 /// The task start time (Epoch)
579 pub starttime: i64,
580 /// Worker type (arbitrary ASCII string)
581 pub worker_type: String,
582 /// Worker ID (arbitrary ASCII string)
583 pub worker_id: Option<String>,
584 /// The user who started the task
585 pub user: Userid,
586 /// The task end time (Epoch)
587 #[serde(skip_serializing_if="Option::is_none")]
588 pub endtime: Option<i64>,
589 /// Task end status
590 #[serde(skip_serializing_if="Option::is_none")]
591 pub status: Option<String>,
592 }
593
594 impl From<crate::server::TaskListInfo> for TaskListItem {
595 fn from(info: crate::server::TaskListInfo) -> Self {
596 let (endtime, status) = info
597 .state
598 .map_or_else(|| (None, None), |(a,b)| (Some(a), Some(b)));
599
600 TaskListItem {
601 upid: info.upid_str,
602 node: "localhost".to_string(),
603 pid: info.upid.pid as i64,
604 pstart: info.upid.pstart,
605 starttime: info.upid.starttime,
606 worker_type: info.upid.worker_type,
607 worker_id: info.upid.worker_id,
608 user: info.upid.userid,
609 endtime,
610 status,
611 }
612 }
613 }
614
615 #[api()]
616 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
617 #[serde(rename_all = "lowercase")]
618 /// Node Power command type.
619 pub enum NodePowerCommand {
620 /// Restart the server
621 Reboot,
622 /// Shutdown the server
623 Shutdown,
624 }
625
626 #[api()]
627 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
628 #[serde(rename_all = "lowercase")]
629 /// Interface configuration method
630 pub enum NetworkConfigMethod {
631 /// Configuration is done manually using other tools
632 Manual,
633 /// Define interfaces with statically allocated addresses.
634 Static,
635 /// Obtain an address via DHCP
636 DHCP,
637 /// Define the loopback interface.
638 Loopback,
639 }
640
641 #[api()]
642 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
643 #[serde(rename_all = "kebab-case")]
644 #[allow(non_camel_case_types)]
645 #[repr(u8)]
646 /// Linux Bond Mode
647 pub enum LinuxBondMode {
648 /// Round-robin policy
649 balance_rr = 0,
650 /// Active-backup policy
651 active_backup = 1,
652 /// XOR policy
653 balance_xor = 2,
654 /// Broadcast policy
655 broadcast = 3,
656 /// IEEE 802.3ad Dynamic link aggregation
657 //#[serde(rename = "802.3ad")]
658 ieee802_3ad = 4,
659 /// Adaptive transmit load balancing
660 balance_tlb = 5,
661 /// Adaptive load balancing
662 balance_alb = 6,
663 }
664
665 #[api()]
666 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
667 #[serde(rename_all = "lowercase")]
668 /// Network interface type
669 pub enum NetworkInterfaceType {
670 /// Loopback
671 Loopback,
672 /// Physical Ethernet device
673 Eth,
674 /// Linux Bridge
675 Bridge,
676 /// Linux Bond
677 Bond,
678 /// Linux VLAN (eth.10)
679 Vlan,
680 /// Interface Alias (eth:1)
681 Alias,
682 /// Unknown interface type
683 Unknown,
684 }
685
686 pub const NETWORK_INTERFACE_NAME_SCHEMA: Schema = StringSchema::new("Network interface name.")
687 .format(&NETWORK_INTERFACE_FORMAT)
688 .min_length(1)
689 .max_length(libc::IFNAMSIZ-1)
690 .schema();
691
692 pub const NETWORK_INTERFACE_ARRAY_SCHEMA: Schema = ArraySchema::new(
693 "Network interface list.", &NETWORK_INTERFACE_NAME_SCHEMA)
694 .schema();
695
696 pub const NETWORK_INTERFACE_LIST_SCHEMA: Schema = StringSchema::new(
697 "A list of network devices, comma separated.")
698 .format(&ApiStringFormat::PropertyString(&NETWORK_INTERFACE_ARRAY_SCHEMA))
699 .schema();
700
701 #[api(
702 properties: {
703 name: {
704 schema: NETWORK_INTERFACE_NAME_SCHEMA,
705 },
706 "type": {
707 type: NetworkInterfaceType,
708 },
709 method: {
710 type: NetworkConfigMethod,
711 optional: true,
712 },
713 method6: {
714 type: NetworkConfigMethod,
715 optional: true,
716 },
717 cidr: {
718 schema: CIDR_V4_SCHEMA,
719 optional: true,
720 },
721 cidr6: {
722 schema: CIDR_V6_SCHEMA,
723 optional: true,
724 },
725 gateway: {
726 schema: IP_V4_SCHEMA,
727 optional: true,
728 },
729 gateway6: {
730 schema: IP_V6_SCHEMA,
731 optional: true,
732 },
733 options: {
734 description: "Option list (inet)",
735 type: Array,
736 items: {
737 description: "Optional attribute line.",
738 type: String,
739 },
740 },
741 options6: {
742 description: "Option list (inet6)",
743 type: Array,
744 items: {
745 description: "Optional attribute line.",
746 type: String,
747 },
748 },
749 comments: {
750 description: "Comments (inet, may span multiple lines)",
751 type: String,
752 optional: true,
753 },
754 comments6: {
755 description: "Comments (inet6, may span multiple lines)",
756 type: String,
757 optional: true,
758 },
759 bridge_ports: {
760 schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
761 optional: true,
762 },
763 slaves: {
764 schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
765 optional: true,
766 },
767 bond_mode: {
768 type: LinuxBondMode,
769 optional: true,
770 }
771 }
772 )]
773 #[derive(Debug, Serialize, Deserialize)]
774 /// Network Interface configuration
775 pub struct Interface {
776 /// Autostart interface
777 #[serde(rename = "autostart")]
778 pub autostart: bool,
779 /// Interface is active (UP)
780 pub active: bool,
781 /// Interface name
782 pub name: String,
783 /// Interface type
784 #[serde(rename = "type")]
785 pub interface_type: NetworkInterfaceType,
786 #[serde(skip_serializing_if="Option::is_none")]
787 pub method: Option<NetworkConfigMethod>,
788 #[serde(skip_serializing_if="Option::is_none")]
789 pub method6: Option<NetworkConfigMethod>,
790 #[serde(skip_serializing_if="Option::is_none")]
791 /// IPv4 address with netmask
792 pub cidr: Option<String>,
793 #[serde(skip_serializing_if="Option::is_none")]
794 /// IPv4 gateway
795 pub gateway: Option<String>,
796 #[serde(skip_serializing_if="Option::is_none")]
797 /// IPv6 address with netmask
798 pub cidr6: Option<String>,
799 #[serde(skip_serializing_if="Option::is_none")]
800 /// IPv6 gateway
801 pub gateway6: Option<String>,
802
803 #[serde(skip_serializing_if="Vec::is_empty")]
804 pub options: Vec<String>,
805 #[serde(skip_serializing_if="Vec::is_empty")]
806 pub options6: Vec<String>,
807
808 #[serde(skip_serializing_if="Option::is_none")]
809 pub comments: Option<String>,
810 #[serde(skip_serializing_if="Option::is_none")]
811 pub comments6: Option<String>,
812
813 #[serde(skip_serializing_if="Option::is_none")]
814 /// Maximum Transmission Unit
815 pub mtu: Option<u64>,
816
817 #[serde(skip_serializing_if="Option::is_none")]
818 pub bridge_ports: Option<Vec<String>>,
819 /// Enable bridge vlan support.
820 #[serde(skip_serializing_if="Option::is_none")]
821 pub bridge_vlan_aware: Option<bool>,
822
823 #[serde(skip_serializing_if="Option::is_none")]
824 pub slaves: Option<Vec<String>>,
825 #[serde(skip_serializing_if="Option::is_none")]
826 pub bond_mode: Option<LinuxBondMode>,
827 }
828
829 // Regression tests
830
831 #[test]
832 fn test_cert_fingerprint_schema() -> Result<(), anyhow::Error> {
833
834 let schema = CERT_FINGERPRINT_SHA256_SCHEMA;
835
836 let invalid_fingerprints = [
837 "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",
838 "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",
839 "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",
840 "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",
841 "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",
842 "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",
843 ];
844
845 for fingerprint in invalid_fingerprints.iter() {
846 if let Ok(_) = parse_simple_value(fingerprint, &schema) {
847 bail!("test fingerprint '{}' failed - got Ok() while exception an error.", fingerprint);
848 }
849 }
850
851 let valid_fingerprints = [
852 "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",
853 "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",
854 ];
855
856 for fingerprint in valid_fingerprints.iter() {
857 let v = match parse_simple_value(fingerprint, &schema) {
858 Ok(v) => v,
859 Err(err) => {
860 bail!("unable to parse fingerprint '{}' - {}", fingerprint, err);
861 }
862 };
863
864 if v != serde_json::json!(fingerprint) {
865 bail!("unable to parse fingerprint '{}' - got wrong value {:?}", fingerprint, v);
866 }
867 }
868
869 Ok(())
870 }
871
872 #[test]
873 fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
874 let invalid_user_ids = [
875 "x", // too short
876 "xx", // too short
877 "xxx", // no realm
878 "xxx@", // no realm
879 "xx x@test", // contains space
880 "xx\nx@test", // contains control character
881 "x:xx@test", // contains collon
882 "xx/x@test", // contains slash
883 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@test", // too long
884 ];
885
886 for name in invalid_user_ids.iter() {
887 if let Ok(_) = parse_simple_value(name, &Userid::API_SCHEMA) {
888 bail!("test userid '{}' failed - got Ok() while exception an error.", name);
889 }
890 }
891
892 let valid_user_ids = [
893 "xxx@y",
894 "name@y",
895 "xxx@test-it.com",
896 "xxx@_T_E_S_T-it.com",
897 "x_x-x.x@test-it.com",
898 ];
899
900 for name in valid_user_ids.iter() {
901 let v = match parse_simple_value(name, &Userid::API_SCHEMA) {
902 Ok(v) => v,
903 Err(err) => {
904 bail!("unable to parse userid '{}' - {}", name, err);
905 }
906 };
907
908 if v != serde_json::json!(name) {
909 bail!("unable to parse userid '{}' - got wrong value {:?}", name, v);
910 }
911 }
912
913 Ok(())
914 }
915
916 #[api()]
917 #[derive(Copy, Clone, Serialize, Deserialize)]
918 #[serde(rename_all = "UPPERCASE")]
919 pub enum RRDMode {
920 /// Maximum
921 Max,
922 /// Average
923 Average,
924 }
925
926
927 #[api()]
928 #[repr(u64)]
929 #[derive(Copy, Clone, Serialize, Deserialize)]
930 #[serde(rename_all = "lowercase")]
931 pub enum RRDTimeFrameResolution {
932 /// 1 min => last 70 minutes
933 Hour = 60,
934 /// 30 min => last 35 hours
935 Day = 60*30,
936 /// 3 hours => about 8 days
937 Week = 60*180,
938 /// 12 hours => last 35 days
939 Month = 60*720,
940 /// 1 week => last 490 days
941 Year = 60*10080,
942 }
943
944 #[api()]
945 #[derive(Serialize, Deserialize)]
946 #[serde(rename_all = "PascalCase")]
947 /// Describes a package for which an update is available.
948 pub struct APTUpdateInfo {
949 /// Package name
950 pub package: String,
951 /// Package title
952 pub title: String,
953 /// Package architecture
954 pub arch: String,
955 /// Human readable package description
956 pub description: String,
957 /// New version to be updated to
958 pub version: String,
959 /// Old version currently installed
960 pub old_version: String,
961 /// Package origin
962 pub origin: String,
963 /// Package priority in human-readable form
964 pub priority: String,
965 /// Package section
966 pub section: String,
967 /// URL under which the package's changelog can be retrieved
968 pub change_log_url: String,
969 }