]>
Commit | Line | Data |
---|---|---|
f7d4e4b5 | 1 | use anyhow::{bail}; |
fc189b19 | 2 | use ::serde::{Deserialize, Serialize}; |
4ebf0eab | 3 | |
9ea4bce4 WB |
4 | use proxmox::api::{api, schema::*}; |
5 | use proxmox::const_regex; | |
6 | use proxmox::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32}; | |
255f378a DM |
7 | |
8 | // File names: may not contain slashes, may not start with "." | |
9 | pub 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 |
19 | macro_rules! DNS_LABEL { () => (r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)") } |
20 | macro_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 | 27 | macro_rules! USER_NAME_REGEX_STR { () => (r"(?:[^\s:/[:cntrl:]]+)") } |
9765092e | 28 | macro_rules! GROUP_NAME_REGEX_STR { () => (USER_NAME_REGEX_STR!()) } |
163dc16c DM |
29 | |
30 | macro_rules! PROXMOX_SAFE_ID_REGEX_STR { () => (r"(?:[A-Za-z0-9_][A-Za-z0-9._\-]*)") } | |
31 | ||
76cf5208 DM |
32 | macro_rules! CIDR_V4_REGEX_STR { () => (concat!(r"(?:", IPV4RE!(), r"/\d{1,2})$")) } |
33 | macro_rules! CIDR_V6_REGEX_STR { () => (concat!(r"(?:", IPV6RE!(), r"/\d{1,3})$")) } | |
34 | ||
255f378a | 35 | const_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 |
74 | pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat = |
75 | ApiStringFormat::Pattern(&SYSTEMD_DATETIME_REGEX); | |
4ebf0eab | 76 | |
76cf5208 DM |
77 | pub const IP_V4_FORMAT: ApiStringFormat = |
78 | ApiStringFormat::Pattern(&IP_V4_REGEX); | |
79 | ||
80 | pub const IP_V6_FORMAT: ApiStringFormat = | |
81 | ApiStringFormat::Pattern(&IP_V6_REGEX); | |
82 | ||
255f378a | 83 | pub const IP_FORMAT: ApiStringFormat = |
76cf5208 | 84 | ApiStringFormat::Pattern(&IP_REGEX); |
bbf9e7e9 | 85 | |
255f378a DM |
86 | pub const PVE_CONFIG_DIGEST_FORMAT: ApiStringFormat = |
87 | ApiStringFormat::Pattern(&SHA256_HEX_REGEX); | |
88 | ||
dcb8db66 DM |
89 | pub const CERT_FINGERPRINT_SHA256_FORMAT: ApiStringFormat = |
90 | ApiStringFormat::Pattern(&CERT_FINGERPRINT_SHA256_REGEX); | |
91 | ||
d0adf270 DM |
92 | pub const PROXMOX_SAFE_ID_FORMAT: ApiStringFormat = |
93 | ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX); | |
94 | ||
454c13ed DM |
95 | pub const SINGLE_LINE_COMMENT_FORMAT: ApiStringFormat = |
96 | ApiStringFormat::Pattern(&SINGLE_LINE_COMMENT_REGEX); | |
97 | ||
b25f313d DM |
98 | pub const HOSTNAME_FORMAT: ApiStringFormat = |
99 | ApiStringFormat::Pattern(&HOSTNAME_REGEX); | |
100 | ||
101 | pub const DNS_NAME_FORMAT: ApiStringFormat = | |
102 | ApiStringFormat::Pattern(&DNS_NAME_REGEX); | |
103 | ||
104 | pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat = | |
105 | ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX); | |
106 | ||
163dc16c DM |
107 | pub const PROXMOX_USER_ID_FORMAT: ApiStringFormat = |
108 | ApiStringFormat::Pattern(&PROXMOX_USER_ID_REGEX); | |
109 | ||
9765092e DM |
110 | pub const PROXMOX_GROUP_ID_FORMAT: ApiStringFormat = |
111 | ApiStringFormat::Pattern(&PROXMOX_GROUP_ID_REGEX); | |
112 | ||
7e7b781a DM |
113 | pub const PASSWORD_FORMAT: ApiStringFormat = |
114 | ApiStringFormat::Pattern(&PASSWORD_REGEX); | |
115 | ||
ed3e60ae DM |
116 | pub const ACL_PATH_FORMAT: ApiStringFormat = |
117 | ApiStringFormat::Pattern(&ACL_PATH_REGEX); | |
118 | ||
68da20bf DM |
119 | pub const NETWORK_INTERFACE_FORMAT: ApiStringFormat = |
120 | ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX); | |
454c13ed | 121 | |
76cf5208 DM |
122 | pub const CIDR_V4_FORMAT: ApiStringFormat = |
123 | ApiStringFormat::Pattern(&CIDR_V4_REGEX); | |
124 | ||
125 | pub const CIDR_V6_FORMAT: ApiStringFormat = | |
126 | ApiStringFormat::Pattern(&CIDR_V6_REGEX); | |
127 | ||
128 | pub const CIDR_FORMAT: ApiStringFormat = | |
129 | ApiStringFormat::Pattern(&CIDR_REGEX); | |
130 | ||
131 | ||
685e1334 DM |
132 | pub 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 | ||
138 | pub 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 | |
144 | pub const CERT_FINGERPRINT_SHA256_SCHEMA: Schema = StringSchema::new( | |
145 | "X509 certificate fingerprint (sha256)." | |
146 | ) | |
147 | .format(&CERT_FINGERPRINT_SHA256_FORMAT) | |
148 | .schema(); | |
149 | ||
002a191a | 150 | pub const PROXMOX_CONFIG_DIGEST_SCHEMA: Schema = StringSchema::new(r#"\ |
255f378a DM |
151 | Prevent changes if current configuration file has different SHA256 digest. |
152 | This can be used to prevent concurrent modifications. | |
153 | "# | |
154 | ) | |
155 | .format(&PVE_CONFIG_DIGEST_FORMAT) | |
156 | .schema(); | |
157 | ||
158 | ||
159 | pub const CHUNK_DIGEST_FORMAT: ApiStringFormat = | |
160 | ApiStringFormat::Pattern(&SHA256_HEX_REGEX); | |
161 | ||
162 | pub const CHUNK_DIGEST_SCHEMA: Schema = StringSchema::new("Chunk digest (SHA256).") | |
163 | .format(&CHUNK_DIGEST_FORMAT) | |
164 | .schema(); | |
165 | ||
166 | pub 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 | ||
176 | pub const SEARCH_DOMAIN_SCHEMA: Schema = | |
177 | StringSchema::new("Search domain for host-name lookup.").schema(); | |
178 | ||
179 | pub const FIRST_DNS_SERVER_SCHEMA: Schema = | |
180 | StringSchema::new("First name server IP address.") | |
181 | .format(&IP_FORMAT) | |
182 | .schema(); | |
183 | ||
184 | pub const SECOND_DNS_SERVER_SCHEMA: Schema = | |
185 | StringSchema::new("Second name server IP address.") | |
186 | .format(&IP_FORMAT) | |
187 | .schema(); | |
188 | ||
189 | pub const THIRD_DNS_SERVER_SCHEMA: Schema = | |
190 | StringSchema::new("Third name server IP address.") | |
191 | .format(&IP_FORMAT) | |
192 | .schema(); | |
193 | ||
76cf5208 DM |
194 | pub const IP_V4_SCHEMA: Schema = |
195 | StringSchema::new("IPv4 address.") | |
196 | .format(&IP_V4_FORMAT) | |
197 | .max_length(15) | |
198 | .schema(); | |
199 | ||
200 | pub const IP_V6_SCHEMA: Schema = | |
201 | StringSchema::new("IPv6 address.") | |
202 | .format(&IP_V6_FORMAT) | |
203 | .max_length(39) | |
204 | .schema(); | |
205 | ||
206 | pub const IP_SCHEMA: Schema = | |
207 | StringSchema::new("IP (IPv4 or IPv6) address.") | |
208 | .format(&IP_FORMAT) | |
209 | .max_length(39) | |
210 | .schema(); | |
211 | ||
212 | pub 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 | ||
218 | pub 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 | ||
224 | pub 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 |
230 | pub 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 |
237 | pub 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 | ||
244 | pub const ACL_PROPAGATE_SCHEMA: Schema = BooleanSchema::new( | |
245 | "Allow to propagate (inherit) permissions.") | |
246 | .default(true) | |
247 | .schema(); | |
248 | ||
249 | pub 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 |
256 | pub const BACKUP_ARCHIVE_NAME_SCHEMA: Schema = |
257 | StringSchema::new("Backup archive name.") | |
1ae5677d | 258 | .format(&PROXMOX_SAFE_ID_FORMAT) |
255f378a DM |
259 | .schema(); |
260 | ||
261 | pub 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 | ||
269 | pub const BACKUP_ID_SCHEMA: Schema = | |
270 | StringSchema::new("Backup ID.") | |
1ae5677d | 271 | .format(&PROXMOX_SAFE_ID_FORMAT) |
255f378a DM |
272 | .schema(); |
273 | ||
274 | pub const BACKUP_TIME_SCHEMA: Schema = | |
275 | IntegerSchema::new("Backup time (Unix epoch.)") | |
276 | .minimum(1_547_797_308) | |
277 | .schema(); | |
5830c205 DM |
278 | |
279 | pub const UPID_SCHEMA: Schema = StringSchema::new("Unique Process/Task ID.") | |
280 | .max_length(256) | |
281 | .schema(); | |
66c49c21 DM |
282 | |
283 | pub 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 |
289 | pub 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 |
295 | pub const SINGLE_LINE_COMMENT_SCHEMA: Schema = StringSchema::new("Comment (single line).") |
296 | .format(&SINGLE_LINE_COMMENT_FORMAT) | |
297 | .schema(); | |
fc189b19 | 298 | |
b25f313d DM |
299 | pub const HOSTNAME_SCHEMA: Schema = StringSchema::new("Hostname (as defined in RFC1123).") |
300 | .format(&HOSTNAME_FORMAT) | |
301 | .schema(); | |
302 | ||
303 | pub 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 |
307 | pub 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 | ||
313 | pub 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 |
319 | pub 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. | |
352 | pub 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 |
383 | pub 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. | |
404 | pub 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. | |
422 | pub 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 | ||
442 | impl 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. | |
462 | pub 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. | |
478 | pub 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. | |
506 | pub 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 | |
517 | pub 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 | |
532 | pub 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 |
551 | pub 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 |
557 | pub 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 | |
615 | pub 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 | 665 | fn 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 | 706 | fn 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 | } |