Skip to main content

wfe_containerd/
config.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct ContainerdConfig {
7    pub image: String,
8    pub command: Option<Vec<String>>,
9    pub run: Option<String>,
10    #[serde(default)]
11    pub env: HashMap<String, String>,
12    #[serde(default)]
13    pub volumes: Vec<VolumeMountConfig>,
14    pub working_dir: Option<String>,
15    #[serde(default = "default_user")]
16    pub user: String,
17    #[serde(default = "default_network")]
18    pub network: String,
19    pub memory: Option<String>,
20    pub cpu: Option<String>,
21    #[serde(default = "default_pull")]
22    pub pull: String,
23    #[serde(default = "default_containerd_addr")]
24    pub containerd_addr: String,
25    /// CLI binary name: "nerdctl" (default) or "docker".
26    #[serde(default = "default_cli")]
27    pub cli: String,
28    #[serde(default)]
29    pub tls: TlsConfig,
30    #[serde(default)]
31    pub registry_auth: HashMap<String, RegistryAuth>,
32    pub timeout_ms: Option<u64>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct VolumeMountConfig {
37    pub source: String,
38    pub target: String,
39    #[serde(default)]
40    pub readonly: bool,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, Default)]
44pub struct TlsConfig {
45    pub ca: Option<String>,
46    pub cert: Option<String>,
47    pub key: Option<String>,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct RegistryAuth {
52    pub username: String,
53    pub password: String,
54}
55
56fn default_user() -> String {
57    "65534:65534".to_string()
58}
59
60fn default_network() -> String {
61    "none".to_string()
62}
63
64fn default_pull() -> String {
65    "if-not-present".to_string()
66}
67
68fn default_containerd_addr() -> String {
69    "/run/containerd/containerd.sock".to_string()
70}
71
72fn default_cli() -> String {
73    "nerdctl".to_string()
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use pretty_assertions::assert_eq;
80
81    #[test]
82    fn serde_round_trip_full_config() {
83        let config = ContainerdConfig {
84            image: "alpine:3.18".to_string(),
85            command: Some(vec!["echo".to_string(), "hello".to_string()]),
86            run: None,
87            env: HashMap::from([("FOO".to_string(), "bar".to_string())]),
88            volumes: vec![VolumeMountConfig {
89                source: "/host/path".to_string(),
90                target: "/container/path".to_string(),
91                readonly: true,
92            }],
93            working_dir: Some("/app".to_string()),
94            user: "1000:1000".to_string(),
95            network: "host".to_string(),
96            memory: Some("512m".to_string()),
97            cpu: Some("1.0".to_string()),
98            pull: "always".to_string(),
99            containerd_addr: "/custom/containerd.sock".to_string(),
100            cli: "nerdctl".to_string(),
101            tls: TlsConfig {
102                ca: Some("/ca.pem".to_string()),
103                cert: Some("/cert.pem".to_string()),
104                key: Some("/key.pem".to_string()),
105            },
106            registry_auth: HashMap::from([(
107                "registry.example.com".to_string(),
108                RegistryAuth {
109                    username: "user".to_string(),
110                    password: "pass".to_string(),
111                },
112            )]),
113            timeout_ms: Some(30000),
114        };
115
116        let json = serde_json::to_string(&config).unwrap();
117        let deserialized: ContainerdConfig = serde_json::from_str(&json).unwrap();
118
119        assert_eq!(deserialized.image, config.image);
120        assert_eq!(deserialized.command, config.command);
121        assert_eq!(deserialized.run, config.run);
122        assert_eq!(deserialized.env, config.env);
123        assert_eq!(deserialized.volumes.len(), 1);
124        assert_eq!(deserialized.volumes[0].source, "/host/path");
125        assert_eq!(deserialized.volumes[0].readonly, true);
126        assert_eq!(deserialized.working_dir, Some("/app".to_string()));
127        assert_eq!(deserialized.user, "1000:1000");
128        assert_eq!(deserialized.network, "host");
129        assert_eq!(deserialized.memory, Some("512m".to_string()));
130        assert_eq!(deserialized.cpu, Some("1.0".to_string()));
131        assert_eq!(deserialized.pull, "always");
132        assert_eq!(deserialized.containerd_addr, "/custom/containerd.sock");
133        assert_eq!(deserialized.tls.ca, Some("/ca.pem".to_string()));
134        assert_eq!(deserialized.tls.cert, Some("/cert.pem".to_string()));
135        assert_eq!(deserialized.tls.key, Some("/key.pem".to_string()));
136        assert!(
137            deserialized
138                .registry_auth
139                .contains_key("registry.example.com")
140        );
141        assert_eq!(deserialized.timeout_ms, Some(30000));
142    }
143
144    #[test]
145    fn serde_round_trip_minimal_config() {
146        let json = r#"{"image": "alpine:latest"}"#;
147        let config: ContainerdConfig = serde_json::from_str(json).unwrap();
148
149        assert_eq!(config.image, "alpine:latest");
150        assert_eq!(config.command, None);
151        assert_eq!(config.run, None);
152        assert!(config.env.is_empty());
153        assert!(config.volumes.is_empty());
154        assert_eq!(config.working_dir, None);
155        assert_eq!(config.user, "65534:65534");
156        assert_eq!(config.network, "none");
157        assert_eq!(config.memory, None);
158        assert_eq!(config.cpu, None);
159        assert_eq!(config.pull, "if-not-present");
160        assert_eq!(config.containerd_addr, "/run/containerd/containerd.sock");
161        assert_eq!(config.timeout_ms, None);
162
163        // Round-trip
164        let serialized = serde_json::to_string(&config).unwrap();
165        let deserialized: ContainerdConfig = serde_json::from_str(&serialized).unwrap();
166        assert_eq!(deserialized.image, "alpine:latest");
167        assert_eq!(deserialized.user, "65534:65534");
168    }
169
170    #[test]
171    fn default_values() {
172        let json = r#"{"image": "busybox"}"#;
173        let config: ContainerdConfig = serde_json::from_str(json).unwrap();
174
175        assert_eq!(config.user, "65534:65534");
176        assert_eq!(config.network, "none");
177        assert_eq!(config.pull, "if-not-present");
178        assert_eq!(config.containerd_addr, "/run/containerd/containerd.sock");
179    }
180
181    #[test]
182    fn volume_mount_serde() {
183        let vol = VolumeMountConfig {
184            source: "/data".to_string(),
185            target: "/mnt/data".to_string(),
186            readonly: false,
187        };
188        let json = serde_json::to_string(&vol).unwrap();
189        let deserialized: VolumeMountConfig = serde_json::from_str(&json).unwrap();
190        assert_eq!(deserialized.source, "/data");
191        assert_eq!(deserialized.target, "/mnt/data");
192        assert_eq!(deserialized.readonly, false);
193
194        // With readonly=true
195        let vol_ro = VolumeMountConfig {
196            source: "/src".to_string(),
197            target: "/dest".to_string(),
198            readonly: true,
199        };
200        let json_ro = serde_json::to_string(&vol_ro).unwrap();
201        let deserialized_ro: VolumeMountConfig = serde_json::from_str(&json_ro).unwrap();
202        assert_eq!(deserialized_ro.readonly, true);
203    }
204
205    #[test]
206    fn tls_config_defaults() {
207        let tls = TlsConfig::default();
208        assert_eq!(tls.ca, None);
209        assert_eq!(tls.cert, None);
210        assert_eq!(tls.key, None);
211
212        let json = r#"{}"#;
213        let deserialized: TlsConfig = serde_json::from_str(json).unwrap();
214        assert_eq!(deserialized.ca, None);
215        assert_eq!(deserialized.cert, None);
216        assert_eq!(deserialized.key, None);
217    }
218
219    #[test]
220    fn registry_auth_serde() {
221        let auth = RegistryAuth {
222            username: "admin".to_string(),
223            password: "secret123".to_string(),
224        };
225        let json = serde_json::to_string(&auth).unwrap();
226        let deserialized: RegistryAuth = serde_json::from_str(&json).unwrap();
227        assert_eq!(deserialized.username, "admin");
228        assert_eq!(deserialized.password, "secret123");
229    }
230}