Skip to main content

podbox/config/
enums.rs

1use std::str::FromStr;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum ImageSource {
7    Build { base: String },
8    Prebuilt { ref_str: String },
9}
10
11impl ImageSource {
12    pub fn is_prebuilt(&self) -> bool {
13        matches!(self, Self::Prebuilt { .. })
14    }
15
16    pub fn is_build(&self) -> bool {
17        matches!(self, Self::Build { .. })
18    }
19}
20
21/// Type-safe package manager identifier.
22///
23/// Replaces raw `&str` dispatch everywhere.  Always use the enum rather
24/// than hard-coding strings like `"dnf"` / `"apt"`.
25#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
26#[serde(rename_all = "lowercase")]
27pub enum PackageManager {
28    #[default]
29    Dnf,
30    Apt,
31    Pacman,
32    Apk,
33    Zypper,
34}
35
36impl PackageManager {
37    /// Canonical display name (e.g. `"dnf"`, `"apt"`).
38    pub const fn as_str(&self) -> &'static str {
39        match self {
40            Self::Dnf => "dnf",
41            Self::Apt => "apt",
42            Self::Pacman => "pacman",
43            Self::Apk => "apk",
44            Self::Zypper => "zypper",
45        }
46    }
47
48    /// Parse a string into a `PackageManager`, returning `None` for unknown
49    /// values (callers fall back to distro detection).
50    pub fn from_str_opt(s: &str) -> Option<Self> {
51        match s {
52            "dnf" => Some(Self::Dnf),
53            "apt" => Some(Self::Apt),
54            "pacman" => Some(Self::Pacman),
55            "apk" => Some(Self::Apk),
56            "zypper" => Some(Self::Zypper),
57            _ => None,
58        }
59    }
60}
61
62impl std::fmt::Display for PackageManager {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        write!(f, "{}", self.as_str())
65    }
66}
67
68impl FromStr for PackageManager {
69    type Err = String;
70
71    fn from_str(s: &str) -> Result<Self, Self::Err> {
72        Self::from_str_opt(s.trim()).ok_or_else(|| format!("unknown package manager: {s}"))
73    }
74}
75
76#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
77pub enum GpuMode {
78    #[default]
79    Auto,
80    Enabled,
81    Disabled,
82    Nvidia,
83}
84
85impl<'de> Deserialize<'de> for GpuMode {
86    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
87    where
88        D: serde::Deserializer<'de>,
89    {
90        use serde::de;
91
92        struct GpuModeVisitor;
93
94        impl<'de> de::Visitor<'de> for GpuModeVisitor {
95            type Value = GpuMode;
96
97            fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
98                f.write_str("true, false, \"auto\", or \"nvidia\"")
99            }
100
101            fn visit_bool<E: de::Error>(self, v: bool) -> Result<GpuMode, E> {
102                Ok(if v {
103                    GpuMode::Enabled
104                } else {
105                    GpuMode::Disabled
106                })
107            }
108
109            fn visit_str<E: de::Error>(self, v: &str) -> Result<GpuMode, E> {
110                match v {
111                    "auto" => Ok(GpuMode::Auto),
112                    "nvidia" => Ok(GpuMode::Nvidia),
113                    "true" => Ok(GpuMode::Enabled),
114                    "false" => Ok(GpuMode::Disabled),
115                    _ => Err(de::Error::unknown_variant(
116                        v,
117                        &["auto", "nvidia", "true", "false"],
118                    )),
119                }
120            }
121        }
122
123        deserializer.deserialize_any(GpuModeVisitor)
124    }
125}
126
127impl Serialize for GpuMode {
128    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
129    where
130        S: serde::Serializer,
131    {
132        match self {
133            GpuMode::Auto => serializer.serialize_str("auto"),
134            GpuMode::Enabled => serializer.serialize_bool(true),
135            GpuMode::Disabled => serializer.serialize_bool(false),
136            GpuMode::Nvidia => serializer.serialize_str("nvidia"),
137        }
138    }
139}
140
141/// Predefined capability profiles that bundle commonly-needed Linux
142/// capabilities.  The profile caps are always emitted; users can add
143/// extra capabilities via `security.cap_add`.
144#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
145#[serde(rename_all = "lowercase")]
146pub enum CapPreset {
147    /// No extra capabilities beyond Podman's defaults.
148    None,
149    /// Sane default: `DAC_READ_SEARCH`, `PERFMON` — needed by tools
150    /// such as btop and htop for file-descriptor and perf monitoring.
151    #[default]
152    Default,
153    /// Monitoring / observability: like `Default` plus `SYS_PTRACE`,
154    /// `SYS_NICE`, `SYSLOG`.
155    Monitoring,
156    /// Powerful / dangerous: like `Monitoring` plus `SYS_ADMIN`,
157    /// `NET_ADMIN`.  Only use when you trust the container fully.
158    Admin,
159}
160
161impl CapPreset {
162    pub fn caps(&self) -> &'static [&'static str] {
163        match self {
164            Self::None => &[],
165            Self::Default => &["DAC_READ_SEARCH", "PERFMON"],
166            Self::Monitoring => &["DAC_READ_SEARCH", "PERFMON", "SYS_PTRACE", "SYS_NICE"],
167            Self::Admin => &[
168                "DAC_READ_SEARCH",
169                "PERFMON",
170                "SYS_PTRACE",
171                "SYS_NICE",
172                "SYS_ADMIN",
173                "NET_ADMIN",
174                "SYSLOG",
175            ],
176        }
177    }
178}
179
180#[derive(Debug, Default, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
181#[serde(rename_all = "lowercase")]
182pub enum OnStop {
183    #[default]
184    Keep,
185    Remove,
186}
187
188#[derive(Debug, Deserialize, Serialize, Clone)]
189#[serde(untagged)]
190pub enum XdgDirValue {
191    Simple(bool),
192    Detailed {
193        enabled: bool,
194        #[serde(default)]
195        read_write: bool,
196    },
197}
198
199impl Default for XdgDirValue {
200    fn default() -> Self {
201        XdgDirValue::Simple(false)
202    }
203}
204
205impl XdgDirValue {
206    pub fn is_enabled(&self) -> bool {
207        match self {
208            XdgDirValue::Simple(b) => *b,
209            XdgDirValue::Detailed { enabled, .. } => *enabled,
210        }
211    }
212
213    pub fn is_read_write(&self) -> bool {
214        match self {
215            XdgDirValue::Simple(_) => false,
216            XdgDirValue::Detailed { read_write, .. } => *read_write,
217        }
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use crate::config::Config;
224
225    use super::*;
226
227    #[test]
228    fn test_gpu_mode_parses_true() {
229        let toml = r#"
230[image]
231base = "fedora:41"
232name = "env"
233[container]
234name = "env"
235home = "~/env"
236[integration]
237gpu = true
238"#;
239        let cfg: Config = toml::from_str(toml).unwrap();
240        assert_eq!(cfg.integration.gpu, GpuMode::Enabled);
241    }
242
243    #[test]
244    fn test_gpu_mode_parses_false() {
245        let toml = r#"
246[image]
247base = "fedora:41"
248name = "env"
249[container]
250name = "env"
251home = "~/env"
252[integration]
253gpu = false
254"#;
255        let cfg: Config = toml::from_str(toml).unwrap();
256        assert_eq!(cfg.integration.gpu, GpuMode::Disabled);
257    }
258
259    #[test]
260    fn test_gpu_mode_parses_auto_string() {
261        let toml = r#"
262[image]
263base = "fedora:41"
264name = "env"
265[container]
266name = "env"
267home = "~/env"
268[integration]
269gpu = "auto"
270"#;
271        let cfg: Config = toml::from_str(toml).unwrap();
272        assert_eq!(cfg.integration.gpu, GpuMode::Auto);
273    }
274
275    #[test]
276    fn test_gpu_mode_parses_nvidia_string() {
277        let toml = r#"
278[image]
279base = "fedora:41"
280name = "env"
281[container]
282name = "env"
283home = "~/env"
284[integration]
285gpu = "nvidia"
286"#;
287        let cfg: Config = toml::from_str(toml).unwrap();
288        assert_eq!(cfg.integration.gpu, GpuMode::Nvidia);
289    }
290
291    #[test]
292    fn test_gpu_mode_serialize() {
293        assert_eq!(serde_json::to_string(&GpuMode::Auto).unwrap(), "\"auto\"");
294        assert_eq!(serde_json::to_string(&GpuMode::Enabled).unwrap(), "true");
295        assert_eq!(serde_json::to_string(&GpuMode::Disabled).unwrap(), "false");
296        assert_eq!(
297            serde_json::to_string(&GpuMode::Nvidia).unwrap(),
298            "\"nvidia\""
299        );
300        #[derive(Serialize)]
301        struct Wrapper {
302            gpu: GpuMode,
303        }
304        let wrapper = Wrapper {
305            gpu: GpuMode::Nvidia,
306        };
307        let toml_out = toml::to_string(&wrapper).unwrap();
308        assert!(toml_out.contains("gpu = \"nvidia\""));
309        let wrapper = Wrapper {
310            gpu: GpuMode::Enabled,
311        };
312        let toml_out = toml::to_string(&wrapper).unwrap();
313        assert!(toml_out.contains("gpu = true"));
314    }
315
316    #[test]
317    fn test_gpu_mode_deserialize_toml_key() {
318        let cases = [
319            ("gpu = true", GpuMode::Enabled),
320            ("gpu = false", GpuMode::Disabled),
321            ("gpu = \"auto\"", GpuMode::Auto),
322            ("gpu = \"nvidia\"", GpuMode::Nvidia),
323        ];
324        for (toml_snippet, expected) in &cases {
325            let full = format!(
326                r#"
327[image]
328base = "fedora:41"
329name = "env"
330[container]
331name = "env"
332home = "~/env"
333[integration]
334{}
335"#,
336                toml_snippet
337            );
338            let cfg: Config = toml::from_str(&full).unwrap();
339            assert_eq!(cfg.integration.gpu, *expected);
340        }
341    }
342}