1use std::fmt;
4
5use crate::{Diagnostic, DiagnosticCode, Label, PodmanLensResult, SensitiveInputReference};
6
7const MAX_ITEMS: usize = 64;
8const MAX_BYTES: usize = 4096;
9
10#[derive(Clone, Debug, Eq, PartialEq)]
12pub struct PublicHealthCommand(String);
13
14impl PublicHealthCommand {
15 pub fn new(value: impl Into<String>) -> PodmanLensResult<Self> {
21 let value = value.into();
22 if value.is_empty() || !valid_text(&value) {
23 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
24 }
25 Ok(Self(value))
26 }
27
28 #[must_use]
30 pub fn as_str(&self) -> &str {
31 &self.0
32 }
33}
34
35#[derive(Clone, Eq, PartialEq)]
37pub struct SensitiveInlineHealthCommand(String);
38
39impl SensitiveInlineHealthCommand {
40 pub fn new(value: impl Into<String>) -> PodmanLensResult<Self> {
46 let value = value.into();
47 if value.is_empty() || !valid_text(&value) {
48 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
49 }
50 Ok(Self(value))
51 }
52}
53
54impl fmt::Debug for SensitiveInlineHealthCommand {
55 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
56 formatter.write_str("SensitiveInlineHealthCommand([redacted])")
57 }
58}
59
60#[derive(Clone, Debug, Eq, PartialEq)]
62pub struct PublicHealthArgumentArray(Vec<String>);
63
64impl PublicHealthArgumentArray {
65 pub fn new<I, S>(arguments: I) -> PodmanLensResult<Self>
71 where
72 I: IntoIterator<Item = S>,
73 S: Into<String>,
74 {
75 let arguments = arguments.into_iter().map(Into::into).collect::<Vec<_>>();
76 validate_arguments(&arguments)?;
77 Ok(Self(arguments))
78 }
79
80 #[must_use]
82 pub fn values(&self) -> &[String] {
83 &self.0
84 }
85}
86
87#[derive(Clone, Eq, PartialEq)]
89pub struct SensitiveInlineHealthArgumentArray(Vec<String>);
90
91impl SensitiveInlineHealthArgumentArray {
92 pub fn new<I, S>(arguments: I) -> PodmanLensResult<Self>
98 where
99 I: IntoIterator<Item = S>,
100 S: Into<String>,
101 {
102 let arguments = arguments.into_iter().map(Into::into).collect::<Vec<_>>();
103 validate_arguments(&arguments)?;
104 Ok(Self(arguments))
105 }
106}
107
108impl fmt::Debug for SensitiveInlineHealthArgumentArray {
109 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
110 formatter.write_str("SensitiveInlineHealthArgumentArray([redacted])")
111 }
112}
113
114#[derive(Clone, Debug, Eq, PartialEq)]
116#[non_exhaustive]
117pub enum HealthCommand {
118 Shell(PublicHealthCommand),
120 Exec(PublicHealthArgumentArray),
122 SensitiveInlineShell(SensitiveInlineHealthCommand),
124 SensitiveInlineExec(SensitiveInlineHealthArgumentArray),
126 ExternalShell(SensitiveInputReference),
128 ExternalExec(SensitiveInputReference),
130}
131
132#[derive(Clone, Copy, Debug, Eq, PartialEq)]
134pub struct HealthDuration(i64);
135
136impl HealthDuration {
137 pub fn new(nanoseconds: u64) -> PodmanLensResult<Self> {
143 if nanoseconds == 0 || nanoseconds > i64::MAX as u64 {
144 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
145 }
146 Ok(Self(i64::try_from(nanoseconds).map_err(|_| {
147 Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent)
148 })?))
149 }
150
151 #[must_use]
153 pub const fn nanoseconds(&self) -> i64 {
154 self.0
155 }
156}
157
158#[derive(Clone, Copy, Debug, Eq, PartialEq)]
160pub struct HealthRetries(u32);
161
162impl HealthRetries {
163 pub const fn new(value: u32) -> PodmanLensResult<Self> {
169 if value == 0 || value > i32::MAX as u32 {
170 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
171 }
172 Ok(Self(value))
173 }
174 #[must_use]
176 pub const fn value(&self) -> u32 {
177 self.0
178 }
179}
180
181#[derive(Clone, Copy, Debug, Eq, PartialEq)]
183pub struct StartupHealthRetries(u32);
184
185impl StartupHealthRetries {
186 pub const fn new(value: u32) -> PodmanLensResult<Self> {
192 if value > i32::MAX as u32 {
193 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
194 }
195 Ok(Self(value))
196 }
197 #[must_use]
199 pub const fn value(&self) -> u32 {
200 self.0
201 }
202}
203
204#[derive(Clone, Copy, Debug, Eq, PartialEq)]
206pub struct StartupHealthSuccesses(u32);
207
208impl StartupHealthSuccesses {
209 pub const fn new(value: u32) -> PodmanLensResult<Self> {
215 if value > i32::MAX as u32 {
216 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
217 }
218 Ok(Self(value))
219 }
220 #[must_use]
222 pub const fn value(&self) -> u32 {
223 self.0
224 }
225}
226
227#[derive(Clone, Copy, Debug, Eq, PartialEq)]
229#[non_exhaustive]
230pub enum HealthInterval {
231 Disabled,
233 Every(HealthDuration),
235}
236
237#[derive(Clone, Copy, Debug, Eq, PartialEq)]
239pub struct HealthTimeout(HealthDuration);
240
241impl HealthTimeout {
242 pub fn new(nanoseconds: u64) -> PodmanLensResult<Self> {
248 if nanoseconds < 1_000_000_000 {
249 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
250 }
251 Ok(Self(HealthDuration::new(nanoseconds)?))
252 }
253 #[must_use]
255 pub const fn nanoseconds(&self) -> i64 {
256 self.0.nanoseconds()
257 }
258}
259
260#[derive(Clone, Copy, Debug, Eq, PartialEq)]
262pub struct HealthStartPeriod(i64);
263
264impl HealthStartPeriod {
265 pub fn new(nanoseconds: u64) -> PodmanLensResult<Self> {
271 if nanoseconds > i64::MAX as u64 {
272 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
273 }
274 Ok(Self(i64::try_from(nanoseconds).map_err(|_| {
275 Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent)
276 })?))
277 }
278 #[must_use]
280 pub const fn nanoseconds(&self) -> i64 {
281 self.0
282 }
283}
284
285#[derive(Clone, Copy, Debug, Eq, PartialEq)]
287#[non_exhaustive]
288pub enum HealthOnFailure {
289 None,
291 Kill,
293 Restart,
295 Stop,
297}
298
299#[derive(Clone, Debug, Eq, PartialEq)]
301pub struct ConfiguredHealthCheck {
302 command: HealthCommand,
303 interval: Option<HealthInterval>,
304 timeout: Option<HealthTimeout>,
305 retries: Option<HealthRetries>,
306 start_period: Option<HealthStartPeriod>,
307 on_failure: Option<HealthOnFailure>,
308}
309
310#[allow(clippy::missing_errors_doc)]
311impl ConfiguredHealthCheck {
312 #[must_use]
314 pub const fn new(command: HealthCommand) -> Self {
315 Self {
316 command,
317 interval: None,
318 timeout: None,
319 retries: None,
320 start_period: None,
321 on_failure: None,
322 }
323 }
324 pub fn set_interval(&mut self, value: HealthInterval) -> PodmanLensResult<()> {
326 set_once(&mut self.interval, value)
327 }
328 pub fn set_timeout(&mut self, value: HealthTimeout) -> PodmanLensResult<()> {
330 set_once(&mut self.timeout, value)
331 }
332 pub fn set_retries(&mut self, value: HealthRetries) -> PodmanLensResult<()> {
334 set_once(&mut self.retries, value)
335 }
336 pub fn set_start_period(&mut self, value: HealthStartPeriod) -> PodmanLensResult<()> {
338 set_once(&mut self.start_period, value)
339 }
340 pub fn set_on_failure(&mut self, value: HealthOnFailure) -> PodmanLensResult<()> {
342 set_once(&mut self.on_failure, value)
343 }
344 #[must_use]
346 pub const fn command(&self) -> &HealthCommand {
347 &self.command
348 }
349 #[must_use]
351 pub const fn interval(&self) -> Option<HealthInterval> {
352 self.interval
353 }
354 #[must_use]
356 pub const fn timeout(&self) -> Option<HealthTimeout> {
357 self.timeout
358 }
359 #[must_use]
361 pub const fn retries(&self) -> Option<HealthRetries> {
362 self.retries
363 }
364 #[must_use]
366 pub const fn start_period(&self) -> Option<HealthStartPeriod> {
367 self.start_period
368 }
369 #[must_use]
371 pub const fn on_failure(&self) -> Option<HealthOnFailure> {
372 self.on_failure
373 }
374}
375
376#[derive(Clone, Debug, Eq, PartialEq)]
378#[non_exhaustive]
379pub enum HealthCheck {
380 Disabled,
382 Command(ConfiguredHealthCheck),
384}
385
386#[derive(Clone, Debug, Eq, PartialEq)]
388pub struct StartupHealthCheck {
389 command: HealthCommand,
390 interval: Option<HealthInterval>,
391 timeout: Option<HealthTimeout>,
392 retries: Option<StartupHealthRetries>,
393 successes: Option<StartupHealthSuccesses>,
394}
395
396#[allow(clippy::missing_errors_doc)]
397impl StartupHealthCheck {
398 #[must_use]
400 pub const fn new(command: HealthCommand) -> Self {
401 Self {
402 command,
403 interval: None,
404 timeout: None,
405 retries: None,
406 successes: None,
407 }
408 }
409
410 pub fn set_interval(&mut self, value: HealthInterval) -> PodmanLensResult<()> {
412 set_once(&mut self.interval, value)
413 }
414 pub fn set_timeout(&mut self, value: HealthTimeout) -> PodmanLensResult<()> {
416 set_once(&mut self.timeout, value)
417 }
418 pub fn set_retries(&mut self, value: StartupHealthRetries) -> PodmanLensResult<()> {
420 set_once(&mut self.retries, value)
421 }
422 pub fn set_successes(&mut self, value: StartupHealthSuccesses) -> PodmanLensResult<()> {
424 set_once(&mut self.successes, value)
425 }
426
427 #[must_use]
429 pub const fn command(&self) -> &HealthCommand {
430 &self.command
431 }
432
433 #[must_use]
435 pub const fn interval(&self) -> Option<HealthInterval> {
436 self.interval
437 }
438 #[must_use]
440 pub const fn timeout(&self) -> Option<HealthTimeout> {
441 self.timeout
442 }
443 #[must_use]
445 pub const fn retries(&self) -> Option<StartupHealthRetries> {
446 self.retries
447 }
448 #[must_use]
450 pub const fn successes(&self) -> Option<StartupHealthSuccesses> {
451 self.successes
452 }
453}
454
455#[derive(Clone, Copy, Debug, Eq, PartialEq)]
457#[non_exhaustive]
458pub enum LogDriver {
459 Journald,
461 K8sFile,
463}
464
465#[derive(Clone, Copy, Debug, Eq, PartialEq)]
467pub struct LogSize(i64);
468
469impl LogSize {
470 pub fn new(bytes: u64) -> PodmanLensResult<Self> {
476 if bytes == 0 || bytes > i64::MAX as u64 {
477 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
478 }
479 Ok(Self(i64::try_from(bytes).map_err(|_| {
480 Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent)
481 })?))
482 }
483
484 #[must_use]
486 pub const fn bytes(&self) -> i64 {
487 self.0
488 }
489}
490
491#[derive(Clone, Debug, Default, Eq, PartialEq)]
493pub struct LoggingSettings {
494 driver: Option<LogDriver>,
495 max_size: Option<LogSize>,
496 journald_labels: Vec<Label>,
497}
498
499#[allow(clippy::missing_errors_doc)] impl LoggingSettings {
501 pub fn set_driver(&mut self, driver: LogDriver) -> PodmanLensResult<()> {
503 set_once(&mut self.driver, driver)
504 }
505
506 pub fn set_max_size(&mut self, size: LogSize) -> PodmanLensResult<()> {
508 set_once(&mut self.max_size, size)
509 }
510
511 pub fn add_journald_label(&mut self, label: Label) -> PodmanLensResult<()> {
513 if self.journald_labels.len() == MAX_ITEMS {
514 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
515 }
516 if self
517 .journald_labels
518 .iter()
519 .any(|existing| existing.key() == label.key())
520 {
521 return Err(Diagnostic::new(DiagnosticCode::DeploymentDuplicateResource));
522 }
523 self.journald_labels.push(label);
524 Ok(())
525 }
526
527 #[must_use]
529 pub const fn driver(&self) -> Option<LogDriver> {
530 self.driver
531 }
532
533 #[must_use]
535 pub const fn max_size(&self) -> Option<LogSize> {
536 self.max_size
537 }
538
539 #[must_use]
541 pub fn journald_labels(&self) -> &[Label] {
542 &self.journald_labels
543 }
544}
545
546#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
548pub struct LinuxCapability(&'static str);
549
550impl LinuxCapability {
551 pub fn new(value: &str) -> PodmanLensResult<Self> {
557 CAPABILITIES
558 .iter()
559 .copied()
560 .find(|known| *known == value)
561 .map(Self)
562 .ok_or_else(|| Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent))
563 }
564
565 #[must_use]
567 pub const fn as_str(&self) -> &'static str {
568 self.0
569 }
570}
571
572const CAPABILITIES: [&str; 41] = [
573 "AUDIT_CONTROL",
574 "AUDIT_READ",
575 "AUDIT_WRITE",
576 "BLOCK_SUSPEND",
577 "BPF",
578 "CHECKPOINT_RESTORE",
579 "CHOWN",
580 "DAC_OVERRIDE",
581 "DAC_READ_SEARCH",
582 "FOWNER",
583 "FSETID",
584 "IPC_LOCK",
585 "IPC_OWNER",
586 "KILL",
587 "LEASE",
588 "LINUX_IMMUTABLE",
589 "MAC_ADMIN",
590 "MAC_OVERRIDE",
591 "MKNOD",
592 "NET_ADMIN",
593 "NET_BIND_SERVICE",
594 "NET_BROADCAST",
595 "NET_RAW",
596 "PERFMON",
597 "SETFCAP",
598 "SETGID",
599 "SETPCAP",
600 "SETUID",
601 "SYS_ADMIN",
602 "SYS_BOOT",
603 "SYS_CHROOT",
604 "SYS_MODULE",
605 "SYS_NICE",
606 "SYS_PACCT",
607 "SYS_PTRACE",
608 "SYS_RAWIO",
609 "SYS_RESOURCE",
610 "SYS_TIME",
611 "SYS_TTY_CONFIG",
612 "SYSLOG",
613 "WAKE_ALARM",
614];
615
616#[derive(Clone, Debug, Default, Eq, PartialEq)]
618pub struct SecuritySettings {
619 privileged: Option<bool>,
620 no_new_privileges: Option<bool>,
621 read_only_filesystem: Option<bool>,
622 read_write_tmpfs: Option<bool>,
623 cap_add: Vec<LinuxCapability>,
624 cap_drop: Vec<LinuxCapability>,
625}
626
627#[allow(clippy::missing_errors_doc)] impl SecuritySettings {
629 pub fn set_privileged(&mut self, enabled: bool) -> PodmanLensResult<()> {
631 set_once(&mut self.privileged, enabled)
632 }
633
634 pub fn set_no_new_privileges(&mut self, enabled: bool) -> PodmanLensResult<()> {
636 set_once(&mut self.no_new_privileges, enabled)
637 }
638
639 pub fn set_read_only_filesystem(&mut self, enabled: bool) -> PodmanLensResult<()> {
641 set_once(&mut self.read_only_filesystem, enabled)
642 }
643
644 pub fn add_capability(&mut self, capability: LinuxCapability) -> PodmanLensResult<()> {
646 add_distinct(&mut self.cap_add, capability)
647 }
648
649 pub fn drop_capability(&mut self, capability: LinuxCapability) -> PodmanLensResult<()> {
651 add_distinct(&mut self.cap_drop, capability)
652 }
653
654 pub fn set_read_write_tmpfs(&mut self, enabled: bool) -> PodmanLensResult<()> {
656 set_once(&mut self.read_write_tmpfs, enabled)
657 }
658
659 #[must_use]
661 pub const fn privileged(&self) -> Option<bool> {
662 self.privileged
663 }
664 #[must_use]
666 pub const fn no_new_privileges(&self) -> Option<bool> {
667 self.no_new_privileges
668 }
669 #[must_use]
671 pub const fn read_only_filesystem(&self) -> Option<bool> {
672 self.read_only_filesystem
673 }
674 #[must_use]
676 pub fn cap_add(&self) -> &[LinuxCapability] {
677 &self.cap_add
678 }
679 #[must_use]
681 pub fn cap_drop(&self) -> &[LinuxCapability] {
682 &self.cap_drop
683 }
684 #[must_use]
686 pub const fn read_write_tmpfs(&self) -> Option<bool> {
687 self.read_write_tmpfs
688 }
689}
690
691#[derive(Clone, Copy, Debug, Eq, PartialEq)]
693#[non_exhaustive]
694pub enum RlimitValue {
695 Finite(u64),
697 Unlimited,
699}
700
701impl RlimitValue {
702 #[must_use]
704 pub const fn finite(value: u64) -> Self {
705 Self::Finite(value)
706 }
707}
708
709#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
711#[non_exhaustive]
712pub enum RlimitKind {
713 NoFile,
715 NProc,
717}
718
719#[derive(Clone, Copy, Debug, Eq, PartialEq)]
721pub struct Rlimit {
722 kind: RlimitKind,
723 soft: RlimitValue,
724 hard: RlimitValue,
725}
726
727#[allow(clippy::missing_errors_doc)] impl Rlimit {
729 pub fn new(kind: RlimitKind, soft: RlimitValue, hard: RlimitValue) -> PodmanLensResult<Self> {
731 if matches!((soft, hard), (RlimitValue::Unlimited, RlimitValue::Finite(_)))
732 || matches!((soft, hard), (RlimitValue::Finite(soft), RlimitValue::Finite(hard)) if soft > hard)
733 {
734 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
735 }
736 Ok(Self { kind, soft, hard })
737 }
738 #[must_use]
740 pub const fn kind(&self) -> RlimitKind {
741 self.kind
742 }
743 #[must_use]
745 pub const fn soft(&self) -> RlimitValue {
746 self.soft
747 }
748 #[must_use]
750 pub const fn hard(&self) -> RlimitValue {
751 self.hard
752 }
753}
754
755#[derive(Clone, Debug, Default, Eq, PartialEq)]
757pub struct ContainerResourceControls {
758 cpu_shares: Option<i64>,
759 cpu_period: Option<i64>,
760 cpu_quota: Option<i64>,
761 memory_bytes: Option<i64>,
762 pids: Option<i64>,
763 rlimits: Vec<Rlimit>,
764}
765
766#[allow(clippy::missing_errors_doc)] impl ContainerResourceControls {
768 pub fn set_cpu_shares(&mut self, value: u32) -> PodmanLensResult<()> {
770 if !(2..=262_144).contains(&value) {
771 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
772 }
773 set_once(&mut self.cpu_shares, i64::from(value))
774 }
775 pub fn set_cpu_period(&mut self, value: u64) -> PodmanLensResult<()> {
777 if !(1_000..=1_000_000).contains(&value) {
778 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
779 }
780 let value = signed_positive(value)?;
781 set_once(&mut self.cpu_period, value)
782 }
783 pub fn set_cpu_quota(&mut self, value: i64) -> PodmanLensResult<()> {
785 if value < 1_000 {
786 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
787 }
788 set_once(&mut self.cpu_quota, value)
789 }
790 pub fn set_memory_bytes(&mut self, value: u64) -> PodmanLensResult<()> {
792 set_once(&mut self.memory_bytes, signed_positive(value)?)
793 }
794 pub fn set_pids(&mut self, value: i64) -> PodmanLensResult<()> {
796 if value == 0 || value < -1 {
797 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
798 }
799 set_once(&mut self.pids, value)
800 }
801 pub fn add_rlimit(&mut self, value: Rlimit) -> PodmanLensResult<()> {
803 if self.rlimits.len() == MAX_ITEMS {
804 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
805 }
806 if self.rlimits.iter().any(|existing| existing.kind == value.kind) {
807 return Err(Diagnostic::new(DiagnosticCode::DeploymentDuplicateResource));
808 }
809 self.rlimits.push(value);
810 Ok(())
811 }
812 #[must_use]
814 pub const fn cpu_shares(&self) -> Option<i64> {
815 self.cpu_shares
816 }
817 #[must_use]
819 pub const fn cpu_period(&self) -> Option<i64> {
820 self.cpu_period
821 }
822 #[must_use]
824 pub const fn cpu_quota(&self) -> Option<i64> {
825 self.cpu_quota
826 }
827 #[must_use]
829 pub const fn memory_bytes(&self) -> Option<i64> {
830 self.memory_bytes
831 }
832 #[must_use]
834 pub const fn pids(&self) -> Option<i64> {
835 self.pids
836 }
837 #[must_use]
839 pub fn rlimits(&self) -> &[Rlimit] {
840 &self.rlimits
841 }
842}
843
844#[derive(Clone, Copy, Debug, Eq, PartialEq)]
846#[non_exhaustive]
847pub enum NamespaceMode {
848 Private,
850 Host,
852}
853
854#[derive(Clone, Copy, Debug, Eq, PartialEq)]
856#[non_exhaustive]
857pub enum IpcNamespaceMode {
858 Private,
860 Host,
862 Shareable,
864 None,
866}
867
868#[derive(Clone, Debug, Default, Eq, PartialEq)]
870pub struct ContainerNamespaceSettings {
871 pid: Option<NamespaceMode>,
872 ipc: Option<IpcNamespaceMode>,
873 uts: Option<NamespaceMode>,
874 cgroup: Option<NamespaceMode>,
875}
876
877#[allow(clippy::missing_errors_doc)]
878impl ContainerNamespaceSettings {
879 pub fn set_pid(&mut self, value: NamespaceMode) -> PodmanLensResult<()> {
881 set_once(&mut self.pid, value)
882 }
883 pub fn set_ipc(&mut self, value: IpcNamespaceMode) -> PodmanLensResult<()> {
885 set_once(&mut self.ipc, value)
886 }
887 pub fn set_uts(&mut self, value: NamespaceMode) -> PodmanLensResult<()> {
889 set_once(&mut self.uts, value)
890 }
891 pub fn set_cgroup(&mut self, value: NamespaceMode) -> PodmanLensResult<()> {
893 set_once(&mut self.cgroup, value)
894 }
895 #[must_use]
897 pub const fn pid(&self) -> Option<NamespaceMode> {
898 self.pid
899 }
900 #[must_use]
902 pub const fn ipc(&self) -> Option<IpcNamespaceMode> {
903 self.ipc
904 }
905 #[must_use]
907 pub const fn uts(&self) -> Option<NamespaceMode> {
908 self.uts
909 }
910 #[must_use]
912 pub const fn cgroup(&self) -> Option<NamespaceMode> {
913 self.cgroup
914 }
915 pub(crate) fn is_empty(&self) -> bool {
916 self == &Self::default()
917 }
918}
919
920#[derive(Clone, Debug, Default, Eq, PartialEq)]
922pub struct ContainerRuntimeSettings {
923 health: Option<HealthCheck>,
924 startup_health: Option<StartupHealthCheck>,
925 logging: LoggingSettings,
926 security: SecuritySettings,
927 resources: ContainerResourceControls,
928 namespaces: ContainerNamespaceSettings,
929}
930
931#[allow(clippy::missing_errors_doc)] impl ContainerRuntimeSettings {
933 pub fn set_health(&mut self, value: HealthCheck) -> PodmanLensResult<()> {
935 set_once(&mut self.health, value)
936 }
937 pub fn set_startup_health(&mut self, value: StartupHealthCheck) -> PodmanLensResult<()> {
939 set_once(&mut self.startup_health, value)
940 }
941 #[must_use]
943 pub fn health(&self) -> Option<&HealthCheck> {
944 self.health.as_ref()
945 }
946 #[must_use]
948 pub fn startup_health(&self) -> Option<&StartupHealthCheck> {
949 self.startup_health.as_ref()
950 }
951 #[must_use]
953 pub fn logging(&self) -> &LoggingSettings {
954 &self.logging
955 }
956 #[must_use]
958 pub fn logging_mut(&mut self) -> &mut LoggingSettings {
959 &mut self.logging
960 }
961 #[must_use]
963 pub fn security(&self) -> &SecuritySettings {
964 &self.security
965 }
966 #[must_use]
968 pub fn security_mut(&mut self) -> &mut SecuritySettings {
969 &mut self.security
970 }
971 #[must_use]
973 pub fn resources(&self) -> &ContainerResourceControls {
974 &self.resources
975 }
976 #[must_use]
978 pub fn resources_mut(&mut self) -> &mut ContainerResourceControls {
979 &mut self.resources
980 }
981 #[must_use]
983 pub fn namespaces(&self) -> &ContainerNamespaceSettings {
984 &self.namespaces
985 }
986 #[must_use]
988 pub fn namespaces_mut(&mut self) -> &mut ContainerNamespaceSettings {
989 &mut self.namespaces
990 }
991}
992
993fn set_once<T: Eq>(slot: &mut Option<T>, value: T) -> PodmanLensResult<()> {
994 match slot {
995 None => {
996 *slot = Some(value);
997 Ok(())
998 }
999 Some(existing) if *existing == value => Err(Diagnostic::new(DiagnosticCode::DeploymentDuplicateResource)),
1000 Some(_) => Err(Diagnostic::new(DiagnosticCode::DeploymentUnsupportedCombination)),
1001 }
1002}
1003fn add_distinct<T: Eq>(values: &mut Vec<T>, value: T) -> PodmanLensResult<()> {
1004 if values.len() == MAX_ITEMS {
1005 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
1006 }
1007 if values.contains(&value) {
1008 return Err(Diagnostic::new(DiagnosticCode::DeploymentDuplicateResource));
1009 }
1010 values.push(value);
1011 Ok(())
1012}
1013fn signed_positive(value: u64) -> PodmanLensResult<i64> {
1014 if value == 0 || value > i64::MAX as u64 {
1015 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
1016 }
1017 i64::try_from(value).map_err(|_| Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent))
1018}
1019fn validate_arguments(values: &[String]) -> PodmanLensResult<()> {
1020 if values.is_empty()
1021 || values.len() > MAX_ITEMS
1022 || values.first().is_some_and(String::is_empty)
1023 || values.iter().any(|value| !valid_text(value))
1024 {
1025 return Err(Diagnostic::new(DiagnosticCode::InvalidDeploymentIntent));
1026 }
1027 Ok(())
1028}
1029fn valid_text(value: &str) -> bool {
1030 value.len() <= MAX_BYTES && !value.chars().any(char::is_control)
1031}