]>
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 | ||
255f378a DM |
32 | const_regex!{ |
33 | pub IP_FORMAT_REGEX = IPRE!(); | |
34 | pub SHA256_HEX_REGEX = r"^[a-f0-9]{64}$"; // fixme: define in common_regex ? | |
35 | pub SYSTEMD_DATETIME_REGEX = r"^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$"; // fixme: define in common_regex ? | |
d0adf270 | 36 | |
da4a15a3 DM |
37 | pub PASSWORD_REGEX = r"^[[:^cntrl:]]*$"; // everything but control characters |
38 | ||
d0adf270 DM |
39 | /// Regex for safe identifiers. |
40 | /// | |
41 | /// This | |
42 | /// [article](https://dwheeler.com/essays/fixing-unix-linux-filenames.html) | |
43 | /// contains further information why it is reasonable to restict | |
44 | /// names this way. This is not only useful for filenames, but for | |
45 | /// any identifier command line tools work with. | |
163dc16c | 46 | pub PROXMOX_SAFE_ID_REGEX = concat!(r"^", PROXMOX_SAFE_ID_REGEX_STR!(), r"$"); |
454c13ed DM |
47 | |
48 | pub SINGLE_LINE_COMMENT_REGEX = r"^[[:^cntrl:]]*$"; | |
b25f313d DM |
49 | |
50 | pub HOSTNAME_REGEX = r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)$"; | |
51 | ||
ae62c4fe | 52 | pub DNS_NAME_REGEX = concat!(r"^", DNS_NAME!(), r"$"); |
b25f313d | 53 | |
ae62c4fe | 54 | pub DNS_NAME_OR_IP_REGEX = concat!(r"^", DNS_NAME!(), "|", IPRE!(), r"$"); |
163dc16c DM |
55 | |
56 | pub PROXMOX_USER_ID_REGEX = concat!(r"^", USER_NAME_REGEX_STR!(), r"@", PROXMOX_SAFE_ID_REGEX_STR!(), r"$"); | |
dcb8db66 | 57 | |
9765092e DM |
58 | pub PROXMOX_GROUP_ID_REGEX = concat!(r"^", GROUP_NAME_REGEX_STR!(), r"$"); |
59 | ||
dcb8db66 | 60 | pub CERT_FINGERPRINT_SHA256_REGEX = r"^(?:[0-9a-fA-F][0-9a-fA-F])(?::[0-9a-fA-F][0-9a-fA-F]){31}$"; |
ed3e60ae | 61 | |
9765092e | 62 | pub ACL_PATH_REGEX = concat!(r"^(?:/|", r"(?:/", PROXMOX_SAFE_ID_REGEX_STR!(), ")+", r")$"); |
255f378a | 63 | } |
4ebf0eab | 64 | |
255f378a DM |
65 | pub const SYSTEMD_DATETIME_FORMAT: ApiStringFormat = |
66 | ApiStringFormat::Pattern(&SYSTEMD_DATETIME_REGEX); | |
4ebf0eab | 67 | |
255f378a DM |
68 | pub const IP_FORMAT: ApiStringFormat = |
69 | ApiStringFormat::Pattern(&IP_FORMAT_REGEX); | |
bbf9e7e9 | 70 | |
255f378a DM |
71 | pub const PVE_CONFIG_DIGEST_FORMAT: ApiStringFormat = |
72 | ApiStringFormat::Pattern(&SHA256_HEX_REGEX); | |
73 | ||
dcb8db66 DM |
74 | pub const CERT_FINGERPRINT_SHA256_FORMAT: ApiStringFormat = |
75 | ApiStringFormat::Pattern(&CERT_FINGERPRINT_SHA256_REGEX); | |
76 | ||
d0adf270 DM |
77 | pub const PROXMOX_SAFE_ID_FORMAT: ApiStringFormat = |
78 | ApiStringFormat::Pattern(&PROXMOX_SAFE_ID_REGEX); | |
79 | ||
454c13ed DM |
80 | pub const SINGLE_LINE_COMMENT_FORMAT: ApiStringFormat = |
81 | ApiStringFormat::Pattern(&SINGLE_LINE_COMMENT_REGEX); | |
82 | ||
b25f313d DM |
83 | pub const HOSTNAME_FORMAT: ApiStringFormat = |
84 | ApiStringFormat::Pattern(&HOSTNAME_REGEX); | |
85 | ||
86 | pub const DNS_NAME_FORMAT: ApiStringFormat = | |
87 | ApiStringFormat::Pattern(&DNS_NAME_REGEX); | |
88 | ||
89 | pub const DNS_NAME_OR_IP_FORMAT: ApiStringFormat = | |
90 | ApiStringFormat::Pattern(&DNS_NAME_OR_IP_REGEX); | |
91 | ||
163dc16c DM |
92 | pub const PROXMOX_USER_ID_FORMAT: ApiStringFormat = |
93 | ApiStringFormat::Pattern(&PROXMOX_USER_ID_REGEX); | |
94 | ||
9765092e DM |
95 | pub const PROXMOX_GROUP_ID_FORMAT: ApiStringFormat = |
96 | ApiStringFormat::Pattern(&PROXMOX_GROUP_ID_REGEX); | |
97 | ||
7e7b781a DM |
98 | pub const PASSWORD_FORMAT: ApiStringFormat = |
99 | ApiStringFormat::Pattern(&PASSWORD_REGEX); | |
100 | ||
ed3e60ae DM |
101 | pub const ACL_PATH_FORMAT: ApiStringFormat = |
102 | ApiStringFormat::Pattern(&ACL_PATH_REGEX); | |
103 | ||
454c13ed | 104 | |
685e1334 DM |
105 | pub const PASSWORD_SCHEMA: Schema = StringSchema::new("Password.") |
106 | .format(&PASSWORD_FORMAT) | |
107 | .min_length(1) | |
b88f9c5b | 108 | .max_length(1024) |
685e1334 DM |
109 | .schema(); |
110 | ||
111 | pub const PBS_PASSWORD_SCHEMA: Schema = StringSchema::new("User Password.") | |
112 | .format(&PASSWORD_FORMAT) | |
113 | .min_length(5) | |
114 | .max_length(64) | |
115 | .schema(); | |
dcb8db66 DM |
116 | |
117 | pub const CERT_FINGERPRINT_SHA256_SCHEMA: Schema = StringSchema::new( | |
118 | "X509 certificate fingerprint (sha256)." | |
119 | ) | |
120 | .format(&CERT_FINGERPRINT_SHA256_FORMAT) | |
121 | .schema(); | |
122 | ||
002a191a | 123 | pub const PROXMOX_CONFIG_DIGEST_SCHEMA: Schema = StringSchema::new(r#"\ |
255f378a DM |
124 | Prevent changes if current configuration file has different SHA256 digest. |
125 | This can be used to prevent concurrent modifications. | |
126 | "# | |
127 | ) | |
128 | .format(&PVE_CONFIG_DIGEST_FORMAT) | |
129 | .schema(); | |
130 | ||
131 | ||
132 | pub const CHUNK_DIGEST_FORMAT: ApiStringFormat = | |
133 | ApiStringFormat::Pattern(&SHA256_HEX_REGEX); | |
134 | ||
135 | pub const CHUNK_DIGEST_SCHEMA: Schema = StringSchema::new("Chunk digest (SHA256).") | |
136 | .format(&CHUNK_DIGEST_FORMAT) | |
137 | .schema(); | |
138 | ||
139 | pub const NODE_SCHEMA: Schema = StringSchema::new("Node name (or 'localhost')") | |
140 | .format(&ApiStringFormat::VerifyFn(|node| { | |
141 | if node == "localhost" || node == proxmox::tools::nodename() { | |
142 | Ok(()) | |
143 | } else { | |
144 | bail!("no such node '{}'", node); | |
145 | } | |
146 | })) | |
147 | .schema(); | |
148 | ||
149 | pub const SEARCH_DOMAIN_SCHEMA: Schema = | |
150 | StringSchema::new("Search domain for host-name lookup.").schema(); | |
151 | ||
152 | pub const FIRST_DNS_SERVER_SCHEMA: Schema = | |
153 | StringSchema::new("First name server IP address.") | |
154 | .format(&IP_FORMAT) | |
155 | .schema(); | |
156 | ||
157 | pub const SECOND_DNS_SERVER_SCHEMA: Schema = | |
158 | StringSchema::new("Second name server IP address.") | |
159 | .format(&IP_FORMAT) | |
160 | .schema(); | |
161 | ||
162 | pub const THIRD_DNS_SERVER_SCHEMA: Schema = | |
163 | StringSchema::new("Third name server IP address.") | |
164 | .format(&IP_FORMAT) | |
165 | .schema(); | |
166 | ||
4b40148c DM |
167 | pub const TIME_ZONE_SCHEMA: Schema = StringSchema::new( |
168 | "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.") | |
169 | .format(&SINGLE_LINE_COMMENT_FORMAT) | |
170 | .min_length(2) | |
171 | .max_length(64) | |
172 | .schema(); | |
173 | ||
ca257c80 DM |
174 | pub const ACL_PATH_SCHEMA: Schema = StringSchema::new( |
175 | "Access control path.") | |
176 | .format(&ACL_PATH_FORMAT) | |
177 | .min_length(1) | |
178 | .max_length(128) | |
179 | .schema(); | |
180 | ||
181 | pub const ACL_PROPAGATE_SCHEMA: Schema = BooleanSchema::new( | |
182 | "Allow to propagate (inherit) permissions.") | |
183 | .default(true) | |
184 | .schema(); | |
185 | ||
186 | pub const ACL_UGID_TYPE_SCHEMA: Schema = StringSchema::new( | |
187 | "Type of 'ugid' property.") | |
188 | .format(&ApiStringFormat::Enum(&["user", "group"])) | |
189 | .schema(); | |
190 | ||
191 | pub const ACL_ROLE_SCHEMA: Schema = StringSchema::new( | |
192 | "Role.") | |
193 | .format(&ApiStringFormat::Enum(&[ | |
194 | "Admin", | |
195 | "Audit", | |
196 | "Datastore.Admin", | |
197 | "Datastore.Audit", | |
198 | "Datastore.User", | |
199 | "NoAccess", | |
200 | ])) | |
201 | .schema(); | |
202 | ||
255f378a DM |
203 | pub const BACKUP_ARCHIVE_NAME_SCHEMA: Schema = |
204 | StringSchema::new("Backup archive name.") | |
1ae5677d | 205 | .format(&PROXMOX_SAFE_ID_FORMAT) |
255f378a DM |
206 | .schema(); |
207 | ||
208 | pub const BACKUP_TYPE_SCHEMA: Schema = | |
209 | StringSchema::new("Backup type.") | |
210 | .format(&ApiStringFormat::Enum(&["vm", "ct", "host"])) | |
211 | .schema(); | |
212 | ||
213 | pub const BACKUP_ID_SCHEMA: Schema = | |
214 | StringSchema::new("Backup ID.") | |
1ae5677d | 215 | .format(&PROXMOX_SAFE_ID_FORMAT) |
255f378a DM |
216 | .schema(); |
217 | ||
218 | pub const BACKUP_TIME_SCHEMA: Schema = | |
219 | IntegerSchema::new("Backup time (Unix epoch.)") | |
220 | .minimum(1_547_797_308) | |
221 | .schema(); | |
5830c205 DM |
222 | |
223 | pub const UPID_SCHEMA: Schema = StringSchema::new("Unique Process/Task ID.") | |
224 | .max_length(256) | |
225 | .schema(); | |
66c49c21 DM |
226 | |
227 | pub const DATASTORE_SCHEMA: Schema = StringSchema::new("Datastore name.") | |
d0adf270 | 228 | .format(&PROXMOX_SAFE_ID_FORMAT) |
688fbe07 | 229 | .min_length(3) |
66c49c21 DM |
230 | .max_length(32) |
231 | .schema(); | |
fc189b19 | 232 | |
167971ed DM |
233 | pub const REMOTE_ID_SCHEMA: Schema = StringSchema::new("Remote ID.") |
234 | .format(&PROXMOX_SAFE_ID_FORMAT) | |
235 | .min_length(3) | |
236 | .max_length(32) | |
237 | .schema(); | |
238 | ||
454c13ed DM |
239 | pub const SINGLE_LINE_COMMENT_SCHEMA: Schema = StringSchema::new("Comment (single line).") |
240 | .format(&SINGLE_LINE_COMMENT_FORMAT) | |
241 | .schema(); | |
fc189b19 | 242 | |
b25f313d DM |
243 | pub const HOSTNAME_SCHEMA: Schema = StringSchema::new("Hostname (as defined in RFC1123).") |
244 | .format(&HOSTNAME_FORMAT) | |
245 | .schema(); | |
246 | ||
247 | pub const DNS_NAME_OR_IP_SCHEMA: Schema = StringSchema::new("DNS name or IP address.") | |
248 | .format(&DNS_NAME_OR_IP_FORMAT) | |
249 | .schema(); | |
250 | ||
163dc16c DM |
251 | pub const PROXMOX_AUTH_REALM_SCHEMA: Schema = StringSchema::new("Authentication domain ID") |
252 | .format(&PROXMOX_SAFE_ID_FORMAT) | |
253 | .min_length(3) | |
254 | .max_length(32) | |
255 | .schema(); | |
256 | ||
257 | pub const PROXMOX_USER_ID_SCHEMA: Schema = StringSchema::new("User ID") | |
258 | .format(&PROXMOX_USER_ID_FORMAT) | |
259 | .min_length(3) | |
260 | .max_length(64) | |
261 | .schema(); | |
262 | ||
9765092e DM |
263 | pub const PROXMOX_GROUP_ID_SCHEMA: Schema = StringSchema::new("Group ID") |
264 | .format(&PROXMOX_GROUP_ID_FORMAT) | |
265 | .min_length(3) | |
266 | .max_length(64) | |
267 | .schema(); | |
268 | ||
fc189b19 DM |
269 | |
270 | // Complex type definitions | |
271 | ||
b31c8019 DM |
272 | #[api( |
273 | properties: { | |
274 | "backup-type": { | |
275 | schema: BACKUP_TYPE_SCHEMA, | |
276 | }, | |
277 | "backup-id": { | |
278 | schema: BACKUP_ID_SCHEMA, | |
279 | }, | |
280 | "last-backup": { | |
281 | schema: BACKUP_TIME_SCHEMA, | |
282 | }, | |
283 | "backup-count": { | |
284 | type: Integer, | |
285 | }, | |
286 | files: { | |
287 | items: { | |
288 | schema: BACKUP_ARCHIVE_NAME_SCHEMA | |
289 | }, | |
290 | }, | |
291 | }, | |
292 | )] | |
293 | #[derive(Serialize, Deserialize)] | |
294 | #[serde(rename_all="kebab-case")] | |
295 | /// Basic information about a backup group. | |
296 | pub struct GroupListItem { | |
297 | pub backup_type: String, // enum | |
298 | pub backup_id: String, | |
299 | pub last_backup: i64, | |
300 | /// Number of contained snapshots | |
301 | pub backup_count: u64, | |
302 | /// List of contained archive files. | |
303 | pub files: Vec<String>, | |
304 | } | |
305 | ||
fc189b19 | 306 | #[api( |
fc189b19 DM |
307 | properties: { |
308 | "backup-type": { | |
309 | schema: BACKUP_TYPE_SCHEMA, | |
310 | }, | |
311 | "backup-id": { | |
312 | schema: BACKUP_ID_SCHEMA, | |
313 | }, | |
314 | "backup-time": { | |
315 | schema: BACKUP_TIME_SCHEMA, | |
316 | }, | |
71da3d6a DM |
317 | files: { |
318 | items: { | |
319 | schema: BACKUP_ARCHIVE_NAME_SCHEMA | |
320 | }, | |
321 | }, | |
fc189b19 DM |
322 | }, |
323 | )] | |
324 | #[derive(Serialize, Deserialize)] | |
325 | #[serde(rename_all="kebab-case")] | |
71da3d6a | 326 | /// Basic information about backup snapshot. |
fc189b19 DM |
327 | pub struct SnapshotListItem { |
328 | pub backup_type: String, // enum | |
329 | pub backup_id: String, | |
330 | pub backup_time: i64, | |
71da3d6a | 331 | /// List of contained archive files. |
fc189b19 | 332 | pub files: Vec<String>, |
71da3d6a | 333 | /// Overall snapshot size (sum of all archive sizes). |
fc189b19 DM |
334 | #[serde(skip_serializing_if="Option::is_none")] |
335 | pub size: Option<u64>, | |
336 | } | |
ff620a3d | 337 | |
09b1f7b2 DM |
338 | #[api( |
339 | properties: { | |
340 | "filename": { | |
341 | schema: BACKUP_ARCHIVE_NAME_SCHEMA, | |
342 | }, | |
343 | }, | |
344 | )] | |
345 | #[derive(Serialize, Deserialize)] | |
346 | #[serde(rename_all="kebab-case")] | |
347 | /// Basic information about archive files inside a backup snapshot. | |
348 | pub struct BackupContent { | |
349 | pub filename: String, | |
350 | /// Archive size (from backup manifest). | |
351 | #[serde(skip_serializing_if="Option::is_none")] | |
352 | pub size: Option<u64>, | |
353 | } | |
354 | ||
a92830dc DM |
355 | #[api( |
356 | properties: { | |
357 | "upid": { | |
358 | optional: true, | |
359 | schema: UPID_SCHEMA, | |
360 | }, | |
361 | }, | |
362 | )] | |
363 | #[derive(Clone, Serialize, Deserialize)] | |
364 | #[serde(rename_all="kebab-case")] | |
365 | /// Garbage collection status. | |
366 | pub struct GarbageCollectionStatus { | |
367 | pub upid: Option<String>, | |
368 | /// Number of processed index files. | |
369 | pub index_file_count: usize, | |
370 | /// Sum of bytes referred by index files. | |
371 | pub index_data_bytes: u64, | |
372 | /// Bytes used on disk. | |
373 | pub disk_bytes: u64, | |
374 | /// Chunks used on disk. | |
375 | pub disk_chunks: usize, | |
376 | /// Sum of removed bytes. | |
377 | pub removed_bytes: u64, | |
378 | /// Number of removed chunks. | |
379 | pub removed_chunks: usize, | |
cf459b19 DM |
380 | /// Sum of pending bytes (pending removal - kept for safety). |
381 | pub pending_bytes: u64, | |
382 | /// Number of pending chunks (pending removal - kept for safety). | |
383 | pub pending_chunks: usize, | |
a92830dc DM |
384 | } |
385 | ||
386 | impl Default for GarbageCollectionStatus { | |
387 | fn default() -> Self { | |
388 | GarbageCollectionStatus { | |
389 | upid: None, | |
390 | index_file_count: 0, | |
391 | index_data_bytes: 0, | |
392 | disk_bytes: 0, | |
393 | disk_chunks: 0, | |
394 | removed_bytes: 0, | |
395 | removed_chunks: 0, | |
cf459b19 DM |
396 | pending_bytes: 0, |
397 | pending_chunks: 0, | |
a92830dc DM |
398 | } |
399 | } | |
400 | } | |
401 | ||
402 | ||
1dc117bb DM |
403 | #[api()] |
404 | #[derive(Serialize, Deserialize)] | |
405 | /// Storage space usage information. | |
406 | pub struct StorageStatus { | |
407 | /// Total space (bytes). | |
408 | pub total: u64, | |
409 | /// Used space (bytes). | |
410 | pub used: u64, | |
411 | /// Available space (bytes). | |
412 | pub avail: u64, | |
413 | } | |
ff620a3d | 414 | |
99384f79 DM |
415 | #[api( |
416 | properties: { | |
417 | "upid": { schema: UPID_SCHEMA }, | |
418 | }, | |
419 | )] | |
420 | #[derive(Serialize, Deserialize)] | |
421 | /// Task properties. | |
422 | pub struct TaskListItem { | |
423 | pub upid: String, | |
424 | /// The node name where the task is running on. | |
425 | pub node: String, | |
426 | /// The Unix PID | |
427 | pub pid: i64, | |
428 | /// The task start time (Epoch) | |
429 | pub pstart: u64, | |
430 | /// The task start time (Epoch) | |
431 | pub starttime: i64, | |
432 | /// Worker type (arbitrary ASCII string) | |
433 | pub worker_type: String, | |
434 | /// Worker ID (arbitrary ASCII string) | |
435 | pub worker_id: Option<String>, | |
436 | /// The user who started the task | |
437 | pub user: String, | |
438 | /// The task end time (Epoch) | |
439 | #[serde(skip_serializing_if="Option::is_none")] | |
440 | pub endtime: Option<i64>, | |
441 | /// Task end status | |
442 | #[serde(skip_serializing_if="Option::is_none")] | |
443 | pub status: Option<String>, | |
444 | } | |
445 | ||
c357260d DM |
446 | #[api()] |
447 | #[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] | |
448 | #[serde(rename_all = "lowercase")] | |
449 | /// Interface configuration method | |
450 | pub enum NetworkConfigMethod { | |
451 | /// Configuration is done manually using other tools | |
452 | Manual, | |
453 | /// Define interfaces with statically allocated addresses. | |
454 | Static, | |
455 | /// Obtain an address via DHCP | |
456 | DHCP, | |
457 | /// Define the loopback interface. | |
458 | Loopback, | |
459 | } | |
460 | ||
461 | #[api( | |
462 | properties: { | |
463 | name: { | |
464 | type: String, | |
465 | min_length: 1, | |
466 | max_length: libc::IFNAMSIZ-1, | |
467 | }, | |
468 | method_v4: { | |
469 | type: NetworkConfigMethod, | |
470 | optional: true, | |
471 | }, | |
472 | method_v6: { | |
473 | type: NetworkConfigMethod, | |
474 | optional: true, | |
475 | }, | |
476 | options_v4: { | |
477 | description: "Option list (inet)", | |
478 | type: Array, | |
479 | items: { | |
480 | description: "Optional attribute.", | |
481 | type: String, | |
482 | }, | |
483 | }, | |
484 | options_v6: { | |
485 | description: "Option list (inet6)", | |
486 | type: Array, | |
487 | items: { | |
488 | description: "Optional attribute.", | |
489 | type: String, | |
490 | }, | |
491 | }, | |
492 | } | |
493 | )] | |
494 | #[derive(Debug, Serialize, Deserialize)] | |
495 | /// Network Interface configuration | |
496 | pub struct Interface { | |
497 | /// Autostart interface | |
498 | pub autostart: bool, | |
499 | /// Interface is a physical network device | |
500 | pub exists: bool, | |
501 | /// Interface is active (UP) | |
502 | pub active: bool, | |
503 | /// Interface name | |
504 | pub name: String, | |
505 | #[serde(skip_serializing_if="Option::is_none")] | |
506 | pub method_v4: Option<NetworkConfigMethod>, | |
507 | #[serde(skip_serializing_if="Option::is_none")] | |
508 | pub method_v6: Option<NetworkConfigMethod>, | |
509 | #[serde(skip_serializing_if="Option::is_none")] | |
510 | /// IPv4 address | |
511 | pub address_v4: Option<String>, | |
512 | #[serde(skip_serializing_if="Option::is_none")] | |
513 | /// IPv4 gateway | |
514 | pub gateway_v4: Option<String>, | |
515 | #[serde(skip_serializing_if="Option::is_none")] | |
516 | /// IPv4 netmask | |
517 | pub netmask_v4: Option<u8>, | |
518 | #[serde(skip_serializing_if="Option::is_none")] | |
519 | /// IPv6 address | |
520 | pub address_v6: Option<String>, | |
521 | #[serde(skip_serializing_if="Option::is_none")] | |
522 | /// IPv6 gateway | |
523 | pub gateway_v6: Option<String>, | |
524 | #[serde(skip_serializing_if="Option::is_none")] | |
525 | /// IPv6 netmask | |
526 | pub netmask_v6: Option<u8>, | |
527 | #[serde(skip_serializing_if="Vec::is_empty")] | |
528 | pub options_v4: Vec<String>, | |
529 | #[serde(skip_serializing_if="Vec::is_empty")] | |
530 | pub options_v6: Vec<String>, | |
531 | } | |
532 | ||
ff620a3d DM |
533 | // Regression tests |
534 | ||
dcb8db66 | 535 | #[test] |
ff329f97 | 536 | fn test_cert_fingerprint_schema() -> Result<(), anyhow::Error> { |
dcb8db66 DM |
537 | |
538 | let schema = CERT_FINGERPRINT_SHA256_SCHEMA; | |
539 | ||
540 | let invalid_fingerprints = [ | |
541 | "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", | |
542 | "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", | |
543 | "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", | |
544 | "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", | |
545 | "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", | |
546 | "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", | |
547 | ]; | |
548 | ||
549 | for fingerprint in invalid_fingerprints.iter() { | |
550 | if let Ok(_) = parse_simple_value(fingerprint, &schema) { | |
551 | bail!("test fingerprint '{}' failed - got Ok() while expection an error.", fingerprint); | |
552 | } | |
553 | } | |
554 | ||
555 | let valid_fingerprints = [ | |
556 | "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", | |
557 | "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", | |
558 | ]; | |
559 | ||
560 | for fingerprint in valid_fingerprints.iter() { | |
561 | let v = match parse_simple_value(fingerprint, &schema) { | |
562 | Ok(v) => v, | |
563 | Err(err) => { | |
564 | bail!("unable to parse fingerprint '{}' - {}", fingerprint, err); | |
565 | } | |
566 | }; | |
567 | ||
568 | if v != serde_json::json!(fingerprint) { | |
569 | bail!("unable to parse fingerprint '{}' - got wrong value {:?}", fingerprint, v); | |
570 | } | |
571 | } | |
572 | ||
573 | Ok(()) | |
574 | } | |
575 | ||
ff620a3d | 576 | #[test] |
ff329f97 | 577 | fn test_proxmox_user_id_schema() -> Result<(), anyhow::Error> { |
ff620a3d DM |
578 | |
579 | let schema = PROXMOX_USER_ID_SCHEMA; | |
580 | ||
581 | let invalid_user_ids = [ | |
582 | "x", // too short | |
583 | "xx", // too short | |
584 | "xxx", // no realm | |
585 | "xxx@", // no realm | |
586 | "xx x@test", // contains space | |
587 | "xx\nx@test", // contains control character | |
588 | "x:xx@test", // contains collon | |
589 | "xx/x@test", // contains slash | |
590 | "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@test", // too long | |
591 | ]; | |
592 | ||
593 | for name in invalid_user_ids.iter() { | |
594 | if let Ok(_) = parse_simple_value(name, &schema) { | |
595 | bail!("test userid '{}' failed - got Ok() while expection an error.", name); | |
596 | } | |
597 | } | |
598 | ||
599 | let valid_user_ids = [ | |
600 | "xxx@y", | |
601 | "name@y", | |
602 | "xxx@test-it.com", | |
603 | "xxx@_T_E_S_T-it.com", | |
604 | "x_x-x.x@test-it.com", | |
605 | ]; | |
606 | ||
607 | for name in valid_user_ids.iter() { | |
608 | let v = match parse_simple_value(name, &schema) { | |
609 | Ok(v) => v, | |
610 | Err(err) => { | |
611 | bail!("unable to parse userid '{}' - {}", name, err); | |
612 | } | |
613 | }; | |
614 | ||
615 | if v != serde_json::json!(name) { | |
616 | bail!("unable to parse userid '{}' - got wrong value {:?}", name, v); | |
617 | } | |
618 | } | |
619 | ||
620 | Ok(()) | |
621 | } |