]> git.proxmox.com Git - proxmox-backup.git/blame - pbs-api-types/src/lib.rs
move pbs-tools/src/percent_encoding.rs to pbs-api-types/src/percent_encoding.rs
[proxmox-backup.git] / pbs-api-types / src / lib.rs
CommitLineData
155f657f
WB
1//! Basic API types used by most of the PBS code.
2
c23192d3 3use serde::{Deserialize, Serialize};
8cc3760e 4use anyhow::bail;
c23192d3 5
25877d05 6pub mod common_regex;
577095e2 7pub mod percent_encoding;
25877d05 8
6ef1b649
WB
9use proxmox_schema::{
10 api, const_regex, ApiStringFormat, ApiType, ArraySchema, Schema, StringSchema, ReturnType,
11};
15cc41b6 12use proxmox_time::parse_daily_duration;
86fb3877 13
bfff4eaa
WB
14#[rustfmt::skip]
15#[macro_export]
16macro_rules! PROXMOX_SAFE_ID_REGEX_STR { () => { r"(?:[A-Za-z0-9_][A-Za-z0-9._\-]*)" }; }
17
18#[rustfmt::skip]
19#[macro_export]
20macro_rules! BACKUP_ID_RE { () => (r"[A-Za-z0-9_][A-Za-z0-9._\-]*") }
21
22#[rustfmt::skip]
23#[macro_export]
24macro_rules! BACKUP_TYPE_RE { () => (r"(?:host|vm|ct)") }
25
26#[rustfmt::skip]
27#[macro_export]
28macro_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") }
29
30#[rustfmt::skip]
31#[macro_export]
32macro_rules! SNAPSHOT_PATH_REGEX_STR {
33 () => (
34 concat!(r"(", BACKUP_TYPE_RE!(), ")/(", BACKUP_ID_RE!(), ")/(", BACKUP_TIME_RE!(), r")")
35 );
36}
37
8cc3760e
DM
38mod acl;
39pub use acl::*;
40
41mod datastore;
42pub use datastore::*;
43
a58a5cf7
TL
44mod human_byte;
45pub use human_byte::HumanByte;
46
e3619d41
DM
47mod jobs;
48pub use jobs::*;
49
45d5d873
DM
50mod key_derivation;
51pub use key_derivation::{Kdf, KeyInfo};
52
6f422880
DM
53mod network;
54pub use network::*;
55
751f6b61
WB
56#[macro_use]
57mod userid;
58pub use userid::Authid;
59pub use userid::Userid;
60pub use userid::{Realm, RealmRef};
61pub use userid::{Tokenname, TokennameRef};
62pub use userid::{Username, UsernameRef};
63pub use userid::{PROXMOX_GROUP_ID_SCHEMA, PROXMOX_TOKEN_ID_SCHEMA, PROXMOX_TOKEN_NAME_SCHEMA};
64
2b7f8dd5
WB
65#[macro_use]
66mod user;
b65dfff5 67pub use user::*;
2b7f8dd5 68
6ef1b649 69pub use proxmox_schema::upid::*;
95f9d67c 70
ea584a75 71mod crypto;
c42a5479 72pub use crypto::{CryptMode, Fingerprint, bytes_as_fingerprint};
ea584a75 73
013b1e8b
WB
74pub mod file_restore;
75
10beed11
DM
76mod openid;
77pub use openid::*;
78
6afdda88
DM
79mod remote;
80pub use remote::*;
81
1ce8e905
DM
82mod tape;
83pub use tape::*;
84
bfd12e87
DM
85mod traffic_control;
86pub use traffic_control::*;
87
8cc3760e
DM
88mod zfs;
89pub use zfs::*;
90
91
75f83c6a
WB
92#[rustfmt::skip]
93#[macro_use]
94mod local_macros {
95 macro_rules! DNS_LABEL { () => (r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") }
96 macro_rules! DNS_NAME { () => (concat!(r"(?:(?:", DNS_LABEL!() , r"\.)*", DNS_LABEL!(), ")")) }
97 macro_rules! CIDR_V4_REGEX_STR { () => (concat!(r"(?:", IPV4RE!(), r"/\d{1,2})$")) }
98 macro_rules! CIDR_V6_REGEX_STR { () => (concat!(r"(?:", IPV6RE!(), r"/\d{1,3})$")) }
99 macro_rules! DNS_ALIAS_LABEL { () => (r"(?:[a-zA-Z0-9_](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") }
100 macro_rules! DNS_ALIAS_NAME {
101 () => (concat!(r"(?:(?:", DNS_ALIAS_LABEL!() , r"\.)*", DNS_ALIAS_LABEL!(), ")"))
102 }
103}
104
86fb3877 105const_regex! {
75f83c6a
WB
106 pub IP_V4_REGEX = concat!(r"^", IPV4RE!(), r"$");
107 pub IP_V6_REGEX = concat!(r"^", IPV6RE!(), r"$");
108 pub IP_REGEX = concat!(r"^", IPRE!(), r"$");
109 pub CIDR_V4_REGEX = concat!(r"^", CIDR_V4_REGEX_STR!(), r"$");
110 pub CIDR_V6_REGEX = concat!(r"^", CIDR_V6_REGEX_STR!(), r"$");
111 pub CIDR_REGEX = concat!(r"^(?:", CIDR_V4_REGEX_STR!(), "|", CIDR_V6_REGEX_STR!(), r")$");
112 pub HOSTNAME_REGEX = r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)$";
113 pub DNS_NAME_REGEX = concat!(r"^", DNS_NAME!(), r"$");
114 pub DNS_ALIAS_REGEX = concat!(r"^", DNS_ALIAS_NAME!(), r"$");
115 pub DNS_NAME_OR_IP_REGEX = concat!(r"^(?:", DNS_NAME!(), "|", IPRE!(), r")$");
116
117 pub SHA256_HEX_REGEX = r"^[a-f0-9]{64}$"; // fixme: define in common_regex ?
118
119 pub PASSWORD_REGEX = r"^[[:^cntrl:]]*$"; // everything but control characters
120
121 pub UUID_REGEX = r"^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$";
122
8cc3760e 123 pub SYSTEMD_DATETIME_REGEX = r"^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$"; // fixme: define in common_regex ?
bfff4eaa 124
86fb3877 125 pub FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$";
3c430e9a
WB
126
127 /// Regex for safe identifiers.
128 ///
129 /// This
130 /// [article](https://dwheeler.com/essays/fixing-unix-linux-filenames.html)
131 /// contains further information why it is reasonable to restict
132 /// names this way. This is not only useful for filenames, but for
133 /// any identifier command line tools work with.
134 pub PROXMOX_SAFE_ID_REGEX = concat!(r"^", PROXMOX_SAFE_ID_REGEX_STR!(), r"$");
135
136 pub SINGLE_LINE_COMMENT_REGEX = r"^[[:^cntrl:]]*$";
75f83c6a
WB
137
138 pub BACKUP_REPO_URL_REGEX = concat!(
139 r"^^(?:(?:(",
140 USER_ID_REGEX_STR!(), "|", APITOKEN_ID_REGEX_STR!(),
141 ")@)?(",
142 DNS_NAME!(), "|", IPRE_BRACKET!(),
143 "):)?(?:([0-9]{1,5}):)?(", PROXMOX_SAFE_ID_REGEX_STR!(), r")$"
144 );
4c1b7761
WB
145
146 pub BLOCKDEVICE_NAME_REGEX = r"^(:?(:?h|s|x?v)d[a-z]+)|(:?nvme\d+n\d+)$";
8cc3760e 147 pub SUBSCRIPTION_KEY_REGEX = concat!(r"^pbs(?:[cbsp])-[0-9a-f]{10}$");
86fb3877
WB
148}
149
75f83c6a
WB
150pub const IP_V4_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&IP_V4_REGEX);
151pub const IP_V6_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&IP_V6_REGEX);
152pub const IP_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&IP_REGEX);
153pub const CIDR_V4_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_V4_REGEX);
154pub const CIDR_V6_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_V6_REGEX);
155pub const CIDR_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&CIDR_REGEX);
8cc3760e
DM
156pub const PVE_CONFIG_DIGEST_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&SHA256_HEX_REGEX);
157pub const PASSWORD_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&PASSWORD_REGEX);
158pub const UUID_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&UUID_REGEX);
159pub const BLOCKDEVICE_NAME_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&BLOCKDEVICE_NAME_REGEX);
160pub const SUBSCRIPTION_KEY_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&SUBSCRIPTION_KEY_REGEX);
161pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&SYSTEMD_DATETIME_REGEX);
162pub const HOSTNAME_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&HOSTNAME_REGEX);
163
164pub const DNS_ALIAS_FORMAT: ApiStringFormat =
165 ApiStringFormat::Pattern(&DNS_ALIAS_REGEX);
166
bfd12e87
DM
167pub const DAILY_DURATION_FORMAT: ApiStringFormat =
168 ApiStringFormat::VerifyFn(|s| parse_daily_duration(s).map(drop));
169
8cc3760e
DM
170pub const SEARCH_DOMAIN_SCHEMA: Schema =
171 StringSchema::new("Search domain for host-name lookup.").schema();
172
173pub const FIRST_DNS_SERVER_SCHEMA: Schema =
174 StringSchema::new("First name server IP address.")
175 .format(&IP_FORMAT)
176 .schema();
177
178pub const SECOND_DNS_SERVER_SCHEMA: Schema =
179 StringSchema::new("Second name server IP address.")
180 .format(&IP_FORMAT)
181 .schema();
182
183pub const THIRD_DNS_SERVER_SCHEMA: Schema =
184 StringSchema::new("Third name server IP address.")
185 .format(&IP_FORMAT)
186 .schema();
187
188pub const HOSTNAME_SCHEMA: Schema = StringSchema::new("Hostname (as defined in RFC1123).")
189 .format(&HOSTNAME_FORMAT)
190 .schema();
75f83c6a 191
6afdda88
DM
192pub const DNS_NAME_FORMAT: ApiStringFormat =
193 ApiStringFormat::Pattern(&DNS_NAME_REGEX);
194
195pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat =
196 ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX);
197
198pub const DNS_NAME_OR_IP_SCHEMA: Schema = StringSchema::new("DNS name or IP address.")
199 .format(&DNS_NAME_OR_IP_FORMAT)
200 .schema();
201
8cc3760e
DM
202pub const NODE_SCHEMA: Schema = StringSchema::new("Node name (or 'localhost')")
203 .format(&ApiStringFormat::VerifyFn(|node| {
25877d05 204 if node == "localhost" || node == proxmox_sys::nodename() {
8cc3760e
DM
205 Ok(())
206 } else {
207 bail!("no such node '{}'", node);
208 }
209 }))
ea584a75 210 .schema();
8cc3760e
DM
211
212pub const TIME_ZONE_SCHEMA: Schema = StringSchema::new(
213 "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.")
214 .format(&SINGLE_LINE_COMMENT_FORMAT)
215 .min_length(2)
216 .max_length(64)
ea584a75
WB
217 .schema();
218
8cc3760e
DM
219pub const BLOCKDEVICE_NAME_SCHEMA: Schema = StringSchema::new("Block device name (/sys/block/<name>).")
220 .format(&BLOCKDEVICE_NAME_FORMAT)
ea584a75 221 .min_length(3)
8cc3760e
DM
222 .max_length(64)
223 .schema();
224
225pub const DISK_ARRAY_SCHEMA: Schema = ArraySchema::new(
226 "Disk name list.", &BLOCKDEVICE_NAME_SCHEMA)
227 .schema();
228
229pub const DISK_LIST_SCHEMA: Schema = StringSchema::new(
230 "A list of disk names, comma separated.")
231 .format(&ApiStringFormat::PropertyString(&DISK_ARRAY_SCHEMA))
232 .schema();
233
234pub const PASSWORD_SCHEMA: Schema = StringSchema::new("Password.")
235 .format(&PASSWORD_FORMAT)
236 .min_length(1)
237 .max_length(1024)
238 .schema();
239
240pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.")
241 .format(&PASSWORD_FORMAT)
242 .min_length(5)
243 .max_length(64)
ea584a75
WB
244 .schema();
245
21211748
DM
246pub const REALM_ID_SCHEMA: Schema = StringSchema::new("Realm name.")
247 .format(&PROXMOX_SAFE_ID_FORMAT)
248 .min_length(2)
249 .max_length(32)
250 .schema();
251
86fb3877
WB
252pub const FINGERPRINT_SHA256_FORMAT: ApiStringFormat =
253 ApiStringFormat::Pattern(&FINGERPRINT_SHA256_REGEX);
254
255pub const CERT_FINGERPRINT_SHA256_SCHEMA: Schema =
256 StringSchema::new("X509 certificate fingerprint (sha256).")
257 .format(&FINGERPRINT_SHA256_FORMAT)
258 .schema();
3c430e9a
WB
259
260pub const PROXMOX_SAFE_ID_FORMAT: ApiStringFormat =
261 ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX);
262
263pub const SINGLE_LINE_COMMENT_FORMAT: ApiStringFormat =
264 ApiStringFormat::Pattern(&SINGLE_LINE_COMMENT_REGEX);
265
266pub const SINGLE_LINE_COMMENT_SCHEMA: Schema = StringSchema::new("Comment (single line).")
267 .format(&SINGLE_LINE_COMMENT_FORMAT)
268 .schema();
bfff4eaa 269
8cc3760e
DM
270pub const SUBSCRIPTION_KEY_SCHEMA: Schema = StringSchema::new("Proxmox Backup Server subscription key.")
271 .format(&SUBSCRIPTION_KEY_FORMAT)
272 .min_length(15)
273 .max_length(16)
274 .schema();
275
276pub const SERVICE_ID_SCHEMA: Schema = StringSchema::new("Service ID.")
277 .max_length(256)
278 .schema();
279
2b7f8dd5
WB
280pub const PROXMOX_CONFIG_DIGEST_SCHEMA: Schema = StringSchema::new(
281 "Prevent changes if current configuration file has different \
282 SHA256 digest. This can be used to prevent concurrent \
283 modifications.",
284)
285.format(&PVE_CONFIG_DIGEST_FORMAT)
286.schema();
287
75f83c6a
WB
288/// API schema format definition for repository URLs
289pub const BACKUP_REPO_URL: ApiStringFormat = ApiStringFormat::Pattern(&BACKUP_REPO_URL_REGEX);
290
75f83c6a 291
ea584a75
WB
292// Complex type definitions
293
51ec8a3c
WB
294
295#[api()]
296#[derive(Default, Serialize, Deserialize)]
297/// Storage space usage information.
298pub struct StorageStatus {
299 /// Total space (bytes).
300 pub total: u64,
301 /// Used space (bytes).
302 pub used: u64,
303 /// Available space (bytes).
304 pub avail: u64,
305}
306
eb5e0ae6
WB
307pub const PASSWORD_HINT_SCHEMA: Schema = StringSchema::new("Password hint.")
308 .format(&SINGLE_LINE_COMMENT_FORMAT)
309 .min_length(1)
310 .max_length(64)
311 .schema();
312
313
314#[api]
315#[derive(Deserialize, Serialize)]
316/// RSA public key information
317pub struct RsaPubKeyInfo {
318 /// Path to key (if stored in a file)
319 #[serde(skip_serializing_if="Option::is_none")]
320 pub path: Option<String>,
321 /// RSA exponent
322 pub exponent: String,
323 /// Hex-encoded RSA modulus
324 pub modulus: String,
325 /// Key (modulus) length in bits
326 pub length: usize,
327}
328
329impl std::convert::TryFrom<openssl::rsa::Rsa<openssl::pkey::Public>> for RsaPubKeyInfo {
330 type Error = anyhow::Error;
331
332 fn try_from(value: openssl::rsa::Rsa<openssl::pkey::Public>) -> Result<Self, Self::Error> {
333 let modulus = value.n().to_hex_str()?.to_string();
334 let exponent = value.e().to_dec_str()?.to_string();
335 let length = value.size() as usize * 8;
336
337 Ok(Self {
338 path: None,
339 exponent,
340 modulus,
341 length,
342 })
343 }
344}
7b570c17 345
e3619d41
DM
346#[api()]
347#[derive(Debug, Clone, Serialize, Deserialize)]
348#[serde(rename_all = "PascalCase")]
349/// Describes a package for which an update is available.
350pub struct APTUpdateInfo {
351 /// Package name
352 pub package: String,
353 /// Package title
354 pub title: String,
355 /// Package architecture
356 pub arch: String,
357 /// Human readable package description
358 pub description: String,
359 /// New version to be updated to
360 pub version: String,
361 /// Old version currently installed
362 pub old_version: String,
363 /// Package origin
364 pub origin: String,
365 /// Package priority in human-readable form
366 pub priority: String,
367 /// Package section
368 pub section: String,
369 /// URL under which the package's changelog can be retrieved
370 pub change_log_url: String,
371 /// Custom extra field for additional package information
372 #[serde(skip_serializing_if="Option::is_none")]
373 pub extra_info: Option<String>,
374}
8cc3760e 375
8cc3760e
DM
376
377#[api()]
378#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
379#[serde(rename_all = "lowercase")]
380/// Node Power command type.
381pub enum NodePowerCommand {
382 /// Restart the server
383 Reboot,
384 /// Shutdown the server
385 Shutdown,
386}
81867f05
DM
387
388
389#[api()]
390#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
391#[serde(rename_all = "lowercase")]
392pub enum TaskStateType {
393 /// Ok
394 OK,
395 /// Warning
396 Warning,
397 /// Error
398 Error,
399 /// Unknown
400 Unknown,
401}
402
403#[api(
404 properties: {
405 upid: { schema: UPID::API_SCHEMA },
406 },
407)]
408#[derive(Serialize, Deserialize)]
409/// Task properties.
410pub struct TaskListItem {
411 pub upid: String,
412 /// The node name where the task is running on.
413 pub node: String,
414 /// The Unix PID
415 pub pid: i64,
416 /// The task start time (Epoch)
417 pub pstart: u64,
418 /// The task start time (Epoch)
419 pub starttime: i64,
420 /// Worker type (arbitrary ASCII string)
421 pub worker_type: String,
422 /// Worker ID (arbitrary ASCII string)
423 pub worker_id: Option<String>,
424 /// The authenticated entity who started the task
425 pub user: String,
426 /// The task end time (Epoch)
427 #[serde(skip_serializing_if="Option::is_none")]
428 pub endtime: Option<i64>,
429 /// Task end status
430 #[serde(skip_serializing_if="Option::is_none")]
431 pub status: Option<String>,
432}
433
434pub const NODE_TASKS_LIST_TASKS_RETURN_TYPE: ReturnType = ReturnType {
435 optional: false,
436 schema: &ArraySchema::new(
437 "A list of tasks.",
438 &TaskListItem::API_SCHEMA,
439 ).schema(),
440};
d1c3bc53 441
c68fa58a
DM
442#[api()]
443#[derive(Copy, Clone, Serialize, Deserialize)]
444#[serde(rename_all = "UPPERCASE")]
445/// RRD consolidation mode
446pub enum RRDMode {
447 /// Maximum
448 Max,
449 /// Average
450 Average,
451}
452
453#[api()]
454#[derive(Copy, Clone, Serialize, Deserialize)]
455#[serde(rename_all = "lowercase")]
456/// RRD time frame
457pub enum RRDTimeFrame {
458 /// Hour
459 Hour,
460 /// Day
461 Day,
462 /// Week
463 Week,
464 /// Month
465 Month,
466 /// Year
467 Year,
468 /// Decade (10 years)
469 Decade,
470}