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