]> git.proxmox.com Git - proxmox-backup.git/blob - pbs-api-types/src/lib.rs
move some api types to pbs-api-types
[proxmox-backup.git] / pbs-api-types / src / lib.rs
1 //! Basic API types used by most of the PBS code.
2
3 use serde::{Deserialize, Serialize};
4
5 use proxmox::api::api;
6 use proxmox::api::schema::{ApiStringFormat, EnumEntry, IntegerSchema, Schema, StringSchema};
7 use proxmox::const_regex;
8 use proxmox::{IPRE, IPRE_BRACKET, IPV4OCTET, IPV4RE, IPV6H16, IPV6LS32, IPV6RE};
9
10 #[rustfmt::skip]
11 #[macro_export]
12 macro_rules! PROXMOX_SAFE_ID_REGEX_STR { () => { r"(?:[A-Za-z0-9_][A-Za-z0-9._\-]*)" }; }
13
14 #[rustfmt::skip]
15 #[macro_export]
16 macro_rules! BACKUP_ID_RE { () => (r"[A-Za-z0-9_][A-Za-z0-9._\-]*") }
17
18 #[rustfmt::skip]
19 #[macro_export]
20 macro_rules! BACKUP_TYPE_RE { () => (r"(?:host|vm|ct)") }
21
22 #[rustfmt::skip]
23 #[macro_export]
24 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") }
25
26 #[rustfmt::skip]
27 #[macro_export]
28 macro_rules! SNAPSHOT_PATH_REGEX_STR {
29 () => (
30 concat!(r"(", BACKUP_TYPE_RE!(), ")/(", BACKUP_ID_RE!(), ")/(", BACKUP_TIME_RE!(), r")")
31 );
32 }
33
34 #[macro_use]
35 mod userid;
36 pub use userid::Authid;
37 pub use userid::Userid;
38 pub use userid::{Realm, RealmRef};
39 pub use userid::{Tokenname, TokennameRef};
40 pub use userid::{Username, UsernameRef};
41 pub use userid::{PROXMOX_GROUP_ID_SCHEMA, PROXMOX_TOKEN_ID_SCHEMA, PROXMOX_TOKEN_NAME_SCHEMA};
42
43 #[macro_use]
44 mod user;
45 pub use user::{ApiToken, User, UserWithTokens};
46 pub use user::{
47 EMAIL_SCHEMA, ENABLE_USER_SCHEMA, EXPIRE_USER_SCHEMA, FIRST_NAME_SCHEMA, LAST_NAME_SCHEMA,
48 };
49
50 pub mod upid;
51 pub use upid::UPID;
52
53 mod crypto;
54 pub use crypto::{CryptMode, Fingerprint};
55
56 #[rustfmt::skip]
57 #[macro_use]
58 mod local_macros {
59 macro_rules! DNS_LABEL { () => (r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") }
60 macro_rules! DNS_NAME { () => (concat!(r"(?:(?:", DNS_LABEL!() , r"\.)*", DNS_LABEL!(), ")")) }
61 macro_rules! CIDR_V4_REGEX_STR { () => (concat!(r"(?:", IPV4RE!(), r"/\d{1,2})$")) }
62 macro_rules! CIDR_V6_REGEX_STR { () => (concat!(r"(?:", IPV6RE!(), r"/\d{1,3})$")) }
63 macro_rules! DNS_ALIAS_LABEL { () => (r"(?:[a-zA-Z0-9_](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") }
64 macro_rules! DNS_ALIAS_NAME {
65 () => (concat!(r"(?:(?:", DNS_ALIAS_LABEL!() , r"\.)*", DNS_ALIAS_LABEL!(), ")"))
66 }
67 }
68
69 const_regex! {
70 pub IP_V4_REGEX = concat!(r"^", IPV4RE!(), r"$");
71 pub IP_V6_REGEX = concat!(r"^", IPV6RE!(), r"$");
72 pub IP_REGEX = concat!(r"^", IPRE!(), r"$");
73 pub CIDR_V4_REGEX = concat!(r"^", CIDR_V4_REGEX_STR!(), r"$");
74 pub CIDR_V6_REGEX = concat!(r"^", CIDR_V6_REGEX_STR!(), r"$");
75 pub CIDR_REGEX = concat!(r"^(?:", CIDR_V4_REGEX_STR!(), "|", CIDR_V6_REGEX_STR!(), r")$");
76 pub HOSTNAME_REGEX = r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)$";
77 pub DNS_NAME_REGEX = concat!(r"^", DNS_NAME!(), r"$");
78 pub DNS_ALIAS_REGEX = concat!(r"^", DNS_ALIAS_NAME!(), r"$");
79 pub DNS_NAME_OR_IP_REGEX = concat!(r"^(?:", DNS_NAME!(), "|", IPRE!(), r")$");
80
81 pub SHA256_HEX_REGEX = r"^[a-f0-9]{64}$"; // fixme: define in common_regex ?
82
83 pub PASSWORD_REGEX = r"^[[:^cntrl:]]*$"; // everything but control characters
84
85 pub UUID_REGEX = r"^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$";
86
87 pub BACKUP_TYPE_REGEX = concat!(r"^(", BACKUP_TYPE_RE!(), r")$");
88
89 pub BACKUP_ID_REGEX = concat!(r"^", BACKUP_ID_RE!(), r"$");
90
91 pub BACKUP_DATE_REGEX = concat!(r"^", BACKUP_TIME_RE!() ,r"$");
92
93 pub GROUP_PATH_REGEX = concat!(r"^(", BACKUP_TYPE_RE!(), ")/(", BACKUP_ID_RE!(), r")$");
94
95 pub BACKUP_FILE_REGEX = r"^.*\.([fd]idx|blob)$";
96
97 pub SNAPSHOT_PATH_REGEX = concat!(r"^", SNAPSHOT_PATH_REGEX_STR!(), r"$");
98
99 pub FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$";
100
101 /// Regex for safe identifiers.
102 ///
103 /// This
104 /// [article](https://dwheeler.com/essays/fixing-unix-linux-filenames.html)
105 /// contains further information why it is reasonable to restict
106 /// names this way. This is not only useful for filenames, but for
107 /// any identifier command line tools work with.
108 pub PROXMOX_SAFE_ID_REGEX = concat!(r"^", PROXMOX_SAFE_ID_REGEX_STR!(), r"$");
109
110 pub SINGLE_LINE_COMMENT_REGEX = r"^[[:^cntrl:]]*$";
111
112 pub BACKUP_REPO_URL_REGEX = concat!(
113 r"^^(?:(?:(",
114 USER_ID_REGEX_STR!(), "|", APITOKEN_ID_REGEX_STR!(),
115 ")@)?(",
116 DNS_NAME!(), "|", IPRE_BRACKET!(),
117 "):)?(?:([0-9]{1,5}):)?(", PROXMOX_SAFE_ID_REGEX_STR!(), r")$"
118 );
119 }
120
121 pub const IP_V4_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&IP_V4_REGEX);
122 pub const IP_V6_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&IP_V6_REGEX);
123 pub const IP_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&IP_REGEX);
124 pub const CIDR_V4_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_V4_REGEX);
125 pub const CIDR_V6_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_V6_REGEX);
126 pub const CIDR_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_REGEX);
127
128 pub const BACKUP_ID_SCHEMA: Schema = StringSchema::new("Backup ID.")
129 .format(&BACKUP_ID_FORMAT)
130 .schema();
131 pub const BACKUP_TYPE_SCHEMA: Schema = StringSchema::new("Backup type.")
132 .format(&ApiStringFormat::Enum(&[
133 EnumEntry::new("vm", "Virtual Machine Backup"),
134 EnumEntry::new("ct", "Container Backup"),
135 EnumEntry::new("host", "Host Backup"),
136 ]))
137 .schema();
138 pub const BACKUP_TIME_SCHEMA: Schema = IntegerSchema::new("Backup time (Unix epoch.)")
139 .minimum(1_547_797_308)
140 .schema();
141
142 pub const DATASTORE_SCHEMA: Schema = StringSchema::new("Datastore name.")
143 .format(&PROXMOX_SAFE_ID_FORMAT)
144 .min_length(3)
145 .max_length(32)
146 .schema();
147
148 pub const FINGERPRINT_SHA256_FORMAT: ApiStringFormat =
149 ApiStringFormat::Pattern(&FINGERPRINT_SHA256_REGEX);
150
151 pub const CERT_FINGERPRINT_SHA256_SCHEMA: Schema =
152 StringSchema::new("X509 certificate fingerprint (sha256).")
153 .format(&FINGERPRINT_SHA256_FORMAT)
154 .schema();
155
156 pub const PRUNE_SCHEMA_KEEP_DAILY: Schema = IntegerSchema::new("Number of daily backups to keep.")
157 .minimum(1)
158 .schema();
159
160 pub const PRUNE_SCHEMA_KEEP_HOURLY: Schema =
161 IntegerSchema::new("Number of hourly backups to keep.")
162 .minimum(1)
163 .schema();
164
165 pub const PRUNE_SCHEMA_KEEP_LAST: Schema = IntegerSchema::new("Number of backups to keep.")
166 .minimum(1)
167 .schema();
168
169 pub const PRUNE_SCHEMA_KEEP_MONTHLY: Schema =
170 IntegerSchema::new("Number of monthly backups to keep.")
171 .minimum(1)
172 .schema();
173
174 pub const PRUNE_SCHEMA_KEEP_WEEKLY: Schema =
175 IntegerSchema::new("Number of weekly backups to keep.")
176 .minimum(1)
177 .schema();
178
179 pub const PRUNE_SCHEMA_KEEP_YEARLY: Schema =
180 IntegerSchema::new("Number of yearly backups to keep.")
181 .minimum(1)
182 .schema();
183
184 pub const PROXMOX_SAFE_ID_FORMAT: ApiStringFormat =
185 ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
186
187 pub const SINGLE_LINE_COMMENT_FORMAT: ApiStringFormat =
188 ApiStringFormat::Pattern(&SINGLE_LINE_COMMENT_REGEX);
189
190 pub const SINGLE_LINE_COMMENT_SCHEMA: Schema = StringSchema::new("Comment (single line).")
191 .format(&SINGLE_LINE_COMMENT_FORMAT)
192 .schema();
193
194 pub const PROXMOX_CONFIG_DIGEST_SCHEMA: Schema = StringSchema::new(
195 "Prevent changes if current configuration file has different \
196 SHA256 digest. This can be used to prevent concurrent \
197 modifications.",
198 )
199 .format(&PVE_CONFIG_DIGEST_FORMAT)
200 .schema();
201
202 pub const BACKUP_ID_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&BACKUP_ID_REGEX);
203
204 /// API schema format definition for repository URLs
205 pub const BACKUP_REPO_URL: ApiStringFormat = ApiStringFormat::Pattern(&BACKUP_REPO_URL_REGEX);
206
207 #[api(
208 properties: {
209 "upid": {
210 optional: true,
211 type: UPID,
212 },
213 },
214 )]
215 #[derive(Clone, Serialize, Deserialize)]
216 #[serde(rename_all = "kebab-case")]
217 /// Garbage collection status.
218 pub struct GarbageCollectionStatus {
219 pub upid: Option<String>,
220 /// Number of processed index files.
221 pub index_file_count: usize,
222 /// Sum of bytes referred by index files.
223 pub index_data_bytes: u64,
224 /// Bytes used on disk.
225 pub disk_bytes: u64,
226 /// Chunks used on disk.
227 pub disk_chunks: usize,
228 /// Sum of removed bytes.
229 pub removed_bytes: u64,
230 /// Number of removed chunks.
231 pub removed_chunks: usize,
232 /// Sum of pending bytes (pending removal - kept for safety).
233 pub pending_bytes: u64,
234 /// Number of pending chunks (pending removal - kept for safety).
235 pub pending_chunks: usize,
236 /// Number of chunks marked as .bad by verify that have been removed by GC.
237 pub removed_bad: usize,
238 /// Number of chunks still marked as .bad after garbage collection.
239 pub still_bad: usize,
240 }
241
242 impl Default for GarbageCollectionStatus {
243 fn default() -> Self {
244 GarbageCollectionStatus {
245 upid: None,
246 index_file_count: 0,
247 index_data_bytes: 0,
248 disk_bytes: 0,
249 disk_chunks: 0,
250 removed_bytes: 0,
251 removed_chunks: 0,
252 pending_bytes: 0,
253 pending_chunks: 0,
254 removed_bad: 0,
255 still_bad: 0,
256 }
257 }
258 }
259
260 pub const PVE_CONFIG_DIGEST_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&SHA256_HEX_REGEX);
261 pub const CHUNK_DIGEST_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&SHA256_HEX_REGEX);
262
263 pub const PASSWORD_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&PASSWORD_REGEX);
264
265 pub const UUID_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&UUID_REGEX);
266
267 pub const BACKUP_ARCHIVE_NAME_SCHEMA: Schema = StringSchema::new("Backup archive name.")
268 .format(&PROXMOX_SAFE_ID_FORMAT)
269 .schema();
270
271 // Complex type definitions
272
273 #[api(
274 properties: {
275 "filename": {
276 schema: BACKUP_ARCHIVE_NAME_SCHEMA,
277 },
278 "crypt-mode": {
279 type: CryptMode,
280 optional: true,
281 },
282 },
283 )]
284 #[derive(Serialize, Deserialize)]
285 #[serde(rename_all = "kebab-case")]
286 /// Basic information about archive files inside a backup snapshot.
287 pub struct BackupContent {
288 pub filename: String,
289 /// Info if file is encrypted, signed, or neither.
290 #[serde(skip_serializing_if = "Option::is_none")]
291 pub crypt_mode: Option<CryptMode>,
292 /// Archive size (from backup manifest).
293 #[serde(skip_serializing_if = "Option::is_none")]
294 pub size: Option<u64>,
295 }
296
297 #[api()]
298 #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
299 #[serde(rename_all = "lowercase")]
300 /// Result of a verify operation.
301 pub enum VerifyState {
302 /// Verification was successful
303 Ok,
304 /// Verification reported one or more errors
305 Failed,
306 }
307
308 #[api(
309 properties: {
310 upid: {
311 type: UPID,
312 },
313 state: {
314 type: VerifyState,
315 },
316 },
317 )]
318 #[derive(Serialize, Deserialize)]
319 /// Task properties.
320 pub struct SnapshotVerifyState {
321 /// UPID of the verify task
322 pub upid: UPID,
323 /// State of the verification. Enum.
324 pub state: VerifyState,
325 }
326
327 #[api(
328 properties: {
329 "backup-type": {
330 schema: BACKUP_TYPE_SCHEMA,
331 },
332 "backup-id": {
333 schema: BACKUP_ID_SCHEMA,
334 },
335 "backup-time": {
336 schema: BACKUP_TIME_SCHEMA,
337 },
338 comment: {
339 schema: SINGLE_LINE_COMMENT_SCHEMA,
340 optional: true,
341 },
342 verification: {
343 type: SnapshotVerifyState,
344 optional: true,
345 },
346 fingerprint: {
347 type: String,
348 optional: true,
349 },
350 files: {
351 items: {
352 schema: BACKUP_ARCHIVE_NAME_SCHEMA
353 },
354 },
355 owner: {
356 type: Authid,
357 optional: true,
358 },
359 },
360 )]
361 #[derive(Serialize, Deserialize)]
362 #[serde(rename_all = "kebab-case")]
363 /// Basic information about backup snapshot.
364 pub struct SnapshotListItem {
365 pub backup_type: String, // enum
366 pub backup_id: String,
367 pub backup_time: i64,
368 /// The first line from manifest "notes"
369 #[serde(skip_serializing_if = "Option::is_none")]
370 pub comment: Option<String>,
371 /// The result of the last run verify task
372 #[serde(skip_serializing_if = "Option::is_none")]
373 pub verification: Option<SnapshotVerifyState>,
374 /// Fingerprint of encryption key
375 #[serde(skip_serializing_if = "Option::is_none")]
376 pub fingerprint: Option<Fingerprint>,
377 /// List of contained archive files.
378 pub files: Vec<BackupContent>,
379 /// Overall snapshot size (sum of all archive sizes).
380 #[serde(skip_serializing_if = "Option::is_none")]
381 pub size: Option<u64>,
382 /// The owner of the snapshots group
383 #[serde(skip_serializing_if = "Option::is_none")]
384 pub owner: Option<Authid>,
385 }
386
387 #[api(
388 properties: {
389 "backup-type": {
390 schema: BACKUP_TYPE_SCHEMA,
391 },
392 "backup-id": {
393 schema: BACKUP_ID_SCHEMA,
394 },
395 "last-backup": {
396 schema: BACKUP_TIME_SCHEMA,
397 },
398 "backup-count": {
399 type: Integer,
400 },
401 files: {
402 items: {
403 schema: BACKUP_ARCHIVE_NAME_SCHEMA
404 },
405 },
406 owner: {
407 type: Authid,
408 optional: true,
409 },
410 },
411 )]
412 #[derive(Serialize, Deserialize)]
413 #[serde(rename_all = "kebab-case")]
414 /// Basic information about a backup group.
415 pub struct GroupListItem {
416 pub backup_type: String, // enum
417 pub backup_id: String,
418 pub last_backup: i64,
419 /// Number of contained snapshots
420 pub backup_count: u64,
421 /// List of contained archive files.
422 pub files: Vec<String>,
423 /// The owner of group
424 #[serde(skip_serializing_if = "Option::is_none")]
425 pub owner: Option<Authid>,
426 /// The first line from group "notes"
427 #[serde(skip_serializing_if = "Option::is_none")]
428 pub comment: Option<String>,
429 }
430
431 #[api(
432 properties: {
433 store: {
434 schema: DATASTORE_SCHEMA,
435 },
436 comment: {
437 optional: true,
438 schema: SINGLE_LINE_COMMENT_SCHEMA,
439 },
440 },
441 )]
442 #[derive(Serialize, Deserialize)]
443 #[serde(rename_all = "kebab-case")]
444 /// Basic information about a datastore.
445 pub struct DataStoreListItem {
446 pub store: String,
447 pub comment: Option<String>,
448 }
449
450 #[api(
451 properties: {
452 "backup-type": {
453 schema: BACKUP_TYPE_SCHEMA,
454 },
455 "backup-id": {
456 schema: BACKUP_ID_SCHEMA,
457 },
458 "backup-time": {
459 schema: BACKUP_TIME_SCHEMA,
460 },
461 },
462 )]
463 #[derive(Serialize, Deserialize)]
464 #[serde(rename_all = "kebab-case")]
465 /// Prune result.
466 pub struct PruneListItem {
467 pub backup_type: String, // enum
468 pub backup_id: String,
469 pub backup_time: i64,
470 /// Keep snapshot
471 pub keep: bool,
472 }
473
474 #[api()]
475 #[derive(Default, Serialize, Deserialize)]
476 /// Storage space usage information.
477 pub struct StorageStatus {
478 /// Total space (bytes).
479 pub total: u64,
480 /// Used space (bytes).
481 pub used: u64,
482 /// Available space (bytes).
483 pub avail: u64,
484 }
485
486 #[api()]
487 #[derive(Serialize, Deserialize, Default)]
488 /// Backup Type group/snapshot counts.
489 pub struct TypeCounts {
490 /// The number of groups of the type.
491 pub groups: u64,
492 /// The number of snapshots of the type.
493 pub snapshots: u64,
494 }
495
496 #[api(
497 properties: {
498 ct: {
499 type: TypeCounts,
500 optional: true,
501 },
502 host: {
503 type: TypeCounts,
504 optional: true,
505 },
506 vm: {
507 type: TypeCounts,
508 optional: true,
509 },
510 other: {
511 type: TypeCounts,
512 optional: true,
513 },
514 },
515 )]
516 #[derive(Serialize, Deserialize, Default)]
517 /// Counts of groups/snapshots per BackupType.
518 pub struct Counts {
519 /// The counts for CT backups
520 pub ct: Option<TypeCounts>,
521 /// The counts for Host backups
522 pub host: Option<TypeCounts>,
523 /// The counts for VM backups
524 pub vm: Option<TypeCounts>,
525 /// The counts for other backup types
526 pub other: Option<TypeCounts>,
527 }