Skip to main content

wfe_containerd/
config.rs

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