]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/types.rs
administration-guide.rst: update snapshot list output
[proxmox-backup.git] / src / api2 / types.rs
CommitLineData
f7d4e4b5 1use anyhow::{bail};
fc189b19 2use ::serde::{Deserialize, Serialize};
4ebf0eab 3
9ea4bce4
WB
4use proxmox::api::{api, schema::*};
5use proxmox::const_regex;
6use proxmox::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32};
255f378a
DM
7
8// File names: may not contain slashes, may not start with "."
9pub const FILENAME_FORMAT: ApiStringFormat = ApiStringFormat::VerifyFn(|name| {
10 if name.starts_with('.') {
11 bail!("file names may not start with '.'");
12 }
13 if name.contains('/') {
14 bail!("file names may not contain slashes");
15 }
16 Ok(())
17});
18
b25f313d
DM
19macro_rules! DNS_LABEL { () => (r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") }
20macro_rules! DNS_NAME { () => (concat!(r"(?:", DNS_LABEL!() , r"\.)*", DNS_LABEL!())) }
255f378a 21
163dc16c
DM
22// we only allow a limited set of characters
23// colon is not allowed, because we store usernames in
24// colon separated lists)!
25// slash is not allowed because it is used as pve API delimiter
26// also see "man useradd"
ae62c4fe 27macro_rules! USER_NAME_REGEX_STR { () => (r"(?:[^\s:/[:cntrl:]]+)") }
9765092e 28macro_rules! GROUP_NAME_REGEX_STR { () => (USER_NAME_REGEX_STR!()) }
163dc16c
DM
29
30macro_rules! PROXMOX_SAFE_ID_REGEX_STR { () => (r"(?:[A-Za-z0-9_][A-Za-z0-9._\-]*)") }
31
76cf5208
DM
32macro_rules! CIDR_V4_REGEX_STR { () => (concat!(r"(?:", IPV4RE!(), r"/\d{1,2})$")) }
33macro_rules! CIDR_V6_REGEX_STR { () => (concat!(r"(?:", IPV6RE!(), r"/\d{1,3})$")) }
34
255f378a 35const_regex!{
76cf5208
DM
36 pub IP_V4_REGEX = concat!(r"^", IPV4RE!(), r"$");
37 pub IP_V6_REGEX = concat!(r"^", IPV6RE!(), r"$");
38 pub IP_REGEX = concat!(r"^", IPRE!(), r"$");
39 pub CIDR_V4_REGEX = concat!(r"^", CIDR_V4_REGEX_STR!(), r"$");
40 pub CIDR_V6_REGEX = concat!(r"^", CIDR_V6_REGEX_STR!(), r"$");
41 pub CIDR_REGEX = concat!(r"^(?:", CIDR_V4_REGEX_STR!(), "|", CIDR_V6_REGEX_STR!(), r")$");
42
255f378a
DM
43 pub SHA256_HEX_REGEX = r"^[a-f0-9]{64}$"; // fixme: define in common_regex ?
44 pub SYSTEMD_DATETIME_REGEX = r"^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$"; // fixme: define in common_regex ?
d0adf270 45
da4a15a3
DM
46 pub PASSWORD_REGEX = r"^[[:^cntrl:]]*$"; // everything but control characters
47
d0adf270
DM
48 /// Regex for safe identifiers.
49 ///
50 /// This
51 /// [article](https://dwheeler.com/essays/fixing-unix-linux-filenames.html)
52 /// contains further information why it is reasonable to restict
53 /// names this way. This is not only useful for filenames, but for
54 /// any identifier command line tools work with.
163dc16c 55 pub PROXMOX_SAFE_ID_REGEX = concat!(r"^", PROXMOX_SAFE_ID_REGEX_STR!(), r"$");
454c13ed
DM
56
57 pub SINGLE_LINE_COMMENT_REGEX = r"^[[:^cntrl:]]*$";
b25f313d
DM
58
59 pub HOSTNAME_REGEX = r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)$";
60
ae62c4fe 61 pub DNS_NAME_REGEX = concat!(r"^", DNS_NAME!(), r"$");
b25f313d 62
ae62c4fe 63 pub DNS_NAME_OR_IP_REGEX = concat!(r"^", DNS_NAME!(), "|", IPRE!(), r"$");
163dc16c
DM
64
65 pub PROXMOX_USER_ID_REGEX = concat!(r"^", USER_NAME_REGEX_STR!(), r"@", PROXMOX_SAFE_ID_REGEX_STR!(), r"$");
dcb8db66 66
9765092e
DM
67 pub PROXMOX_GROUP_ID_REGEX = concat!(r"^", GROUP_NAME_REGEX_STR!(), r"$");
68
dcb8db66 69 pub CERT_FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$";
ed3e60ae 70
9765092e 71 pub ACL_PATH_REGEX = concat!(r"^(?:/|", r"(?:/", PROXMOX_SAFE_ID_REGEX_STR!(), ")+", r")$");
255f378a 72}
4ebf0eab 73
255f378a
DM
74pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat =
75 ApiStringFormat::Pattern(&SYSTEMD_DATETIME_REGEX);
4ebf0eab 76
76cf5208
DM
77pub const IP_V4_FORMAT: ApiStringFormat =
78 ApiStringFormat::Pattern(&IP_V4_REGEX);
79
80pub const IP_V6_FORMAT: ApiStringFormat =
81 ApiStringFormat::Pattern(&IP_V6_REGEX);
82
255f378a 83pub const IP_FORMAT: ApiStringFormat =
76cf5208 84 ApiStringFormat::Pattern(&IP_REGEX);
bbf9e7e9 85
255f378a
DM
86pub const PVE_CONFIG_DIGEST_FORMAT: ApiStringFormat =
87 ApiStringFormat::Pattern(&SHA256_HEX_REGEX);
88
dcb8db66
DM
89pub const CERT_FINGERPRINT_SHA256_FORMAT: ApiStringFormat =
90 ApiStringFormat::Pattern(&CERT_FINGERPRINT_SHA256_REGEX);
91
d0adf270
DM
92pub const PROXMOX_SAFE_ID_FORMAT: ApiStringFormat =
93 ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
94
454c13ed
DM
95pub const SINGLE_LINE_COMMENT_FORMAT: ApiStringFormat =
96 ApiStringFormat::Pattern(&SINGLE_LINE_COMMENT_REGEX);
97
b25f313d
DM
98pub const HOSTNAME_FORMAT: ApiStringFormat =
99 ApiStringFormat::Pattern(&HOSTNAME_REGEX);
100
101pub const DNS_NAME_FORMAT: ApiStringFormat =
102 ApiStringFormat::Pattern(&DNS_NAME_REGEX);
103
104pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat =
105 ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX);
106
163dc16c
DM
107pub const PROXMOX_USER_ID_FORMAT: ApiStringFormat =
108 ApiStringFormat::Pattern(&PROXMOX_USER_ID_REGEX);
109
9765092e
DM
110pub const PROXMOX_GROUP_ID_FORMAT: ApiStringFormat =
111 ApiStringFormat::Pattern(&PROXMOX_GROUP_ID_REGEX);
112
7e7b781a
DM
113pub const PASSWORD_FORMAT: ApiStringFormat =
114 ApiStringFormat::Pattern(&PASSWORD_REGEX);
115
ed3e60ae
DM
116pub const ACL_PATH_FORMAT: ApiStringFormat =
117 ApiStringFormat::Pattern(&ACL_PATH_REGEX);
118
68da20bf
DM
119pub const NETWORK_INTERFACE_FORMAT: ApiStringFormat =
120 ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
454c13ed 121
76cf5208
DM
122pub const CIDR_V4_FORMAT: ApiStringFormat =
123 ApiStringFormat::Pattern(&CIDR_V4_REGEX);
124
125pub const CIDR_V6_FORMAT: ApiStringFormat =
126 ApiStringFormat::Pattern(&CIDR_V6_REGEX);
127
128pub const CIDR_FORMAT: ApiStringFormat =
129 ApiStringFormat::Pattern(&CIDR_REGEX);
130
131
685e1334
DM
132pub const PASSWORD_SCHEMA: Schema = StringSchema::new("Password.")
133 .format(&PASSWORD_FORMAT)
134 .min_length(1)
b88f9c5b 135 .max_length(1024)
685e1334
DM
136 .schema();
137
138pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.")
139 .format(&PASSWORD_FORMAT)
140 .min_length(5)
141 .max_length(64)
142 .schema();
dcb8db66
DM
143
144pub const CERT_FINGERPRINT_SHA256_SCHEMA: Schema = StringSchema::new(
145 "X509 certificate fingerprint (sha256)."
146)
147 .format(&CERT_FINGERPRINT_SHA256_FORMAT)
148 .schema();
149
002a191a 150pub const PROXMOX_CONFIG_DIGEST_SCHEMA: Schema = StringSchema::new(r#"\
255f378a
DM
151Prevent changes if current configuration file has different SHA256 digest.
152This can be used to prevent concurrent modifications.
153"#
154)
155 .format(&PVE_CONFIG_DIGEST_FORMAT)
156 .schema();
157
158
159pub const CHUNK_DIGEST_FORMAT: ApiStringFormat =
160 ApiStringFormat::Pattern(&SHA256_HEX_REGEX);
161
162pub const CHUNK_DIGEST_SCHEMA: Schema = StringSchema::new("Chunk digest (SHA256).")
163 .format(&CHUNK_DIGEST_FORMAT)
164 .schema();
165
166pub 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
176pub const SEARCH_DOMAIN_SCHEMA: Schema =
177 StringSchema::new("Search domain for host-name lookup.").schema();
178
179pub const FIRST_DNS_SERVER_SCHEMA: Schema =
180 StringSchema::new("First name server IP address.")
181 .format(&IP_FORMAT)
182 .schema();
183
184pub const SECOND_DNS_SERVER_SCHEMA: Schema =
185 StringSchema::new("Second name server IP address.")
186 .format(&IP_FORMAT)
187 .schema();
188
189pub const THIRD_DNS_SERVER_SCHEMA: Schema =
190 StringSchema::new("Third name server IP address.")
191 .format(&IP_FORMAT)
192 .schema();
193
76cf5208
DM
194pub const IP_V4_SCHEMA: Schema =
195 StringSchema::new("IPv4 address.")
196 .format(&IP_V4_FORMAT)
197 .max_length(15)
198 .schema();
199
200pub const IP_V6_SCHEMA: Schema =
201 StringSchema::new("IPv6 address.")
202 .format(&IP_V6_FORMAT)
203 .max_length(39)
204 .schema();
205
206pub const IP_SCHEMA: Schema =
207 StringSchema::new("IP (IPv4 or IPv6) address.")
208 .format(&IP_FORMAT)
209 .max_length(39)
210 .schema();
211
212pub 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
218pub 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
224pub 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
4b40148c
DM
230pub 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
ca257c80
DM
237pub 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
244pub const ACL_PROPAGATE_SCHEMA: Schema = BooleanSchema::new(
245 "Allow to propagate (inherit) permissions.")
246 .default(true)
247 .schema();
248
249pub const ACL_UGID_TYPE_SCHEMA: Schema = StringSchema::new(
250 "Type of 'ugid' property.")
ca257c80 251 .format(&ApiStringFormat::Enum(&[
bc0d0388
DM
252 EnumEntry::new("user", "User"),
253 EnumEntry::new("group", "Group")]))
ca257c80
DM
254 .schema();
255
255f378a
DM
256pub const BACKUP_ARCHIVE_NAME_SCHEMA: Schema =
257 StringSchema::new("Backup archive name.")
1ae5677d 258 .format(&PROXMOX_SAFE_ID_FORMAT)
255f378a
DM
259 .schema();
260
261pub const BACKUP_TYPE_SCHEMA: Schema =
262 StringSchema::new("Backup type.")
bc0d0388
DM
263 .format(&ApiStringFormat::Enum(&[
264 EnumEntry::new("vm", "Virtual Machine Backup"),
265 EnumEntry::new("ct", "Container Backup"),
266 EnumEntry::new("host", "Host Backup")]))
255f378a
DM
267 .schema();
268
269pub const BACKUP_ID_SCHEMA: Schema =
270 StringSchema::new("Backup ID.")
1ae5677d 271 .format(&PROXMOX_SAFE_ID_FORMAT)
255f378a
DM
272 .schema();
273
274pub const BACKUP_TIME_SCHEMA: Schema =
275 IntegerSchema::new("Backup time (Unix epoch.)")
276 .minimum(1_547_797_308)
277 .schema();
5830c205
DM
278
279pub const UPID_SCHEMA: Schema = StringSchema::new("Unique Process/Task ID.")
280 .max_length(256)
281 .schema();
66c49c21
DM
282
283pub const DATASTORE_SCHEMA: Schema = StringSchema::new("Datastore name.")
d0adf270 284 .format(&PROXMOX_SAFE_ID_FORMAT)
688fbe07 285 .min_length(3)
66c49c21
DM
286 .max_length(32)
287 .schema();
fc189b19 288
167971ed
DM
289pub const REMOTE_ID_SCHEMA: Schema = StringSchema::new("Remote ID.")
290 .format(&PROXMOX_SAFE_ID_FORMAT)
291 .min_length(3)
292 .max_length(32)
293 .schema();
294
454c13ed
DM
295pub const SINGLE_LINE_COMMENT_SCHEMA: Schema = StringSchema::new("Comment (single line).")
296 .format(&SINGLE_LINE_COMMENT_FORMAT)
297 .schema();
fc189b19 298
b25f313d
DM
299pub const HOSTNAME_SCHEMA: Schema = StringSchema::new("Hostname (as defined in RFC1123).")
300 .format(&HOSTNAME_FORMAT)
301 .schema();
302
303pub const DNS_NAME_OR_IP_SCHEMA: Schema = StringSchema::new("DNS name or IP address.")
304 .format(&DNS_NAME_OR_IP_FORMAT)
305 .schema();
306
163dc16c
DM
307pub const PROXMOX_AUTH_REALM_SCHEMA: Schema = StringSchema::new("Authentication domain ID")
308 .format(&PROXMOX_SAFE_ID_FORMAT)
309 .min_length(3)
310 .max_length(32)
311 .schema();
312
313pub const PROXMOX_USER_ID_SCHEMA: Schema = StringSchema::new("User ID")
314 .format(&PROXMOX_USER_ID_FORMAT)
315 .min_length(3)
316 .max_length(64)
317 .schema();
318
9765092e
DM
319pub const PROXMOX_GROUP_ID_SCHEMA: Schema = StringSchema::new("Group ID")
320 .format(&PROXMOX_GROUP_ID_FORMAT)
321 .min_length(3)
322 .max_length(64)
323 .schema();
324
fc189b19
DM
325
326// Complex type definitions
327
b31c8019
DM
328#[api(
329 properties: {
330 "backup-type": {
331 schema: BACKUP_TYPE_SCHEMA,
332 },
333 "backup-id": {
334 schema: BACKUP_ID_SCHEMA,
335 },
336 "last-backup": {
337 schema: BACKUP_TIME_SCHEMA,
338 },
339 "backup-count": {
340 type: Integer,
341 },
342 files: {
343 items: {
344 schema: BACKUP_ARCHIVE_NAME_SCHEMA
345 },
346 },
347 },
348)]
349#[derive(Serialize, Deserialize)]
350#[serde(rename_all="kebab-case")]
351/// Basic information about a backup group.
352pub struct GroupListItem {
353 pub backup_type: String, // enum
354 pub backup_id: String,
355 pub last_backup: i64,
356 /// Number of contained snapshots
357 pub backup_count: u64,
358 /// List of contained archive files.
359 pub files: Vec<String>,
360}
361
fc189b19 362#[api(
fc189b19
DM
363 properties: {
364 "backup-type": {
365 schema: BACKUP_TYPE_SCHEMA,
366 },
367 "backup-id": {
368 schema: BACKUP_ID_SCHEMA,
369 },
370 "backup-time": {
371 schema: BACKUP_TIME_SCHEMA,
372 },
71da3d6a
DM
373 files: {
374 items: {
375 schema: BACKUP_ARCHIVE_NAME_SCHEMA
376 },
377 },
fc189b19
DM
378 },
379)]
380#[derive(Serialize, Deserialize)]
381#[serde(rename_all="kebab-case")]
71da3d6a 382/// Basic information about backup snapshot.
fc189b19
DM
383pub struct SnapshotListItem {
384 pub backup_type: String, // enum
385 pub backup_id: String,
386 pub backup_time: i64,
71da3d6a 387 /// List of contained archive files.
fc189b19 388 pub files: Vec<String>,
71da3d6a 389 /// Overall snapshot size (sum of all archive sizes).
fc189b19
DM
390 #[serde(skip_serializing_if="Option::is_none")]
391 pub size: Option<u64>,
392}
ff620a3d 393
09b1f7b2
DM
394#[api(
395 properties: {
396 "filename": {
397 schema: BACKUP_ARCHIVE_NAME_SCHEMA,
398 },
399 },
400)]
401#[derive(Serialize, Deserialize)]
402#[serde(rename_all="kebab-case")]
403/// Basic information about archive files inside a backup snapshot.
404pub struct BackupContent {
405 pub filename: String,
406 /// Archive size (from backup manifest).
407 #[serde(skip_serializing_if="Option::is_none")]
408 pub size: Option<u64>,
409}
410
a92830dc
DM
411#[api(
412 properties: {
413 "upid": {
414 optional: true,
415 schema: UPID_SCHEMA,
416 },
417 },
418)]
419#[derive(Clone, Serialize, Deserialize)]
420#[serde(rename_all="kebab-case")]
421/// Garbage collection status.
422pub struct GarbageCollectionStatus {
423 pub upid: Option<String>,
424 /// Number of processed index files.
425 pub index_file_count: usize,
426 /// Sum of bytes referred by index files.
427 pub index_data_bytes: u64,
428 /// Bytes used on disk.
429 pub disk_bytes: u64,
430 /// Chunks used on disk.
431 pub disk_chunks: usize,
432 /// Sum of removed bytes.
433 pub removed_bytes: u64,
434 /// Number of removed chunks.
435 pub removed_chunks: usize,
cf459b19
DM
436 /// Sum of pending bytes (pending removal - kept for safety).
437 pub pending_bytes: u64,
438 /// Number of pending chunks (pending removal - kept for safety).
439 pub pending_chunks: usize,
a92830dc
DM
440}
441
442impl Default for GarbageCollectionStatus {
443 fn default() -> Self {
444 GarbageCollectionStatus {
445 upid: None,
446 index_file_count: 0,
447 index_data_bytes: 0,
448 disk_bytes: 0,
449 disk_chunks: 0,
450 removed_bytes: 0,
451 removed_chunks: 0,
cf459b19
DM
452 pending_bytes: 0,
453 pending_chunks: 0,
a92830dc
DM
454 }
455 }
456}
457
458
1dc117bb
DM
459#[api()]
460#[derive(Serialize, Deserialize)]
461/// Storage space usage information.
462pub struct StorageStatus {
463 /// Total space (bytes).
464 pub total: u64,
465 /// Used space (bytes).
466 pub used: u64,
467 /// Available space (bytes).
468 pub avail: u64,
469}
ff620a3d 470
99384f79
DM
471#[api(
472 properties: {
473 "upid": { schema: UPID_SCHEMA },
474 },
475)]
476#[derive(Serialize, Deserialize)]
477/// Task properties.
478pub struct TaskListItem {
479 pub upid: String,
480 /// The node name where the task is running on.
481 pub node: String,
482 /// The Unix PID
483 pub pid: i64,
484 /// The task start time (Epoch)
485 pub pstart: u64,
486 /// The task start time (Epoch)
487 pub starttime: i64,
488 /// Worker type (arbitrary ASCII string)
489 pub worker_type: String,
490 /// Worker ID (arbitrary ASCII string)
491 pub worker_id: Option<String>,
492 /// The user who started the task
493 pub user: String,
494 /// The task end time (Epoch)
495 #[serde(skip_serializing_if="Option::is_none")]
496 pub endtime: Option<i64>,
497 /// Task end status
498 #[serde(skip_serializing_if="Option::is_none")]
499 pub status: Option<String>,
500}
501
ed751dc2
DM
502#[api()]
503#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
504#[serde(rename_all = "lowercase")]
505/// Node Power command type.
506pub enum NodePowerCommand {
507 /// Restart the server
508 Reboot,
509 /// Shutdown the server
510 Shutdown,
511}
512
c357260d
DM
513#[api()]
514#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
515#[serde(rename_all = "lowercase")]
516/// Interface configuration method
517pub enum NetworkConfigMethod {
518 /// Configuration is done manually using other tools
519 Manual,
520 /// Define interfaces with statically allocated addresses.
521 Static,
522 /// Obtain an address via DHCP
523 DHCP,
524 /// Define the loopback interface.
525 Loopback,
526}
527
02269f3d
DM
528#[api()]
529#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
530#[serde(rename_all = "lowercase")]
531/// Network interface type
532pub enum NetworkInterfaceType {
533 /// Loopback
534 Loopback,
535 /// Physical Ethernet device
536 Ethernet,
537 /// Name looks like a physical ethernet device, but device is not found
538 Vanished,
539 /// Linux Bridge
540 Bridge,
541 /// Linux Bond
542 Bond,
543 /// Linux VLAN (eth.10)
544 Vlan,
545 /// Interface Alias (eth:1)
546 Alias,
547 /// Unknown interface type
548 Unknown,
549}
550
68da20bf
DM
551pub const NETWORK_INTERFACE_NAME_SCHEMA: Schema = StringSchema::new("Network interface name.")
552 .format(&NETWORK_INTERFACE_FORMAT)
553 .min_length(1)
554 .max_length(libc::IFNAMSIZ-1)
555 .schema();
556
1d9a68c2
DM
557pub const NETWORK_INTERFACE_LIST_SCHEMA: Schema = ArraySchema::new(
558 "Network interface list.", &NETWORK_INTERFACE_NAME_SCHEMA)
559 .schema();
560
c357260d
DM
561#[api(
562 properties: {
563 name: {
68da20bf 564 schema: NETWORK_INTERFACE_NAME_SCHEMA,
c357260d 565 },
02269f3d
DM
566 interface_type: {
567 type: NetworkInterfaceType,
568 },
c357260d
DM
569 method_v4: {
570 type: NetworkConfigMethod,
571 optional: true,
572 },
573 method_v6: {
574 type: NetworkConfigMethod,
575 optional: true,
576 },
577 options_v4: {
578 description: "Option list (inet)",
579 type: Array,
580 items: {
68da20bf 581 description: "Optional attribute line.",
c357260d
DM
582 type: String,
583 },
584 },
585 options_v6: {
586 description: "Option list (inet6)",
587 type: Array,
588 items: {
68da20bf 589 description: "Optional attribute line.",
c357260d
DM
590 type: String,
591 },
592 },
5f60a58f 593 comments_v4: {
8a6b86b8
DM
594 description: "Comments (inet, may span multiple lines)",
595 type: String,
596 optional: true,
5f60a58f
DM
597 },
598 comments_v6: {
8a6b86b8
DM
599 description: "Comments (inet6, may span multiple lines)",
600 type: String,
601 optional: true,
5f60a58f 602 },
1d9a68c2
DM
603 bridge_ports: {
604 schema: NETWORK_INTERFACE_LIST_SCHEMA,
605 optional: true,
606 },
42fbe91a
DM
607 bond_slaves: {
608 schema: NETWORK_INTERFACE_LIST_SCHEMA,
609 optional: true,
610 },
c357260d
DM
611 }
612)]
613#[derive(Debug, Serialize, Deserialize)]
614/// Network Interface configuration
615pub struct Interface {
616 /// Autostart interface
f1026a5a 617 pub auto: bool,
c357260d
DM
618 /// Interface is active (UP)
619 pub active: bool,
620 /// Interface name
621 pub name: String,
02269f3d
DM
622 /// Interface type
623 pub interface_type: NetworkInterfaceType,
c357260d 624 #[serde(skip_serializing_if="Option::is_none")]
8b57cd44 625 pub method_v4: Option<NetworkConfigMethod>,
c357260d
DM
626 #[serde(skip_serializing_if="Option::is_none")]
627 pub method_v6: Option<NetworkConfigMethod>,
628 #[serde(skip_serializing_if="Option::is_none")]
8b57cd44
DM
629 /// IPv4 address with netmask
630 pub cidr_v4: Option<String>,
c357260d
DM
631 #[serde(skip_serializing_if="Option::is_none")]
632 /// IPv4 gateway
633 pub gateway_v4: Option<String>,
634 #[serde(skip_serializing_if="Option::is_none")]
8b57cd44
DM
635 /// IPv6 address with netmask
636 pub cidr_v6: Option<String>,
c357260d
DM
637 #[serde(skip_serializing_if="Option::is_none")]
638 /// IPv6 gateway
639 pub gateway_v6: Option<String>,
3fce3bc3 640
c357260d
DM
641 #[serde(skip_serializing_if="Vec::is_empty")]
642 pub options_v4: Vec<String>,
643 #[serde(skip_serializing_if="Vec::is_empty")]
644 pub options_v6: Vec<String>,
2c18efd9 645
8a6b86b8
DM
646 #[serde(skip_serializing_if="Option::is_none")]
647 pub comments_v4: Option<String>,
648 #[serde(skip_serializing_if="Option::is_none")]
649 pub comments_v6: Option<String>,
5f60a58f 650
2c18efd9
DM
651 #[serde(skip_serializing_if="Option::is_none")]
652 /// Maximum Transmission Unit
653 pub mtu: Option<u64>,
1d9a68c2
DM
654
655 #[serde(skip_serializing_if="Option::is_none")]
656 pub bridge_ports: Option<Vec<String>>,
42fbe91a
DM
657
658 #[serde(skip_serializing_if="Option::is_none")]
659 pub bond_slaves: Option<Vec<String>>,
c357260d
DM
660}
661
ff620a3d
DM
662// Regression tests
663
dcb8db66 664#[test]
ff329f97 665fn test_cert_fingerprint_schema() -> Result<(), anyhow::Error> {
dcb8db66
DM
666
667 let schema = CERT_FINGERPRINT_SHA256_SCHEMA;
668
669 let invalid_fingerprints = [
670 "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",
671 "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",
672 "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",
673 "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",
674 "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",
675 "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",
676 ];
677
678 for fingerprint in invalid_fingerprints.iter() {
679 if let Ok(_) = parse_simple_value(fingerprint, &schema) {
680 bail!("test fingerprint '{}' failed - got Ok() while expection an error.", fingerprint);
681 }
682 }
683
684 let valid_fingerprints = [
685 "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",
686 "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",
687 ];
688
689 for fingerprint in valid_fingerprints.iter() {
690 let v = match parse_simple_value(fingerprint, &schema) {
691 Ok(v) => v,
692 Err(err) => {
693 bail!("unable to parse fingerprint '{}' - {}", fingerprint, err);
694 }
695 };
696
697 if v != serde_json::json!(fingerprint) {
698 bail!("unable to parse fingerprint '{}' - got wrong value {:?}", fingerprint, v);
699 }
700 }
701
702 Ok(())
703}
704
ff620a3d 705#[test]
ff329f97 706fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> {
ff620a3d
DM
707
708 let schema = PROXMOX_USER_ID_SCHEMA;
709
710 let invalid_user_ids = [
711 "x", // too short
712 "xx", // too short
713 "xxx", // no realm
714 "xxx@", // no realm
715 "xx x@test", // contains space
716 "xx\nx@test", // contains control character
717 "x:xx@test", // contains collon
718 "xx/x@test", // contains slash
719 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@test", // too long
720 ];
721
722 for name in invalid_user_ids.iter() {
723 if let Ok(_) = parse_simple_value(name, &schema) {
724 bail!("test userid '{}' failed - got Ok() while expection an error.", name);
725 }
726 }
727
728 let valid_user_ids = [
729 "xxx@y",
730 "name@y",
731 "xxx@test-it.com",
732 "xxx@_T_E_S_T-it.com",
733 "x_x-x.x@test-it.com",
734 ];
735
736 for name in valid_user_ids.iter() {
737 let v = match parse_simple_value(name, &schema) {
738 Ok(v) => v,
739 Err(err) => {
740 bail!("unable to parse userid '{}' - {}", name, err);
741 }
742 };
743
744 if v != serde_json::json!(name) {
745 bail!("unable to parse userid '{}' - got wrong value {:?}", name, v);
746 }
747 }
748
749 Ok(())
750}