1 //! Basic API types used by most of the PBS code.
3 use serde
::{Deserialize, Serialize}
;
6 use proxmox
::api
::schema
::{
7 ApiStringFormat
, ApiType
, ArraySchema
, EnumEntry
, IntegerSchema
, ReturnType
, Schema
,
10 use proxmox
::const_regex
;
11 use proxmox
::{IPRE, IPRE_BRACKET, IPV4OCTET, IPV4RE, IPV6H16, IPV6LS32, IPV6RE}
;
15 macro_rules
! PROXMOX_SAFE_ID_REGEX_STR { () => { r"(?:[A-Za-z0-9_][A-Za-z0-9._\-]*)" }
; }
19 macro_rules
! BACKUP_ID_RE { () => (r"[A-Za-z0-9_][A-Za-z0-9._\-]*") }
23 macro_rules
! BACKUP_TYPE_RE { () => (r"(?:host|vm|ct)") }
27 macro_rules
! BACKUP_TIME_RE { () => (r"[0-9]{4}
-[0-9]{2}
-[0-9]{2}T
[0-9]{2}
:[0-9]{2}
:[0-9]{2}Z
") }
31 macro_rules! SNAPSHOT_PATH_REGEX_STR {
33 concat!(r"(", BACKUP_TYPE_RE!(), ")/(", BACKUP_ID_RE!(), ")/(", BACKUP_TIME_RE!(), r")")
39 pub use userid::Authid;
40 pub use userid::Userid;
41 pub use userid::{Realm, RealmRef};
42 pub use userid::{Tokenname, TokennameRef};
43 pub use userid::{Username, UsernameRef};
44 pub use userid::{PROXMOX_GROUP_ID_SCHEMA, PROXMOX_TOKEN_ID_SCHEMA, PROXMOX_TOKEN_NAME_SCHEMA};
48 pub use user::{ApiToken, User, UserWithTokens};
50 EMAIL_SCHEMA, ENABLE_USER_SCHEMA, EXPIRE_USER_SCHEMA, FIRST_NAME_SCHEMA, LAST_NAME_SCHEMA,
57 pub use crypto::{CryptMode, Fingerprint};
70 macro_rules! DNS_LABEL { () => (r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") }
71 macro_rules! DNS_NAME { () => (concat!(r"(?:(?:", DNS_LABEL!() , r"\.)*", DNS_LABEL!(), ")")) }
72 macro_rules! CIDR_V4_REGEX_STR { () => (concat!(r"(?:", IPV4RE!(), r"/\d{1,2})$")) }
73 macro_rules
! CIDR_V6_REGEX_STR { () => (concat!(r"(?:", IPV6RE!(), r"/\d{1,3}
)$
")) }
74 macro_rules! DNS_ALIAS_LABEL { () => (r"(?:[a-zA-Z0-9_](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") }
75 macro_rules! DNS_ALIAS_NAME {
76 () => (concat!(r"(?
:(?
:", DNS_ALIAS_LABEL!() , r"\.)*", DNS_ALIAS_LABEL!(), ")"))
81 pub IP_V4_REGEX = concat!(r"^
", IPV4RE!(), r"$
");
82 pub IP_V6_REGEX = concat!(r"^
", IPV6RE!(), r"$
");
83 pub IP_REGEX = concat!(r"^
", IPRE!(), r"$
");
84 pub CIDR_V4_REGEX = concat!(r"^
", CIDR_V4_REGEX_STR!(), r"$
");
85 pub CIDR_V6_REGEX = concat!(r"^
", CIDR_V6_REGEX_STR!(), r"$
");
86 pub CIDR_REGEX = concat!(r"^
(?
:", CIDR_V4_REGEX_STR!(), "|", CIDR_V6_REGEX_STR!(), r")$
");
87 pub HOSTNAME_REGEX = r"^
(?
:[a
-zA
-Z0
-9](?
:[a
-zA
-Z0
-9\-]*[a
-zA
-Z0
-9])?
)$
";
88 pub DNS_NAME_REGEX = concat!(r"^
", DNS_NAME!(), r"$
");
89 pub DNS_ALIAS_REGEX = concat!(r"^
", DNS_ALIAS_NAME!(), r"$
");
90 pub DNS_NAME_OR_IP_REGEX = concat!(r"^
(?
:", DNS_NAME!(), "|", IPRE!(), r")$
");
92 pub SHA256_HEX_REGEX = r"^
[a
-f0
-9]{64}$
"; // fixme: define in common_regex ?
94 pub PASSWORD_REGEX = r"^
[[:^cntrl
:]]*$
"; // everything but control characters
96 pub UUID_REGEX = r"^
[0-9a
-f
]{8}
(?
:-[0-9a
-f
]{4}
){3}
-[0-9a
-f
]{12}$
";
98 pub BACKUP_TYPE_REGEX = concat!(r"^
(", BACKUP_TYPE_RE!(), r")$
");
100 pub BACKUP_ID_REGEX = concat!(r"^
", BACKUP_ID_RE!(), r"$
");
102 pub BACKUP_DATE_REGEX = concat!(r"^
", BACKUP_TIME_RE!() ,r"$
");
104 pub GROUP_PATH_REGEX = concat!(r"^
(", BACKUP_TYPE_RE!(), ")/(", BACKUP_ID_RE!(), r")$
");
106 pub BACKUP_FILE_REGEX = r"^
.*\.([fd
]idx
|blob
)$
";
108 pub SNAPSHOT_PATH_REGEX = concat!(r"^
", SNAPSHOT_PATH_REGEX_STR!(), r"$
");
110 pub FINGERPRINT_SHA256_REGEX = r"^
(?
:[0-9a
-fA
-F
][0-9a
-fA
-F
])(?
::[0-9a
-fA
-F
][0-9a
-fA
-F
]){31}$
";
112 /// Regex for safe identifiers.
115 /// [article](https://dwheeler.com/essays/fixing-unix-linux-filenames.html)
116 /// contains further information why it is reasonable to restict
117 /// names this way. This is not only useful for filenames, but for
118 /// any identifier command line tools work with.
119 pub PROXMOX_SAFE_ID_REGEX = concat!(r"^
", PROXMOX_SAFE_ID_REGEX_STR!(), r"$
");
121 pub SINGLE_LINE_COMMENT_REGEX = r"^
[[:^cntrl
:]]*$
";
123 pub BACKUP_REPO_URL_REGEX = concat!(
125 USER_ID_REGEX_STR!(), "|", APITOKEN_ID_REGEX_STR!(),
127 DNS_NAME!(), "|", IPRE_BRACKET!(),
128 "):)?
(?
:([0-9]{1,5}
):)?
(", PROXMOX_SAFE_ID_REGEX_STR!(), r")$
"
131 pub BLOCKDEVICE_NAME_REGEX = r"^
(:?
(:?h
|s
|x?v
)d
[a
-z
]+)|(:?nvme
\d
+n
\d
+)$
";
134 pub const IP_V4_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&IP_V4_REGEX);
135 pub const IP_V6_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&IP_V6_REGEX);
136 pub const IP_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&IP_REGEX);
137 pub const CIDR_V4_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_V4_REGEX);
138 pub const CIDR_V6_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_V6_REGEX);
139 pub const CIDR_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_REGEX);
141 pub const DNS_NAME_FORMAT: ApiStringFormat =
142 ApiStringFormat::Pattern(&DNS_NAME_REGEX);
144 pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat =
145 ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX);
147 pub const DNS_NAME_OR_IP_SCHEMA: Schema = StringSchema::new("DNS name or IP address
.")
148 .format(&DNS_NAME_OR_IP_FORMAT)
151 pub const BACKUP_ID_SCHEMA: Schema = StringSchema::new("Backup ID
.")
152 .format(&BACKUP_ID_FORMAT)
154 pub const BACKUP_TYPE_SCHEMA: Schema = StringSchema::new("Backup
type.")
155 .format(&ApiStringFormat::Enum(&[
156 EnumEntry::new("vm
", "Virtual Machine Backup
"),
157 EnumEntry::new("ct
", "Container Backup
"),
158 EnumEntry::new("host
", "Host Backup
"),
161 pub const BACKUP_TIME_SCHEMA: Schema = IntegerSchema::new("Backup
time (Unix epoch
.)")
162 .minimum(1_547_797_308)
165 pub const DATASTORE_SCHEMA: Schema = StringSchema::new("Datastore name
.")
166 .format(&PROXMOX_SAFE_ID_FORMAT)
171 pub const REALM_ID_SCHEMA: Schema = StringSchema::new("Realm name
.")
172 .format(&PROXMOX_SAFE_ID_FORMAT)
177 pub const FINGERPRINT_SHA256_FORMAT: ApiStringFormat =
178 ApiStringFormat::Pattern(&FINGERPRINT_SHA256_REGEX);
180 pub const CERT_FINGERPRINT_SHA256_SCHEMA: Schema =
181 StringSchema::new("X509 certificate
fingerprint (sha256
).")
182 .format(&FINGERPRINT_SHA256_FORMAT)
185 pub const PRUNE_SCHEMA_KEEP_DAILY: Schema = IntegerSchema::new("Number of daily backups to keep
.")
189 pub const PRUNE_SCHEMA_KEEP_HOURLY: Schema =
190 IntegerSchema::new("Number of hourly backups to keep
.")
194 pub const PRUNE_SCHEMA_KEEP_LAST: Schema = IntegerSchema::new("Number of backups to keep
.")
198 pub const PRUNE_SCHEMA_KEEP_MONTHLY: Schema =
199 IntegerSchema::new("Number of monthly backups to keep
.")
203 pub const PRUNE_SCHEMA_KEEP_WEEKLY: Schema =
204 IntegerSchema::new("Number of weekly backups to keep
.")
208 pub const PRUNE_SCHEMA_KEEP_YEARLY: Schema =
209 IntegerSchema::new("Number of yearly backups to keep
.")
213 pub const PROXMOX_SAFE_ID_FORMAT: ApiStringFormat =
214 ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
216 pub const SINGLE_LINE_COMMENT_FORMAT: ApiStringFormat =
217 ApiStringFormat::Pattern(&SINGLE_LINE_COMMENT_REGEX);
219 pub const SINGLE_LINE_COMMENT_SCHEMA: Schema = StringSchema::new("Comment (single line
).")
220 .format(&SINGLE_LINE_COMMENT_FORMAT)
223 pub const PROXMOX_CONFIG_DIGEST_SCHEMA: Schema = StringSchema::new(
224 "Prevent changes
if current configuration file has different
\
225 SHA256 digest
. This can be used to prevent concurrent
\
228 .format(&PVE_CONFIG_DIGEST_FORMAT)
231 pub const BACKUP_ID_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&BACKUP_ID_REGEX);
233 /// API schema format definition for repository URLs
234 pub const BACKUP_REPO_URL: ApiStringFormat = ApiStringFormat::Pattern(&BACKUP_REPO_URL_REGEX);
244 #[derive(Clone, Serialize, Deserialize)]
245 #[serde(rename_all = "kebab
-case
")]
246 /// Garbage collection status.
247 pub struct GarbageCollectionStatus {
248 pub upid: Option<String>,
249 /// Number of processed index files.
250 pub index_file_count: usize,
251 /// Sum of bytes referred by index files.
252 pub index_data_bytes: u64,
253 /// Bytes used on disk.
255 /// Chunks used on disk.
256 pub disk_chunks: usize,
257 /// Sum of removed bytes.
258 pub removed_bytes: u64,
259 /// Number of removed chunks.
260 pub removed_chunks: usize,
261 /// Sum of pending bytes (pending removal - kept for safety).
262 pub pending_bytes: u64,
263 /// Number of pending chunks (pending removal - kept for safety).
264 pub pending_chunks: usize,
265 /// Number of chunks marked as .bad by verify that have been removed by GC.
266 pub removed_bad: usize,
267 /// Number of chunks still marked as .bad after garbage collection.
268 pub still_bad: usize,
271 impl Default for GarbageCollectionStatus {
272 fn default() -> Self {
273 GarbageCollectionStatus {
289 pub const PVE_CONFIG_DIGEST_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&SHA256_HEX_REGEX);
290 pub const CHUNK_DIGEST_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&SHA256_HEX_REGEX);
292 pub const PASSWORD_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&PASSWORD_REGEX);
294 pub const UUID_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&UUID_REGEX);
296 pub const BACKUP_ARCHIVE_NAME_SCHEMA: Schema = StringSchema::new("Backup archive name
.")
297 .format(&PROXMOX_SAFE_ID_FORMAT)
300 // Complex type definitions
305 schema: BACKUP_ARCHIVE_NAME_SCHEMA,
313 #[derive(Serialize, Deserialize)]
314 #[serde(rename_all = "kebab
-case
")]
315 /// Basic information about archive files inside a backup snapshot.
316 pub struct BackupContent {
317 pub filename: String,
318 /// Info if file is encrypted, signed, or neither.
319 #[serde(skip_serializing_if = "Option
::is_none
")]
320 pub crypt_mode: Option<CryptMode>,
321 /// Archive size (from backup manifest).
322 #[serde(skip_serializing_if = "Option
::is_none
")]
323 pub size: Option<u64>,
327 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
328 #[serde(rename_all = "lowercase
")]
329 /// Result of a verify operation.
330 pub enum VerifyState {
331 /// Verification was successful
333 /// Verification reported one or more errors
347 #[derive(Serialize, Deserialize)]
349 pub struct SnapshotVerifyState {
350 /// UPID of the verify task
352 /// State of the verification. Enum.
353 pub state: VerifyState,
359 schema: BACKUP_TYPE_SCHEMA,
362 schema: BACKUP_ID_SCHEMA,
365 schema: BACKUP_TIME_SCHEMA,
368 schema: SINGLE_LINE_COMMENT_SCHEMA,
372 type: SnapshotVerifyState,
381 schema: BACKUP_ARCHIVE_NAME_SCHEMA
390 #[derive(Serialize, Deserialize)]
391 #[serde(rename_all = "kebab
-case
")]
392 /// Basic information about backup snapshot.
393 pub struct SnapshotListItem {
394 pub backup_type: String, // enum
395 pub backup_id: String,
396 pub backup_time: i64,
397 /// The first line from manifest "notes
"
398 #[serde(skip_serializing_if = "Option
::is_none
")]
399 pub comment: Option<String>,
400 /// The result of the last run verify task
401 #[serde(skip_serializing_if = "Option
::is_none
")]
402 pub verification: Option<SnapshotVerifyState>,
403 /// Fingerprint of encryption key
404 #[serde(skip_serializing_if = "Option
::is_none
")]
405 pub fingerprint: Option<Fingerprint>,
406 /// List of contained archive files.
407 pub files: Vec<BackupContent>,
408 /// Overall snapshot size (sum of all archive sizes).
409 #[serde(skip_serializing_if = "Option
::is_none
")]
410 pub size: Option<u64>,
411 /// The owner of the snapshots group
412 #[serde(skip_serializing_if = "Option
::is_none
")]
413 pub owner: Option<Authid>,
419 schema: BACKUP_TYPE_SCHEMA,
422 schema: BACKUP_ID_SCHEMA,
425 schema: BACKUP_TIME_SCHEMA,
432 schema: BACKUP_ARCHIVE_NAME_SCHEMA
441 #[derive(Serialize, Deserialize)]
442 #[serde(rename_all = "kebab
-case
")]
443 /// Basic information about a backup group.
444 pub struct GroupListItem {
445 pub backup_type: String, // enum
446 pub backup_id: String,
447 pub last_backup: i64,
448 /// Number of contained snapshots
449 pub backup_count: u64,
450 /// List of contained archive files.
451 pub files: Vec<String>,
452 /// The owner of group
453 #[serde(skip_serializing_if = "Option
::is_none
")]
454 pub owner: Option<Authid>,
455 /// The first line from group "notes
"
456 #[serde(skip_serializing_if = "Option
::is_none
")]
457 pub comment: Option<String>,
463 schema: DATASTORE_SCHEMA,
467 schema: SINGLE_LINE_COMMENT_SCHEMA,
471 #[derive(Serialize, Deserialize)]
472 #[serde(rename_all = "kebab
-case
")]
473 /// Basic information about a datastore.
474 pub struct DataStoreListItem {
476 pub comment: Option<String>,
482 schema: BACKUP_TYPE_SCHEMA,
485 schema: BACKUP_ID_SCHEMA,
488 schema: BACKUP_TIME_SCHEMA,
492 #[derive(Serialize, Deserialize)]
493 #[serde(rename_all = "kebab
-case
")]
495 pub struct PruneListItem {
496 pub backup_type: String, // enum
497 pub backup_id: String,
498 pub backup_time: i64,
504 #[derive(Default, Serialize, Deserialize)]
505 /// Storage space usage information.
506 pub struct StorageStatus {
507 /// Total space (bytes).
509 /// Used space (bytes).
511 /// Available space (bytes).
516 #[derive(Serialize, Deserialize, Default)]
517 /// Backup Type group/snapshot counts.
518 pub struct TypeCounts {
519 /// The number of groups of the type.
521 /// The number of snapshots of the type.
545 #[derive(Serialize, Deserialize, Default)]
546 /// Counts of groups/snapshots per BackupType.
548 /// The counts for CT backups
549 pub ct: Option<TypeCounts>,
550 /// The counts for Host backups
551 pub host: Option<TypeCounts>,
552 /// The counts for VM backups
553 pub vm: Option<TypeCounts>,
554 /// The counts for other backup types
555 pub other: Option<TypeCounts>,
558 pub const PASSWORD_HINT_SCHEMA: Schema = StringSchema::new("Password hint
.")
559 .format(&SINGLE_LINE_COMMENT_FORMAT)
566 #[derive(Deserialize, Serialize)]
567 /// RSA public key information
568 pub struct RsaPubKeyInfo {
569 /// Path to key (if stored in a file)
570 #[serde(skip_serializing_if="Option
::is_none
")]
571 pub path: Option<String>,
573 pub exponent: String,
574 /// Hex-encoded RSA modulus
576 /// Key (modulus) length in bits
580 impl std::convert::TryFrom<openssl::rsa::Rsa<openssl::pkey::Public>> for RsaPubKeyInfo {
581 type Error = anyhow::Error;
583 fn try_from(value: openssl::rsa::Rsa<openssl::pkey::Public>) -> Result<Self, Self::Error> {
584 let modulus = value.n().to_hex_str()?.to_string();
585 let exponent = value.e().to_dec_str()?.to_string();
586 let length = value.size() as usize * 8;
599 upid: { schema: UPID::API_SCHEMA },
602 #[derive(Serialize, Deserialize)]
604 pub struct TaskListItem {
606 /// The node name where the task is running on.
610 /// The task start time (Epoch)
612 /// The task start time (Epoch)
614 /// Worker type (arbitrary ASCII string)
615 pub worker_type: String,
616 /// Worker ID (arbitrary ASCII string)
617 pub worker_id: Option<String>,
618 /// The authenticated entity who started the task
620 /// The task end time (Epoch)
621 #[serde(skip_serializing_if="Option
::is_none
")]
622 pub endtime: Option<i64>,
624 #[serde(skip_serializing_if="Option
::is_none
")]
625 pub status: Option<String>,
628 pub const ADMIN_DATASTORE_LIST_SNAPSHOTS_RETURN_TYPE: ReturnType = ReturnType {
630 schema: &ArraySchema::new(
631 "Returns the list of snapshots
.",
632 &SnapshotListItem::API_SCHEMA,
636 pub const ADMIN_DATASTORE_LIST_SNAPSHOT_FILES_RETURN_TYPE: ReturnType = ReturnType {
638 schema: &ArraySchema::new(
639 "Returns the list of archive files inside a backup snapshots
.",
640 &BackupContent::API_SCHEMA,
644 pub const ADMIN_DATASTORE_LIST_GROUPS_RETURN_TYPE: ReturnType = ReturnType {
646 schema: &ArraySchema::new(
647 "Returns the list of backup groups
.",
648 &GroupListItem::API_SCHEMA,
652 pub const ADMIN_DATASTORE_PRUNE_RETURN_TYPE: ReturnType = ReturnType {
654 schema: &ArraySchema::new(
655 "Returns the list of snapshots and a flag indicating
if there are kept or removed
.",
656 &PruneListItem::API_SCHEMA,
660 pub const NODE_TASKS_LIST_TASKS_RETURN_TYPE: ReturnType = ReturnType {
662 schema: &ArraySchema::new(
664 &TaskListItem::API_SCHEMA,