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