]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/types/mod.rs
move remaining client tools to pbs-tools/datastore
[proxmox-backup.git] / src / api2 / types / mod.rs
1 //! API Type Definitions
2
3 use anyhow::bail;
4 use serde::{Deserialize, Serialize};
5
6 use proxmox::api::{api, schema::*};
7 use proxmox::const_regex;
8
9 use pbs_datastore::catalog::CatalogEntryType;
10
11 use crate::{
12 backup::DirEntryAttribute,
13 config::acl::Role,
14 };
15
16 mod tape;
17 pub use tape::*;
18
19 mod file_restore;
20 pub use file_restore::*;
21
22 mod acme;
23 pub use acme::*;
24
25 pub use pbs_api_types::*;
26
27 // File names: may not contain slashes, may not start with "."
28 pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| {
29 if name.starts_with('.') {
30 bail!("file names may not start with '.'");
31 }
32 if name.contains('/') {
33 bail!("file names may not contain slashes");
34 }
35 Ok(())
36 });
37
38 const_regex!{
39 pub SYSTEMD_DATETIME_REGEX = r"^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$"; // fixme: define in common_regex ?
40
41 /// Regex for verification jobs 'DATASTORE:ACTUAL_JOB_ID'
42 pub VERIFICATION_JOB_WORKER_ID_REGEX = concat!(r"^(", PROXMOX_SAFE_ID_REGEX_STR!(), r"):");
43 /// Regex for sync jobs 'REMOTE:REMOTE_DATASTORE:LOCAL_DATASTORE:ACTUAL_JOB_ID'
44 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"):");
45
46 pub ACL_PATH_REGEX = concat!(r"^(?:/|", r"(?:/", PROXMOX_SAFE_ID_REGEX_STR!(), ")+", r")$");
47
48 pub SUBSCRIPTION_KEY_REGEX = concat!(r"^pbs(?:[cbsp])-[0-9a-f]{10}$");
49
50 pub BLOCKDEVICE_NAME_REGEX = r"^(:?(:?h|s|x?v)d[a-z]+)|(:?nvme\d+n\d+)$";
51
52 pub ZPOOL_NAME_REGEX = r"^[a-zA-Z][a-z0-9A-Z\-_.:]+$";
53
54 pub DATASTORE_MAP_REGEX = concat!(r"(:?", PROXMOX_SAFE_ID_REGEX_STR!(), r"=)?", PROXMOX_SAFE_ID_REGEX_STR!());
55
56 pub TAPE_RESTORE_SNAPSHOT_REGEX = concat!(r"^", PROXMOX_SAFE_ID_REGEX_STR!(), r":", SNAPSHOT_PATH_REGEX_STR!(), r"$");
57 }
58
59 pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat =
60 ApiStringFormat::Pattern(&SYSTEMD_DATETIME_REGEX);
61
62 pub const HOSTNAME_FORMAT: ApiStringFormat =
63 ApiStringFormat::Pattern(&HOSTNAME_REGEX);
64
65 pub const DNS_NAME_FORMAT: ApiStringFormat =
66 ApiStringFormat::Pattern(&DNS_NAME_REGEX);
67
68 pub const DNS_ALIAS_FORMAT: ApiStringFormat =
69 ApiStringFormat::Pattern(&DNS_ALIAS_REGEX);
70
71 pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat =
72 ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX);
73
74 pub const ACL_PATH_FORMAT: ApiStringFormat =
75 ApiStringFormat::Pattern(&ACL_PATH_REGEX);
76
77 pub const NETWORK_INTERFACE_FORMAT: ApiStringFormat =
78 ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
79
80 pub const SUBSCRIPTION_KEY_FORMAT: ApiStringFormat =
81 ApiStringFormat::Pattern(&SUBSCRIPTION_KEY_REGEX);
82
83 pub const BLOCKDEVICE_NAME_FORMAT: ApiStringFormat =
84 ApiStringFormat::Pattern(&BLOCKDEVICE_NAME_REGEX);
85
86 pub const DATASTORE_MAP_FORMAT: ApiStringFormat =
87 ApiStringFormat::Pattern(&DATASTORE_MAP_REGEX);
88
89 pub const TAPE_RESTORE_SNAPSHOT_FORMAT: ApiStringFormat =
90 ApiStringFormat::Pattern(&TAPE_RESTORE_SNAPSHOT_REGEX);
91
92 pub const PASSWORD_SCHEMA: Schema = StringSchema::new("Password.")
93 .format(&PASSWORD_FORMAT)
94 .min_length(1)
95 .max_length(1024)
96 .schema();
97
98 pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.")
99 .format(&PASSWORD_FORMAT)
100 .min_length(5)
101 .max_length(64)
102 .schema();
103
104 pub const TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA: Schema = StringSchema::new(
105 "Tape encryption key fingerprint (sha256)."
106 )
107 .format(&FINGERPRINT_SHA256_FORMAT)
108 .schema();
109
110 pub const CHUNK_DIGEST_SCHEMA: Schema = StringSchema::new("Chunk digest (SHA256).")
111 .format(&CHUNK_DIGEST_FORMAT)
112 .schema();
113
114 pub const NODE_SCHEMA: Schema = StringSchema::new("Node name (or 'localhost')")
115 .format(&ApiStringFormat::VerifyFn(|node| {
116 if node == "localhost" || node == proxmox::tools::nodename() {
117 Ok(())
118 } else {
119 bail!("no such node '{}'", node);
120 }
121 }))
122 .schema();
123
124 pub const SEARCH_DOMAIN_SCHEMA: Schema =
125 StringSchema::new("Search domain for host-name lookup.").schema();
126
127 pub const FIRST_DNS_SERVER_SCHEMA: Schema =
128 StringSchema::new("First name server IP address.")
129 .format(&IP_FORMAT)
130 .schema();
131
132 pub const SECOND_DNS_SERVER_SCHEMA: Schema =
133 StringSchema::new("Second name server IP address.")
134 .format(&IP_FORMAT)
135 .schema();
136
137 pub const THIRD_DNS_SERVER_SCHEMA: Schema =
138 StringSchema::new("Third name server IP address.")
139 .format(&IP_FORMAT)
140 .schema();
141
142 pub const IP_V4_SCHEMA: Schema =
143 StringSchema::new("IPv4 address.")
144 .format(&IP_V4_FORMAT)
145 .max_length(15)
146 .schema();
147
148 pub const IP_V6_SCHEMA: Schema =
149 StringSchema::new("IPv6 address.")
150 .format(&IP_V6_FORMAT)
151 .max_length(39)
152 .schema();
153
154 pub const IP_SCHEMA: Schema =
155 StringSchema::new("IP (IPv4 or IPv6) address.")
156 .format(&IP_FORMAT)
157 .max_length(39)
158 .schema();
159
160 pub const CIDR_V4_SCHEMA: Schema =
161 StringSchema::new("IPv4 address with netmask (CIDR notation).")
162 .format(&CIDR_V4_FORMAT)
163 .max_length(18)
164 .schema();
165
166 pub const CIDR_V6_SCHEMA: Schema =
167 StringSchema::new("IPv6 address with netmask (CIDR notation).")
168 .format(&CIDR_V6_FORMAT)
169 .max_length(43)
170 .schema();
171
172 pub const CIDR_SCHEMA: Schema =
173 StringSchema::new("IP address (IPv4 or IPv6) with netmask (CIDR notation).")
174 .format(&CIDR_FORMAT)
175 .max_length(43)
176 .schema();
177
178 pub const TIME_ZONE_SCHEMA: Schema = StringSchema::new(
179 "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.")
180 .format(&SINGLE_LINE_COMMENT_FORMAT)
181 .min_length(2)
182 .max_length(64)
183 .schema();
184
185 pub const ACL_PATH_SCHEMA: Schema = StringSchema::new(
186 "Access control path.")
187 .format(&ACL_PATH_FORMAT)
188 .min_length(1)
189 .max_length(128)
190 .schema();
191
192 pub const ACL_PROPAGATE_SCHEMA: Schema = BooleanSchema::new(
193 "Allow to propagate (inherit) permissions.")
194 .default(true)
195 .schema();
196
197 pub const ACL_UGID_TYPE_SCHEMA: Schema = StringSchema::new(
198 "Type of 'ugid' property.")
199 .format(&ApiStringFormat::Enum(&[
200 EnumEntry::new("user", "User"),
201 EnumEntry::new("group", "Group")]))
202 .schema();
203
204 #[api(
205 properties: {
206 propagate: {
207 schema: ACL_PROPAGATE_SCHEMA,
208 },
209 path: {
210 schema: ACL_PATH_SCHEMA,
211 },
212 ugid_type: {
213 schema: ACL_UGID_TYPE_SCHEMA,
214 },
215 ugid: {
216 type: String,
217 description: "User or Group ID.",
218 },
219 roleid: {
220 type: Role,
221 }
222 }
223 )]
224 #[derive(Serialize, Deserialize)]
225 /// ACL list entry.
226 pub struct AclListItem {
227 pub path: String,
228 pub ugid: String,
229 pub ugid_type: String,
230 pub propagate: bool,
231 pub roleid: String,
232 }
233
234 pub const UPID_SCHEMA: Schema = StringSchema::new("Unique Process/Task ID.")
235 .max_length(256)
236 .schema();
237
238 pub const DATASTORE_MAP_SCHEMA: Schema = StringSchema::new("Datastore mapping.")
239 .format(&DATASTORE_MAP_FORMAT)
240 .min_length(3)
241 .max_length(65)
242 .type_text("(<source>=)?<target>")
243 .schema();
244
245 pub const DATASTORE_MAP_ARRAY_SCHEMA: Schema = ArraySchema::new(
246 "Datastore mapping list.", &DATASTORE_MAP_SCHEMA)
247 .schema();
248
249 pub const DATASTORE_MAP_LIST_SCHEMA: Schema = StringSchema::new(
250 "A list of Datastore mappings (or single datastore), comma separated. \
251 For example 'a=b,e' maps the source datastore 'a' to target 'b and \
252 all other sources to the default 'e'. If no default is given, only the \
253 specified sources are mapped.")
254 .format(&ApiStringFormat::PropertyString(&DATASTORE_MAP_ARRAY_SCHEMA))
255 .schema();
256
257 pub const TAPE_RESTORE_SNAPSHOT_SCHEMA: Schema = StringSchema::new(
258 "A snapshot in the format: 'store:type/id/time")
259 .format(&TAPE_RESTORE_SNAPSHOT_FORMAT)
260 .type_text("store:type/id/time")
261 .schema();
262
263 pub const MEDIA_SET_UUID_SCHEMA: Schema =
264 StringSchema::new("MediaSet Uuid (We use the all-zero Uuid to reseve an empty media for a specific pool).")
265 .format(&UUID_FORMAT)
266 .schema();
267
268 pub const MEDIA_UUID_SCHEMA: Schema =
269 StringSchema::new("Media Uuid.")
270 .format(&UUID_FORMAT)
271 .schema();
272
273 pub const SYNC_SCHEDULE_SCHEMA: Schema = StringSchema::new(
274 "Run sync job at specified schedule.")
275 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
276 .type_text("<calendar-event>")
277 .schema();
278
279 pub const GC_SCHEDULE_SCHEMA: Schema = StringSchema::new(
280 "Run garbage collection job at specified schedule.")
281 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
282 .type_text("<calendar-event>")
283 .schema();
284
285 pub const PRUNE_SCHEDULE_SCHEMA: Schema = StringSchema::new(
286 "Run prune job at specified schedule.")
287 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
288 .type_text("<calendar-event>")
289 .schema();
290
291 pub const VERIFICATION_SCHEDULE_SCHEMA: Schema = StringSchema::new(
292 "Run verify job at specified schedule.")
293 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
294 .type_text("<calendar-event>")
295 .schema();
296
297 pub const REMOTE_ID_SCHEMA: Schema = StringSchema::new("Remote ID.")
298 .format(&PROXMOX_SAFE_ID_FORMAT)
299 .min_length(3)
300 .max_length(32)
301 .schema();
302
303 pub const JOB_ID_SCHEMA: Schema = StringSchema::new("Job ID.")
304 .format(&PROXMOX_SAFE_ID_FORMAT)
305 .min_length(3)
306 .max_length(32)
307 .schema();
308
309 pub const REMOVE_VANISHED_BACKUPS_SCHEMA: Schema = BooleanSchema::new(
310 "Delete vanished backups. This remove the local copy if the remote backup was deleted.")
311 .default(true)
312 .schema();
313
314 pub const IGNORE_VERIFIED_BACKUPS_SCHEMA: Schema = BooleanSchema::new(
315 "Do not verify backups that are already verified if their verification is not outdated.")
316 .default(true)
317 .schema();
318
319 pub const VERIFICATION_OUTDATED_AFTER_SCHEMA: Schema = IntegerSchema::new(
320 "Days after that a verification becomes outdated")
321 .minimum(1)
322 .schema();
323
324 pub const HOSTNAME_SCHEMA: Schema = StringSchema::new("Hostname (as defined in RFC1123).")
325 .format(&HOSTNAME_FORMAT)
326 .schema();
327
328 pub const DNS_NAME_OR_IP_SCHEMA: Schema = StringSchema::new("DNS name or IP address.")
329 .format(&DNS_NAME_OR_IP_FORMAT)
330 .schema();
331
332 pub const SUBSCRIPTION_KEY_SCHEMA: Schema = StringSchema::new("Proxmox Backup Server subscription key.")
333 .format(&SUBSCRIPTION_KEY_FORMAT)
334 .min_length(15)
335 .max_length(16)
336 .schema();
337
338 pub const BLOCKDEVICE_NAME_SCHEMA: Schema = StringSchema::new("Block device name (/sys/block/<name>).")
339 .format(&BLOCKDEVICE_NAME_FORMAT)
340 .min_length(3)
341 .max_length(64)
342 .schema();
343
344 pub const REALM_ID_SCHEMA: Schema = StringSchema::new("Realm name.")
345 .format(&PROXMOX_SAFE_ID_FORMAT)
346 .min_length(2)
347 .max_length(32)
348 .schema();
349
350 // Complex type definitions
351
352 #[api(
353 properties: {
354 "gc-status": {
355 type: GarbageCollectionStatus,
356 optional: true,
357 },
358 counts: {
359 type: Counts,
360 optional: true,
361 },
362 },
363 )]
364 #[derive(Serialize, Deserialize)]
365 #[serde(rename_all="kebab-case")]
366 /// Overall Datastore status and useful information.
367 pub struct DataStoreStatus {
368 /// Total space (bytes).
369 pub total: u64,
370 /// Used space (bytes).
371 pub used: u64,
372 /// Available space (bytes).
373 pub avail: u64,
374 /// Status of last GC
375 #[serde(skip_serializing_if="Option::is_none")]
376 pub gc_status: Option<GarbageCollectionStatus>,
377 /// Group/Snapshot counts
378 #[serde(skip_serializing_if="Option::is_none")]
379 pub counts: Option<Counts>,
380 }
381
382 #[api(
383 properties: {
384 upid: { schema: UPID_SCHEMA },
385 user: { type: Authid },
386 },
387 )]
388 #[derive(Serialize, Deserialize)]
389 /// Task properties.
390 pub struct TaskListItem {
391 pub upid: String,
392 /// The node name where the task is running on.
393 pub node: String,
394 /// The Unix PID
395 pub pid: i64,
396 /// The task start time (Epoch)
397 pub pstart: u64,
398 /// The task start time (Epoch)
399 pub starttime: i64,
400 /// Worker type (arbitrary ASCII string)
401 pub worker_type: String,
402 /// Worker ID (arbitrary ASCII string)
403 pub worker_id: Option<String>,
404 /// The authenticated entity who started the task
405 pub user: Authid,
406 /// The task end time (Epoch)
407 #[serde(skip_serializing_if="Option::is_none")]
408 pub endtime: Option<i64>,
409 /// Task end status
410 #[serde(skip_serializing_if="Option::is_none")]
411 pub status: Option<String>,
412 }
413
414 impl From<crate::server::TaskListInfo> for TaskListItem {
415 fn from(info: crate::server::TaskListInfo) -> Self {
416 let (endtime, status) = info
417 .state
418 .map_or_else(|| (None, None), |a| (Some(a.endtime()), Some(a.to_string())));
419
420 TaskListItem {
421 upid: info.upid_str,
422 node: "localhost".to_string(),
423 pid: info.upid.pid as i64,
424 pstart: info.upid.pstart,
425 starttime: info.upid.starttime,
426 worker_type: info.upid.worker_type,
427 worker_id: info.upid.worker_id,
428 user: info.upid.auth_id,
429 endtime,
430 status,
431 }
432 }
433 }
434
435 #[api()]
436 #[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
437 #[serde(rename_all = "lowercase")]
438 pub enum TaskStateType {
439 /// Ok
440 OK,
441 /// Warning
442 Warning,
443 /// Error
444 Error,
445 /// Unknown
446 Unknown,
447 }
448
449 #[api()]
450 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
451 #[serde(rename_all = "lowercase")]
452 /// Node Power command type.
453 pub enum NodePowerCommand {
454 /// Restart the server
455 Reboot,
456 /// Shutdown the server
457 Shutdown,
458 }
459
460 #[api()]
461 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
462 #[serde(rename_all = "lowercase")]
463 /// Interface configuration method
464 pub enum NetworkConfigMethod {
465 /// Configuration is done manually using other tools
466 Manual,
467 /// Define interfaces with statically allocated addresses.
468 Static,
469 /// Obtain an address via DHCP
470 DHCP,
471 /// Define the loopback interface.
472 Loopback,
473 }
474
475 #[api()]
476 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
477 #[serde(rename_all = "kebab-case")]
478 #[allow(non_camel_case_types)]
479 #[repr(u8)]
480 /// Linux Bond Mode
481 pub enum LinuxBondMode {
482 /// Round-robin policy
483 balance_rr = 0,
484 /// Active-backup policy
485 active_backup = 1,
486 /// XOR policy
487 balance_xor = 2,
488 /// Broadcast policy
489 broadcast = 3,
490 /// IEEE 802.3ad Dynamic link aggregation
491 #[serde(rename = "802.3ad")]
492 ieee802_3ad = 4,
493 /// Adaptive transmit load balancing
494 balance_tlb = 5,
495 /// Adaptive load balancing
496 balance_alb = 6,
497 }
498
499 #[api()]
500 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
501 #[serde(rename_all = "kebab-case")]
502 #[allow(non_camel_case_types)]
503 #[repr(u8)]
504 /// Bond Transmit Hash Policy for LACP (802.3ad)
505 pub enum BondXmitHashPolicy {
506 /// Layer 2
507 layer2 = 0,
508 /// Layer 2+3
509 #[serde(rename = "layer2+3")]
510 layer2_3 = 1,
511 /// Layer 3+4
512 #[serde(rename = "layer3+4")]
513 layer3_4 = 2,
514 }
515
516 #[api()]
517 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
518 #[serde(rename_all = "lowercase")]
519 /// Network interface type
520 pub enum NetworkInterfaceType {
521 /// Loopback
522 Loopback,
523 /// Physical Ethernet device
524 Eth,
525 /// Linux Bridge
526 Bridge,
527 /// Linux Bond
528 Bond,
529 /// Linux VLAN (eth.10)
530 Vlan,
531 /// Interface Alias (eth:1)
532 Alias,
533 /// Unknown interface type
534 Unknown,
535 }
536
537 pub const NETWORK_INTERFACE_NAME_SCHEMA: Schema = StringSchema::new("Network interface name.")
538 .format(&NETWORK_INTERFACE_FORMAT)
539 .min_length(1)
540 .max_length(libc::IFNAMSIZ-1)
541 .schema();
542
543 pub const NETWORK_INTERFACE_ARRAY_SCHEMA: Schema = ArraySchema::new(
544 "Network interface list.", &NETWORK_INTERFACE_NAME_SCHEMA)
545 .schema();
546
547 pub const NETWORK_INTERFACE_LIST_SCHEMA: Schema = StringSchema::new(
548 "A list of network devices, comma separated.")
549 .format(&ApiStringFormat::PropertyString(&NETWORK_INTERFACE_ARRAY_SCHEMA))
550 .schema();
551
552 #[api(
553 properties: {
554 name: {
555 schema: NETWORK_INTERFACE_NAME_SCHEMA,
556 },
557 "type": {
558 type: NetworkInterfaceType,
559 },
560 method: {
561 type: NetworkConfigMethod,
562 optional: true,
563 },
564 method6: {
565 type: NetworkConfigMethod,
566 optional: true,
567 },
568 cidr: {
569 schema: CIDR_V4_SCHEMA,
570 optional: true,
571 },
572 cidr6: {
573 schema: CIDR_V6_SCHEMA,
574 optional: true,
575 },
576 gateway: {
577 schema: IP_V4_SCHEMA,
578 optional: true,
579 },
580 gateway6: {
581 schema: IP_V6_SCHEMA,
582 optional: true,
583 },
584 options: {
585 description: "Option list (inet)",
586 type: Array,
587 items: {
588 description: "Optional attribute line.",
589 type: String,
590 },
591 },
592 options6: {
593 description: "Option list (inet6)",
594 type: Array,
595 items: {
596 description: "Optional attribute line.",
597 type: String,
598 },
599 },
600 comments: {
601 description: "Comments (inet, may span multiple lines)",
602 type: String,
603 optional: true,
604 },
605 comments6: {
606 description: "Comments (inet6, may span multiple lines)",
607 type: String,
608 optional: true,
609 },
610 bridge_ports: {
611 schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
612 optional: true,
613 },
614 slaves: {
615 schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
616 optional: true,
617 },
618 bond_mode: {
619 type: LinuxBondMode,
620 optional: true,
621 },
622 "bond-primary": {
623 schema: NETWORK_INTERFACE_NAME_SCHEMA,
624 optional: true,
625 },
626 bond_xmit_hash_policy: {
627 type: BondXmitHashPolicy,
628 optional: true,
629 },
630 }
631 )]
632 #[derive(Debug, Serialize, Deserialize)]
633 /// Network Interface configuration
634 pub struct Interface {
635 /// Autostart interface
636 #[serde(rename = "autostart")]
637 pub autostart: bool,
638 /// Interface is active (UP)
639 pub active: bool,
640 /// Interface name
641 pub name: String,
642 /// Interface type
643 #[serde(rename = "type")]
644 pub interface_type: NetworkInterfaceType,
645 #[serde(skip_serializing_if="Option::is_none")]
646 pub method: Option<NetworkConfigMethod>,
647 #[serde(skip_serializing_if="Option::is_none")]
648 pub method6: Option<NetworkConfigMethod>,
649 #[serde(skip_serializing_if="Option::is_none")]
650 /// IPv4 address with netmask
651 pub cidr: Option<String>,
652 #[serde(skip_serializing_if="Option::is_none")]
653 /// IPv4 gateway
654 pub gateway: Option<String>,
655 #[serde(skip_serializing_if="Option::is_none")]
656 /// IPv6 address with netmask
657 pub cidr6: Option<String>,
658 #[serde(skip_serializing_if="Option::is_none")]
659 /// IPv6 gateway
660 pub gateway6: Option<String>,
661
662 #[serde(skip_serializing_if="Vec::is_empty")]
663 pub options: Vec<String>,
664 #[serde(skip_serializing_if="Vec::is_empty")]
665 pub options6: Vec<String>,
666
667 #[serde(skip_serializing_if="Option::is_none")]
668 pub comments: Option<String>,
669 #[serde(skip_serializing_if="Option::is_none")]
670 pub comments6: Option<String>,
671
672 #[serde(skip_serializing_if="Option::is_none")]
673 /// Maximum Transmission Unit
674 pub mtu: Option<u64>,
675
676 #[serde(skip_serializing_if="Option::is_none")]
677 pub bridge_ports: Option<Vec<String>>,
678 /// Enable bridge vlan support.
679 #[serde(skip_serializing_if="Option::is_none")]
680 pub bridge_vlan_aware: Option<bool>,
681
682 #[serde(skip_serializing_if="Option::is_none")]
683 pub slaves: Option<Vec<String>>,
684 #[serde(skip_serializing_if="Option::is_none")]
685 pub bond_mode: Option<LinuxBondMode>,
686 #[serde(skip_serializing_if="Option::is_none")]
687 #[serde(rename = "bond-primary")]
688 pub bond_primary: Option<String>,
689 pub bond_xmit_hash_policy: Option<BondXmitHashPolicy>,
690 }
691
692 // Regression tests
693
694 #[test]
695 fn test_cert_fingerprint_schema() -> Result<(), anyhow::Error> {
696
697 let schema = CERT_FINGERPRINT_SHA256_SCHEMA;
698
699 let invalid_fingerprints = [
700 "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",
701 "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",
702 "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",
703 "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",
704 "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",
705 "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",
706 ];
707
708 for fingerprint in invalid_fingerprints.iter() {
709 if parse_simple_value(fingerprint, &schema).is_ok() {
710 bail!("test fingerprint '{}' failed - got Ok() while exception an error.", fingerprint);
711 }
712 }
713
714 let valid_fingerprints = [
715 "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",
716 "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",
717 ];
718
719 for fingerprint in valid_fingerprints.iter() {
720 let v = match parse_simple_value(fingerprint, &schema) {
721 Ok(v) => v,
722 Err(err) => {
723 bail!("unable to parse fingerprint '{}' - {}", fingerprint, err);
724 }
725 };
726
727 if v != serde_json::json!(fingerprint) {
728 bail!("unable to parse fingerprint '{}' - got wrong value {:?}", fingerprint, v);
729 }
730 }
731
732 Ok(())
733 }
734
735 #[test]
736 fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
737 let invalid_user_ids = [
738 "x", // too short
739 "xx", // too short
740 "xxx", // no realm
741 "xxx@", // no realm
742 "xx x@test", // contains space
743 "xx\nx@test", // contains control character
744 "x:xx@test", // contains collon
745 "xx/x@test", // contains slash
746 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@test", // too long
747 ];
748
749 for name in invalid_user_ids.iter() {
750 if parse_simple_value(name, &Userid::API_SCHEMA).is_ok() {
751 bail!("test userid '{}' failed - got Ok() while exception an error.", name);
752 }
753 }
754
755 let valid_user_ids = [
756 "xxx@y",
757 "name@y",
758 "xxx@test-it.com",
759 "xxx@_T_E_S_T-it.com",
760 "x_x-x.x@test-it.com",
761 ];
762
763 for name in valid_user_ids.iter() {
764 let v = match parse_simple_value(name, &Userid::API_SCHEMA) {
765 Ok(v) => v,
766 Err(err) => {
767 bail!("unable to parse userid '{}' - {}", name, err);
768 }
769 };
770
771 if v != serde_json::json!(name) {
772 bail!("unable to parse userid '{}' - got wrong value {:?}", name, v);
773 }
774 }
775
776 Ok(())
777 }
778
779 #[api()]
780 #[derive(Copy, Clone, Serialize, Deserialize)]
781 #[serde(rename_all = "UPPERCASE")]
782 pub enum RRDMode {
783 /// Maximum
784 Max,
785 /// Average
786 Average,
787 }
788
789
790 #[api()]
791 #[repr(u64)]
792 #[derive(Copy, Clone, Serialize, Deserialize)]
793 #[serde(rename_all = "lowercase")]
794 pub enum RRDTimeFrameResolution {
795 /// 1 min => last 70 minutes
796 Hour = 60,
797 /// 30 min => last 35 hours
798 Day = 60*30,
799 /// 3 hours => about 8 days
800 Week = 60*180,
801 /// 12 hours => last 35 days
802 Month = 60*720,
803 /// 1 week => last 490 days
804 Year = 60*10080,
805 }
806
807 #[api()]
808 #[derive(Debug, Clone, Serialize, Deserialize)]
809 #[serde(rename_all = "PascalCase")]
810 /// Describes a package for which an update is available.
811 pub struct APTUpdateInfo {
812 /// Package name
813 pub package: String,
814 /// Package title
815 pub title: String,
816 /// Package architecture
817 pub arch: String,
818 /// Human readable package description
819 pub description: String,
820 /// New version to be updated to
821 pub version: String,
822 /// Old version currently installed
823 pub old_version: String,
824 /// Package origin
825 pub origin: String,
826 /// Package priority in human-readable form
827 pub priority: String,
828 /// Package section
829 pub section: String,
830 /// URL under which the package's changelog can be retrieved
831 pub change_log_url: String,
832 /// Custom extra field for additional package information
833 #[serde(skip_serializing_if="Option::is_none")]
834 pub extra_info: Option<String>,
835 }
836
837 #[api()]
838 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
839 #[serde(rename_all = "lowercase")]
840 /// When do we send notifications
841 pub enum Notify {
842 /// Never send notification
843 Never,
844 /// Send notifications for failed and successful jobs
845 Always,
846 /// Send notifications for failed jobs only
847 Error,
848 }
849
850 #[api(
851 properties: {
852 gc: {
853 type: Notify,
854 optional: true,
855 },
856 verify: {
857 type: Notify,
858 optional: true,
859 },
860 sync: {
861 type: Notify,
862 optional: true,
863 },
864 },
865 )]
866 #[derive(Debug, Serialize, Deserialize)]
867 /// Datastore notify settings
868 pub struct DatastoreNotify {
869 /// Garbage collection settings
870 pub gc: Option<Notify>,
871 /// Verify job setting
872 pub verify: Option<Notify>,
873 /// Sync job setting
874 pub sync: Option<Notify>,
875 }
876
877 /// An entry in a hierarchy of files for restore and listing.
878 #[api()]
879 #[derive(Serialize, Deserialize)]
880 pub struct ArchiveEntry {
881 /// Base64-encoded full path to the file, including the filename
882 pub filepath: String,
883 /// Displayable filename text for UIs
884 pub text: String,
885 /// File or directory type of this entry
886 #[serde(rename = "type")]
887 pub entry_type: String,
888 /// Is this entry a leaf node, or does it have children (i.e. a directory)?
889 pub leaf: bool,
890 /// The file size, if entry_type is 'f' (file)
891 #[serde(skip_serializing_if="Option::is_none")]
892 pub size: Option<u64>,
893 /// The file "last modified" time stamp, if entry_type is 'f' (file)
894 #[serde(skip_serializing_if="Option::is_none")]
895 pub mtime: Option<i64>,
896 }
897
898 impl ArchiveEntry {
899 pub fn new(filepath: &[u8], entry_type: Option<&DirEntryAttribute>) -> Self {
900 let size = match entry_type {
901 Some(DirEntryAttribute::File { size, .. }) => Some(*size),
902 _ => None,
903 };
904 Self::new_with_size(filepath, entry_type, size)
905 }
906
907 pub fn new_with_size(
908 filepath: &[u8],
909 entry_type: Option<&DirEntryAttribute>,
910 size: Option<u64>,
911 ) -> Self {
912 Self {
913 filepath: base64::encode(filepath),
914 text: String::from_utf8_lossy(filepath.split(|x| *x == b'/').last().unwrap())
915 .to_string(),
916 entry_type: match entry_type {
917 Some(entry_type) => CatalogEntryType::from(entry_type).to_string(),
918 None => "v".to_owned(),
919 },
920 leaf: !matches!(entry_type, None | Some(DirEntryAttribute::Directory { .. })),
921 size,
922 mtime: match entry_type {
923 Some(DirEntryAttribute::File { mtime, .. }) => Some(*mtime),
924 _ => None,
925 },
926 }
927 }
928 }
929
930 pub const DATASTORE_NOTIFY_STRING_SCHEMA: Schema = StringSchema::new(
931 "Datastore notification setting")
932 .format(&ApiStringFormat::PropertyString(&DatastoreNotify::API_SCHEMA))
933 .schema();
934
935
936 #[api(
937 properties: {
938 "next-run": {
939 description: "Estimated time of the next run (UNIX epoch).",
940 optional: true,
941 type: Integer,
942 },
943 "last-run-state": {
944 description: "Result of the last run.",
945 optional: true,
946 type: String,
947 },
948 "last-run-upid": {
949 description: "Task UPID of the last run.",
950 optional: true,
951 type: String,
952 },
953 "last-run-endtime": {
954 description: "Endtime of the last run.",
955 optional: true,
956 type: Integer,
957 },
958 }
959 )]
960 #[derive(Serialize,Deserialize,Default)]
961 #[serde(rename_all="kebab-case")]
962 /// Job Scheduling Status
963 pub struct JobScheduleStatus {
964 #[serde(skip_serializing_if="Option::is_none")]
965 pub next_run: Option<i64>,
966 #[serde(skip_serializing_if="Option::is_none")]
967 pub last_run_state: Option<String>,
968 #[serde(skip_serializing_if="Option::is_none")]
969 pub last_run_upid: Option<String>,
970 #[serde(skip_serializing_if="Option::is_none")]
971 pub last_run_endtime: Option<i64>,
972 }
973
974 #[api]
975 #[derive(Serialize, Deserialize, Default)]
976 #[serde(rename_all = "kebab-case")]
977 /// Node memory usage counters
978 pub struct NodeMemoryCounters {
979 /// Total memory
980 pub total: u64,
981 /// Used memory
982 pub used: u64,
983 /// Free memory
984 pub free: u64,
985 }
986
987 #[api]
988 #[derive(Serialize, Deserialize, Default)]
989 #[serde(rename_all = "kebab-case")]
990 /// Node swap usage counters
991 pub struct NodeSwapCounters {
992 /// Total swap
993 pub total: u64,
994 /// Used swap
995 pub used: u64,
996 /// Free swap
997 pub free: u64,
998 }
999
1000 #[api]
1001 #[derive(Serialize,Deserialize,Default)]
1002 #[serde(rename_all = "kebab-case")]
1003 /// Contains general node information such as the fingerprint`
1004 pub struct NodeInformation {
1005 /// The SSL Fingerprint
1006 pub fingerprint: String,
1007 }
1008
1009 #[api]
1010 #[derive(Serialize, Deserialize, Default)]
1011 #[serde(rename_all = "kebab-case")]
1012 /// Information about the CPU
1013 pub struct NodeCpuInformation {
1014 /// The CPU model
1015 pub model: String,
1016 /// The number of CPU sockets
1017 pub sockets: usize,
1018 /// The number of CPU cores (incl. threads)
1019 pub cpus: usize,
1020 }
1021
1022 #[api(
1023 properties: {
1024 memory: {
1025 type: NodeMemoryCounters,
1026 },
1027 root: {
1028 type: StorageStatus,
1029 },
1030 swap: {
1031 type: NodeSwapCounters,
1032 },
1033 loadavg: {
1034 type: Array,
1035 items: {
1036 type: Number,
1037 description: "the load",
1038 }
1039 },
1040 cpuinfo: {
1041 type: NodeCpuInformation,
1042 },
1043 info: {
1044 type: NodeInformation,
1045 }
1046 },
1047 )]
1048 #[derive(Serialize, Deserialize, Default)]
1049 #[serde(rename_all = "kebab-case")]
1050 /// The Node status
1051 pub struct NodeStatus {
1052 pub memory: NodeMemoryCounters,
1053 pub root: StorageStatus,
1054 pub swap: NodeSwapCounters,
1055 /// The current uptime of the server.
1056 pub uptime: u64,
1057 /// Load for 1, 5 and 15 minutes.
1058 pub loadavg: [f64; 3],
1059 /// The current kernel version.
1060 pub kversion: String,
1061 /// Total CPU usage since last query.
1062 pub cpu: f64,
1063 /// Total IO wait since last query.
1064 pub wait: f64,
1065 pub cpuinfo: NodeCpuInformation,
1066 pub info: NodeInformation,
1067 }
1068
1069 pub const HTTP_PROXY_SCHEMA: Schema = StringSchema::new(
1070 "HTTP proxy configuration [http://]<host>[:port]")
1071 .format(&ApiStringFormat::VerifyFn(|s| {
1072 proxmox_http::ProxyConfig::parse_proxy_url(s)?;
1073 Ok(())
1074 }))
1075 .min_length(1)
1076 .max_length(128)
1077 .type_text("[http://]<host>[:port]")
1078 .schema();