]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/types/mod.rs
move some api types to pbs-api-types
[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;
255f378a 8
f323e906
WB
9use pbs_datastore::catalog::CatalogEntryType;
10
bf78f708 11use crate::{
ea584a75 12 backup::DirEntryAttribute,
bf78f708
DM
13 config::acl::Role,
14};
f28d9088 15
a0765714
DM
16mod tape;
17pub use tape::*;
18
dd9cef56
SR
19mod file_restore;
20pub use file_restore::*;
21
39c5db7f
DM
22mod acme;
23pub use acme::*;
24
751f6b61 25pub use pbs_api_types::*;
86fb3877 26
255f378a
DM
27// File names: may not contain slashes, may not start with "."
28pub 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
255f378a 38const_regex!{
255f378a 39 pub SYSTEMD_DATETIME_REGEX = r"^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$"; // fixme: define in common_regex ?
d0adf270 40
dbd45a72
FG
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
9765092e 46 pub ACL_PATH_REGEX = concat!(r"^(?:/|", r"(?:/", PROXMOX_SAFE_ID_REGEX_STR!(), ")+", r")$");
9069debc 47
926d2531
TL
48 pub SUBSCRIPTION_KEY_REGEX = concat!(r"^pbs(?:[cbsp])-[0-9a-f]{10}$");
49
9069debc 50 pub BLOCKDEVICE_NAME_REGEX = r"^(:?(:?h|s|x?v)d[a-z]+)|(:?nvme\d+n\d+)$";
7957fabf
DC
51
52 pub ZPOOL_NAME_REGEX = r"^[a-zA-Z][a-z0-9A-Z\-_.:]+$";
bc228e5e 53
4c4e5c2b 54 pub DATASTORE_MAP_REGEX = concat!(r"(:?", PROXMOX_SAFE_ID_REGEX_STR!(), r"=)?", PROXMOX_SAFE_ID_REGEX_STR!());
5e42d385
DC
55
56 pub TAPE_RESTORE_SNAPSHOT_REGEX = concat!(r"^", PROXMOX_SAFE_ID_REGEX_STR!(), r":", SNAPSHOT_PATH_REGEX_STR!(), r"$");
255f378a 57}
4ebf0eab 58
255f378a
DM
59pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat =
60 ApiStringFormat::Pattern(&SYSTEMD_DATETIME_REGEX);
4ebf0eab 61
b25f313d
DM
62pub const HOSTNAME_FORMAT: ApiStringFormat =
63 ApiStringFormat::Pattern(&HOSTNAME_REGEX);
64
65pub const DNS_NAME_FORMAT: ApiStringFormat =
66 ApiStringFormat::Pattern(&DNS_NAME_REGEX);
67
d724116c
WB
68pub const DNS_ALIAS_FORMAT: ApiStringFormat =
69 ApiStringFormat::Pattern(&DNS_ALIAS_REGEX);
70
b25f313d
DM
71pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat =
72 ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX);
73
ed3e60ae
DM
74pub const ACL_PATH_FORMAT: ApiStringFormat =
75 ApiStringFormat::Pattern(&ACL_PATH_REGEX);
76
68da20bf
DM
77pub const NETWORK_INTERFACE_FORMAT: ApiStringFormat =
78 ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
454c13ed 79
926d2531
TL
80pub const SUBSCRIPTION_KEY_FORMAT: ApiStringFormat =
81 ApiStringFormat::Pattern(&SUBSCRIPTION_KEY_REGEX);
82
9069debc
DM
83pub const BLOCKDEVICE_NAME_FORMAT: ApiStringFormat =
84 ApiStringFormat::Pattern(&BLOCKDEVICE_NAME_REGEX);
76cf5208 85
4c4e5c2b
DC
86pub const DATASTORE_MAP_FORMAT: ApiStringFormat =
87 ApiStringFormat::Pattern(&DATASTORE_MAP_REGEX);
88
5e42d385
DC
89pub const TAPE_RESTORE_SNAPSHOT_FORMAT: ApiStringFormat =
90 ApiStringFormat::Pattern(&TAPE_RESTORE_SNAPSHOT_REGEX);
91
685e1334
DM
92pub const PASSWORD_SCHEMA: Schema = StringSchema::new("Password.")
93 .format(&PASSWORD_FORMAT)
94 .min_length(1)
b88f9c5b 95 .max_length(1024)
685e1334
DM
96 .schema();
97
98pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.")
99 .format(&PASSWORD_FORMAT)
100 .min_length(5)
101 .max_length(64)
102 .schema();
dcb8db66 103
d5a48b5c
DM
104pub const TAPE_ENCRYPTION_KEY_FINGERPRINT_SCHEMA: Schema = StringSchema::new(
105 "Tape encryption key fingerprint (sha256)."
255f378a 106)
d5a48b5c 107 .format(&FINGERPRINT_SHA256_FORMAT)
255f378a
DM
108 .schema();
109
255f378a
DM
110pub const CHUNK_DIGEST_SCHEMA: Schema = StringSchema::new("Chunk digest (SHA256).")
111 .format(&CHUNK_DIGEST_FORMAT)
112 .schema();
113
114pub 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
124pub const SEARCH_DOMAIN_SCHEMA: Schema =
125 StringSchema::new("Search domain for host-name lookup.").schema();
126
127pub const FIRST_DNS_SERVER_SCHEMA: Schema =
128 StringSchema::new("First name server IP address.")
129 .format(&IP_FORMAT)
130 .schema();
131
132pub const SECOND_DNS_SERVER_SCHEMA: Schema =
133 StringSchema::new("Second name server IP address.")
134 .format(&IP_FORMAT)
135 .schema();
136
137pub const THIRD_DNS_SERVER_SCHEMA: Schema =
138 StringSchema::new("Third name server IP address.")
139 .format(&IP_FORMAT)
140 .schema();
141
76cf5208
DM
142pub const IP_V4_SCHEMA: Schema =
143 StringSchema::new("IPv4 address.")
144 .format(&IP_V4_FORMAT)
145 .max_length(15)
146 .schema();
147
148pub const IP_V6_SCHEMA: Schema =
149 StringSchema::new("IPv6 address.")
150 .format(&IP_V6_FORMAT)
151 .max_length(39)
152 .schema();
153
154pub const IP_SCHEMA: Schema =
155 StringSchema::new("IP (IPv4 or IPv6) address.")
156 .format(&IP_FORMAT)
157 .max_length(39)
158 .schema();
159
160pub 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
166pub 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
172pub 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
4b40148c
DM
178pub 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
ca257c80
DM
185pub 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
192pub const ACL_PROPAGATE_SCHEMA: Schema = BooleanSchema::new(
193 "Allow to propagate (inherit) permissions.")
194 .default(true)
195 .schema();
196
197pub const ACL_UGID_TYPE_SCHEMA: Schema = StringSchema::new(
198 "Type of 'ugid' property.")
ca257c80 199 .format(&ApiStringFormat::Enum(&[
bc0d0388
DM
200 EnumEntry::new("user", "User"),
201 EnumEntry::new("group", "Group")]))
ca257c80
DM
202 .schema();
203
bf78f708
DM
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.
226pub 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
5830c205
DM
234pub const UPID_SCHEMA: Schema = StringSchema::new("Unique Process/Task ID.")
235 .max_length(256)
236 .schema();
66c49c21 237
4c4e5c2b
DC
238pub const DATASTORE_MAP_SCHEMA: Schema = StringSchema::new("Datastore mapping.")
239 .format(&DATASTORE_MAP_FORMAT)
240 .min_length(3)
241 .max_length(65)
93fb2e0d 242 .type_text("(<source>=)?<target>")
4c4e5c2b
DC
243 .schema();
244
245pub const DATASTORE_MAP_ARRAY_SCHEMA: Schema = ArraySchema::new(
246 "Datastore mapping list.", &DATASTORE_MAP_SCHEMA)
247 .schema();
248
249pub const DATASTORE_MAP_LIST_SCHEMA: Schema = StringSchema::new(
e244b9d0
DC
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.")
4c4e5c2b
DC
254 .format(&ApiStringFormat::PropertyString(&DATASTORE_MAP_ARRAY_SCHEMA))
255 .schema();
256
5e42d385
DC
257pub 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
bc228e5e 263pub const MEDIA_SET_UUID_SCHEMA: Schema =
f490dda0 264 StringSchema::new("MediaSet Uuid (We use the all-zero Uuid to reseve an empty media for a specific pool).")
bc228e5e
DM
265 .format(&UUID_FORMAT)
266 .schema();
267
268pub const MEDIA_UUID_SCHEMA: Schema =
269 StringSchema::new("Media Uuid.")
270 .format(&UUID_FORMAT)
271 .schema();
272
2888b27f
DC
273pub 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))
8f02db04 276 .type_text("<calendar-event>")
2888b27f
DC
277 .schema();
278
42fdbe51
DM
279pub 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))
8f02db04 282 .type_text("<calendar-event>")
42fdbe51
DM
283 .schema();
284
67f7ffd0
DM
285pub 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))
8f02db04 288 .type_text("<calendar-event>")
67f7ffd0
DM
289 .schema();
290
78efafc2 291pub const VERIFICATION_SCHEDULE_SCHEMA: Schema = StringSchema::new(
f37ef25b
HL
292 "Run verify job at specified schedule.")
293 .format(&ApiStringFormat::VerifyFn(crate::tools::systemd::time::verify_calendar_event))
8f02db04 294 .type_text("<calendar-event>")
f37ef25b
HL
295 .schema();
296
167971ed
DM
297pub 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
b4900286
DM
303pub 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
309pub 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
9b2bad7a
HL
314pub 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
319pub const VERIFICATION_OUTDATED_AFTER_SCHEMA: Schema = IntegerSchema::new(
320 "Days after that a verification becomes outdated")
321 .minimum(1)
322 .schema();
323
b25f313d
DM
324pub const HOSTNAME_SCHEMA: Schema = StringSchema::new("Hostname (as defined in RFC1123).")
325 .format(&HOSTNAME_FORMAT)
326 .schema();
327
328pub const DNS_NAME_OR_IP_SCHEMA: Schema = StringSchema::new("DNS name or IP address.")
329 .format(&DNS_NAME_OR_IP_FORMAT)
330 .schema();
331
926d2531
TL
332pub 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
9069debc
DM
338pub 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();
fc189b19 343
2165f0d4
DM
344pub 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
fc189b19
DM
350// Complex type definitions
351
14e08625
DC
352#[api(
353 properties: {
98afc7b1
FG
354 "gc-status": {
355 type: GarbageCollectionStatus,
356 optional: true,
357 },
358 counts: {
359 type: Counts,
360 optional: true,
361 },
14e08625
DC
362 },
363)]
364#[derive(Serialize, Deserialize)]
365#[serde(rename_all="kebab-case")]
366/// Overall Datastore status and useful information.
367pub 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
98afc7b1
FG
375 #[serde(skip_serializing_if="Option::is_none")]
376 pub gc_status: Option<GarbageCollectionStatus>,
14e08625 377 /// Group/Snapshot counts
98afc7b1
FG
378 #[serde(skip_serializing_if="Option::is_none")]
379 pub counts: Option<Counts>,
14e08625
DC
380}
381
99384f79
DM
382#[api(
383 properties: {
e7cb4dc5 384 upid: { schema: UPID_SCHEMA },
16245d54 385 user: { type: Authid },
99384f79
DM
386 },
387)]
388#[derive(Serialize, Deserialize)]
389/// Task properties.
390pub 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>,
e6dc35ac 404 /// The authenticated entity who started the task
16245d54 405 pub user: Authid,
99384f79
DM
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
df528ee6
DC
414impl From<crate::server::TaskListInfo> for TaskListItem {
415 fn from(info: crate::server::TaskListInfo) -> Self {
416 let (endtime, status) = info
417 .state
77bd2a46 418 .map_or_else(|| (None, None), |a| (Some(a.endtime()), Some(a.to_string())));
df528ee6
DC
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,
16245d54 428 user: info.upid.auth_id,
df528ee6
DC
429 endtime,
430 status,
431 }
432 }
433}
434
5976c392
DC
435#[api()]
436#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
437#[serde(rename_all = "lowercase")]
438pub enum TaskStateType {
439 /// Ok
440 OK,
441 /// Warning
442 Warning,
443 /// Error
444 Error,
445 /// Unknown
446 Unknown,
447}
448
ed751dc2
DM
449#[api()]
450#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
451#[serde(rename_all = "lowercase")]
452/// Node Power command type.
453pub enum NodePowerCommand {
454 /// Restart the server
455 Reboot,
456 /// Shutdown the server
457 Shutdown,
458}
459
c357260d
DM
460#[api()]
461#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
462#[serde(rename_all = "lowercase")]
463/// Interface configuration method
464pub 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
bab5d18c
DM
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
481pub 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
8f2f3dd7 491 #[serde(rename = "802.3ad")]
bab5d18c
DM
492 ieee802_3ad = 4,
493 /// Adaptive transmit load balancing
494 balance_tlb = 5,
495 /// Adaptive load balancing
496 balance_alb = 6,
497}
498
8f2f3dd7
DC
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)
505pub 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
02269f3d
DM
516#[api()]
517#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
518#[serde(rename_all = "lowercase")]
519/// Network interface type
520pub enum NetworkInterfaceType {
521 /// Loopback
522 Loopback,
523 /// Physical Ethernet device
7b22acd0 524 Eth,
02269f3d
DM
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
68da20bf
DM
537pub 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
3aedb738 543pub const NETWORK_INTERFACE_ARRAY_SCHEMA: Schema = ArraySchema::new(
1d9a68c2
DM
544 "Network interface list.", &NETWORK_INTERFACE_NAME_SCHEMA)
545 .schema();
546
3aedb738
DM
547pub 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
c357260d
DM
552#[api(
553 properties: {
554 name: {
68da20bf 555 schema: NETWORK_INTERFACE_NAME_SCHEMA,
c357260d 556 },
7b22acd0 557 "type": {
02269f3d
DM
558 type: NetworkInterfaceType,
559 },
7b22acd0 560 method: {
c357260d
DM
561 type: NetworkConfigMethod,
562 optional: true,
563 },
7b22acd0 564 method6: {
c357260d
DM
565 type: NetworkConfigMethod,
566 optional: true,
567 },
7b22acd0
DM
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: {
c357260d
DM
585 description: "Option list (inet)",
586 type: Array,
587 items: {
68da20bf 588 description: "Optional attribute line.",
c357260d
DM
589 type: String,
590 },
591 },
7b22acd0 592 options6: {
c357260d
DM
593 description: "Option list (inet6)",
594 type: Array,
595 items: {
68da20bf 596 description: "Optional attribute line.",
c357260d
DM
597 type: String,
598 },
599 },
7b22acd0 600 comments: {
8a6b86b8
DM
601 description: "Comments (inet, may span multiple lines)",
602 type: String,
603 optional: true,
5f60a58f 604 },
7b22acd0 605 comments6: {
8a6b86b8
DM
606 description: "Comments (inet6, may span multiple lines)",
607 type: String,
608 optional: true,
5f60a58f 609 },
1d9a68c2 610 bridge_ports: {
3aedb738 611 schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
1d9a68c2
DM
612 optional: true,
613 },
bab5d18c 614 slaves: {
3aedb738 615 schema: NETWORK_INTERFACE_ARRAY_SCHEMA,
42fbe91a
DM
616 optional: true,
617 },
bab5d18c
DM
618 bond_mode: {
619 type: LinuxBondMode,
620 optional: true,
85959a99
DC
621 },
622 "bond-primary": {
623 schema: NETWORK_INTERFACE_NAME_SCHEMA,
624 optional: true,
625 },
8f2f3dd7
DC
626 bond_xmit_hash_policy: {
627 type: BondXmitHashPolicy,
628 optional: true,
629 },
c357260d
DM
630 }
631)]
632#[derive(Debug, Serialize, Deserialize)]
633/// Network Interface configuration
634pub struct Interface {
635 /// Autostart interface
7b22acd0
DM
636 #[serde(rename = "autostart")]
637 pub autostart: bool,
c357260d
DM
638 /// Interface is active (UP)
639 pub active: bool,
640 /// Interface name
641 pub name: String,
02269f3d 642 /// Interface type
7b22acd0 643 #[serde(rename = "type")]
02269f3d 644 pub interface_type: NetworkInterfaceType,
c357260d 645 #[serde(skip_serializing_if="Option::is_none")]
7b22acd0 646 pub method: Option<NetworkConfigMethod>,
c357260d 647 #[serde(skip_serializing_if="Option::is_none")]
7b22acd0 648 pub method6: Option<NetworkConfigMethod>,
c357260d 649 #[serde(skip_serializing_if="Option::is_none")]
8b57cd44 650 /// IPv4 address with netmask
7b22acd0 651 pub cidr: Option<String>,
c357260d
DM
652 #[serde(skip_serializing_if="Option::is_none")]
653 /// IPv4 gateway
7b22acd0 654 pub gateway: Option<String>,
c357260d 655 #[serde(skip_serializing_if="Option::is_none")]
8b57cd44 656 /// IPv6 address with netmask
7b22acd0 657 pub cidr6: Option<String>,
c357260d
DM
658 #[serde(skip_serializing_if="Option::is_none")]
659 /// IPv6 gateway
7b22acd0 660 pub gateway6: Option<String>,
3fce3bc3 661
c357260d 662 #[serde(skip_serializing_if="Vec::is_empty")]
7b22acd0 663 pub options: Vec<String>,
c357260d 664 #[serde(skip_serializing_if="Vec::is_empty")]
7b22acd0 665 pub options6: Vec<String>,
2c18efd9 666
8a6b86b8 667 #[serde(skip_serializing_if="Option::is_none")]
7b22acd0 668 pub comments: Option<String>,
8a6b86b8 669 #[serde(skip_serializing_if="Option::is_none")]
7b22acd0 670 pub comments6: Option<String>,
5f60a58f 671
2c18efd9
DM
672 #[serde(skip_serializing_if="Option::is_none")]
673 /// Maximum Transmission Unit
674 pub mtu: Option<u64>,
1d9a68c2
DM
675
676 #[serde(skip_serializing_if="Option::is_none")]
677 pub bridge_ports: Option<Vec<String>>,
7b22acd0
DM
678 /// Enable bridge vlan support.
679 #[serde(skip_serializing_if="Option::is_none")]
680 pub bridge_vlan_aware: Option<bool>,
42fbe91a
DM
681
682 #[serde(skip_serializing_if="Option::is_none")]
bab5d18c
DM
683 pub slaves: Option<Vec<String>>,
684 #[serde(skip_serializing_if="Option::is_none")]
685 pub bond_mode: Option<LinuxBondMode>,
85959a99
DC
686 #[serde(skip_serializing_if="Option::is_none")]
687 #[serde(rename = "bond-primary")]
688 pub bond_primary: Option<String>,
8f2f3dd7 689 pub bond_xmit_hash_policy: Option<BondXmitHashPolicy>,
c357260d
DM
690}
691
ff620a3d
DM
692// Regression tests
693
dcb8db66 694#[test]
ff329f97 695fn test_cert_fingerprint_schema() -> Result<(), anyhow::Error> {
dcb8db66
DM
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() {
3984a5fd 709 if parse_simple_value(fingerprint, &schema).is_ok() {
add5861e 710 bail!("test fingerprint '{}' failed - got Ok() while exception an error.", fingerprint);
dcb8db66
DM
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
ff620a3d 735#[test]
ff329f97 736fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
ff620a3d
DM
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() {
3984a5fd 750 if parse_simple_value(name, &Userid::API_SCHEMA).is_ok() {
add5861e 751 bail!("test userid '{}' failed - got Ok() while exception an error.", name);
ff620a3d
DM
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() {
e7cb4dc5 764 let v = match parse_simple_value(name, &Userid::API_SCHEMA) {
ff620a3d
DM
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}
a2f862ee
DM
778
779#[api()]
780#[derive(Copy, Clone, Serialize, Deserialize)]
781#[serde(rename_all = "UPPERCASE")]
782pub 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")]
794pub 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}
a4e86972
SR
806
807#[api()]
ed2beb33 808#[derive(Debug, Clone, Serialize, Deserialize)]
a4e86972
SR
809#[serde(rename_all = "PascalCase")]
810/// Describes a package for which an update is available.
811pub 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,
6f0073bb
TL
832 /// Custom extra field for additional package information
833 #[serde(skip_serializing_if="Option::is_none")]
2decf85d 834 pub extra_info: Option<String>,
a4e86972 835}
6e545d00
DM
836
837#[api()]
838#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
839#[serde(rename_all = "lowercase")]
840/// When do we send notifications
841pub enum Notify {
842 /// Never send notification
843 Never,
d1d74c43 844 /// Send notifications for failed and successful jobs
6e545d00
DM
845 Always,
846 /// Send notifications for failed jobs only
847 Error,
848}
c26c9390
DM
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
868pub 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
227501c0
DC
877/// An entry in a hierarchy of files for restore and listing.
878#[api()]
879#[derive(Serialize, Deserialize)]
880pub 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
898impl ArchiveEntry {
4d0dc299 899 pub fn new(filepath: &[u8], entry_type: Option<&DirEntryAttribute>) -> Self {
6a59fa0e
SR
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 {
227501c0
DC
912 Self {
913 filepath: base64::encode(filepath),
914 text: String::from_utf8_lossy(filepath.split(|x| *x == b'/').last().unwrap())
915 .to_string(),
4d0dc299
SR
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 { .. })),
6a59fa0e 921 size,
227501c0 922 mtime: match entry_type {
4d0dc299 923 Some(DirEntryAttribute::File { mtime, .. }) => Some(*mtime),
6a59fa0e 924 _ => None,
227501c0
DC
925 },
926 }
927 }
928}
929
c26c9390
DM
930pub const DATASTORE_NOTIFY_STRING_SCHEMA: Schema = StringSchema::new(
931 "Datastore notification setting")
932 .format(&ApiStringFormat::PropertyString(&DatastoreNotify::API_SCHEMA))
933 .schema();
82a103c8
DM
934
935
936pub const PASSWORD_HINT_SCHEMA: Schema = StringSchema::new("Password hint.")
937 .format(&SINGLE_LINE_COMMENT_FORMAT)
938 .min_length(1)
939 .max_length(64)
940 .schema();
69b8bc3b 941
42c0f784
FG
942#[api]
943#[derive(Deserialize, Serialize)]
944/// RSA public key information
945pub struct RsaPubKeyInfo {
946 /// Path to key (if stored in a file)
947 #[serde(skip_serializing_if="Option::is_none")]
948 pub path: Option<String>,
949 /// RSA exponent
950 pub exponent: String,
951 /// Hex-encoded RSA modulus
952 pub modulus: String,
953 /// Key (modulus) length in bits
954 pub length: usize,
955}
956
957impl std::convert::TryFrom<openssl::rsa::Rsa<openssl::pkey::Public>> for RsaPubKeyInfo {
958 type Error = anyhow::Error;
959
960 fn try_from(value: openssl::rsa::Rsa<openssl::pkey::Public>) -> Result<Self, Self::Error> {
961 let modulus = value.n().to_hex_str()?.to_string();
962 let exponent = value.e().to_dec_str()?.to_string();
963 let length = value.size() as usize * 8;
964
965 Ok(Self {
966 path: None,
967 exponent,
968 modulus,
969 length,
970 })
971 }
972}
1689296d
DM
973
974#[api(
975 properties: {
976 "next-run": {
977 description: "Estimated time of the next run (UNIX epoch).",
978 optional: true,
979 type: Integer,
980 },
981 "last-run-state": {
982 description: "Result of the last run.",
983 optional: true,
984 type: String,
985 },
986 "last-run-upid": {
987 description: "Task UPID of the last run.",
988 optional: true,
989 type: String,
990 },
991 "last-run-endtime": {
992 description: "Endtime of the last run.",
993 optional: true,
994 type: Integer,
995 },
996 }
997)]
1689296d 998#[derive(Serialize,Deserialize,Default)]
3e3b505c 999#[serde(rename_all="kebab-case")]
1689296d
DM
1000/// Job Scheduling Status
1001pub struct JobScheduleStatus {
1002 #[serde(skip_serializing_if="Option::is_none")]
1003 pub next_run: Option<i64>,
1004 #[serde(skip_serializing_if="Option::is_none")]
1005 pub last_run_state: Option<String>,
1006 #[serde(skip_serializing_if="Option::is_none")]
1007 pub last_run_upid: Option<String>,
1008 #[serde(skip_serializing_if="Option::is_none")]
1009 pub last_run_endtime: Option<i64>,
1010}
75054859
DC
1011
1012#[api]
1013#[derive(Serialize, Deserialize, Default)]
1014#[serde(rename_all = "kebab-case")]
1015/// Node memory usage counters
1016pub struct NodeMemoryCounters {
1017 /// Total memory
1018 pub total: u64,
1019 /// Used memory
1020 pub used: u64,
1021 /// Free memory
1022 pub free: u64,
1023}
1024
398636b6
DC
1025#[api]
1026#[derive(Serialize, Deserialize, Default)]
1027#[serde(rename_all = "kebab-case")]
1028/// Node swap usage counters
1029pub struct NodeSwapCounters {
1030 /// Total swap
1031 pub total: u64,
1032 /// Used swap
1033 pub used: u64,
1034 /// Free swap
1035 pub free: u64,
1036}
1037
75054859
DC
1038#[api]
1039#[derive(Serialize,Deserialize,Default)]
1040#[serde(rename_all = "kebab-case")]
1041/// Contains general node information such as the fingerprint`
1042pub struct NodeInformation {
1043 /// The SSL Fingerprint
1044 pub fingerprint: String,
1045}
1046
398636b6
DC
1047#[api]
1048#[derive(Serialize, Deserialize, Default)]
1049#[serde(rename_all = "kebab-case")]
1050/// Information about the CPU
1051pub struct NodeCpuInformation {
1052 /// The CPU model
1053 pub model: String,
1054 /// The number of CPU sockets
1055 pub sockets: usize,
1056 /// The number of CPU cores (incl. threads)
1057 pub cpus: usize,
1058}
1059
75054859
DC
1060#[api(
1061 properties: {
1062 memory: {
1063 type: NodeMemoryCounters,
1064 },
1065 root: {
1066 type: StorageStatus,
1067 },
398636b6
DC
1068 swap: {
1069 type: NodeSwapCounters,
1070 },
1071 loadavg: {
1072 type: Array,
1073 items: {
1074 type: Number,
1075 description: "the load",
1076 }
1077 },
1078 cpuinfo: {
1079 type: NodeCpuInformation,
1080 },
75054859
DC
1081 info: {
1082 type: NodeInformation,
1083 }
1084 },
1085)]
1086#[derive(Serialize, Deserialize, Default)]
1087#[serde(rename_all = "kebab-case")]
1088/// The Node status
1089pub struct NodeStatus {
1090 pub memory: NodeMemoryCounters,
1091 pub root: StorageStatus,
398636b6
DC
1092 pub swap: NodeSwapCounters,
1093 /// The current uptime of the server.
1094 pub uptime: u64,
1095 /// Load for 1, 5 and 15 minutes.
1096 pub loadavg: [f64; 3],
1097 /// The current kernel version.
1098 pub kversion: String,
75054859
DC
1099 /// Total CPU usage since last query.
1100 pub cpu: f64,
398636b6
DC
1101 /// Total IO wait since last query.
1102 pub wait: f64,
1103 pub cpuinfo: NodeCpuInformation,
75054859
DC
1104 pub info: NodeInformation,
1105}
467bd01c
DM
1106
1107pub const HTTP_PROXY_SCHEMA: Schema = StringSchema::new(
1108 "HTTP proxy configuration [http://]<host>[:port]")
1109 .format(&ApiStringFormat::VerifyFn(|s| {
1d781c5b 1110 proxmox_http::ProxyConfig::parse_proxy_url(s)?;
467bd01c
DM
1111 Ok(())
1112 }))
1113 .min_length(1)
1114 .max_length(128)
1115 .type_text("[http://]<host>[:port]")
1116 .schema();