gioser

tawasuyu/brahman

Rust 326TOML 123JSON 14Markdown 114554 Definiciones

⎇ 3f8a3ea4b6d3de529e42ae418d486bc81cca308e crates/modules/matilda/matilda-core/src/container.rs Rust · 132 líneas · 4.1 KB Historia Quién tocó qué crudo

feat(matilda): administración de servidores — core + config + plan 3f8a3ea4b · sergio ·

1//! `Container` — la especificación declarativa de un contenedor Docker.
2//!
3//! Es sólo el *deseo*: qué imagen, qué puertos, qué entorno. Ejecutar
4//! Docker es trabajo de capas superiores; aquí el contenedor es un dato
5//! comparable (`PartialEq`) para que el plan detecte cambios.
6
7use serde::{Deserialize, Serialize};
8
9/// Política de reinicio del contenedor.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
11#[serde(rename_all = "kebab-case")]
12pub enum RestartPolicy {
13    /// Nunca reiniciar.
14    #[default]
15    No,
16    /// Reiniciar sólo si salió con error.
17    OnFailure,
18    /// Reiniciar siempre.
19    Always,
20    /// Reiniciar salvo que se haya detenido a mano.
21    UnlessStopped,
22}
23
24impl RestartPolicy {
25    /// Valor tal como lo espera el flag `--restart` de Docker.
26    pub fn docker_flag(self) -> &'static str {
27        match self {
28            RestartPolicy::No => "no",
29            RestartPolicy::OnFailure => "on-failure",
30            RestartPolicy::Always => "always",
31            RestartPolicy::UnlessStopped => "unless-stopped",
32        }
33    }
34}
35
36/// Un mapeo de puerto `host → contenedor`.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38pub struct PortMap {
39    pub host: u16,
40    pub container: u16,
41}
42
43impl PortMap {
44    pub fn new(host: u16, container: u16) -> Self {
45        Self { host, container }
46    }
47}
48
49/// La especificación declarativa de un contenedor. Clave única: `name`.
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
51pub struct Container {
52    pub name: String,
53    /// Imagen con etiqueta — `"nginx:1.27"`, `"postgres:16"`.
54    pub image: String,
55    pub ports: Vec<PortMap>,
56    /// Variables de entorno, ordenadas por clave para comparación estable.
57    pub env: Vec<(String, String)>,
58    /// Volúmenes `ruta_host → ruta_contenedor`.
59    pub volumes: Vec<(String, String)>,
60    pub restart: RestartPolicy,
61}
62
63impl Container {
64    /// Contenedor mínimo: nombre + imagen.
65    pub fn new(name: impl Into<String>, image: impl Into<String>) -> Self {
66        Self {
67            name: name.into(),
68            image: image.into(),
69            ports: Vec::new(),
70            env: Vec::new(),
71            volumes: Vec::new(),
72            restart: RestartPolicy::default(),
73        }
74    }
75
76    /// Publica un puerto (encadenable).
77    pub fn with_port(mut self, host: u16, container: u16) -> Self {
78        self.ports.push(PortMap::new(host, container));
79        self
80    }
81
82    /// Define una variable de entorno (encadenable). El vector se
83    /// mantiene ordenado por clave para que dos contenedores con el
84    /// mismo entorno comparen iguales sin importar el orden de llamada.
85    pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
86        let key = key.into();
87        self.env.retain(|(k, _)| k != &key);
88        self.env.push((key, value.into()));
89        self.env.sort_by(|a, b| a.0.cmp(&b.0));
90        self
91    }
92
93    /// Monta un volumen (encadenable).
94    pub fn with_volume(
95        mut self,
96        host_path: impl Into<String>,
97        container_path: impl Into<String>,
98    ) -> Self {
99        self.volumes.push((host_path.into(), container_path.into()));
100        self
101    }
102
103    /// Fija la política de reinicio (encadenable).
104    pub fn with_restart(mut self, restart: RestartPolicy) -> Self {
105        self.restart = restart;
106        self
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn env_order_does_not_affect_equality() {
116        let a = Container::new("c", "img").with_env("B", "2").with_env("A", "1");
117        let b = Container::new("c", "img").with_env("A", "1").with_env("B", "2");
118        assert_eq!(a, b);
119    }
120
121    #[test]
122    fn with_env_overwrites_same_key() {
123        let c = Container::new("c", "img").with_env("K", "old").with_env("K", "new");
124        assert_eq!(c.env, vec![("K".to_string(), "new".to_string())]);
125    }
126
127    #[test]
128    fn restart_flags_match_docker() {
129        assert_eq!(RestartPolicy::UnlessStopped.docker_flag(), "unless-stopped");
130        assert_eq!(RestartPolicy::default(), RestartPolicy::No);
131    }
132}