Skip to main content

xianyun_deploy/deployer/
registry.rs

1//! Global registry of deployer manager factories.
2//!
3//! Ported from `internal/deployer/registry.go`.
4
5use std::collections::HashMap;
6use std::sync::{Mutex, OnceLock};
7
8use super::{DeployerError, Manager};
9
10/// A function that creates a new deployer manager.
11pub type ManagerFactory = Box<dyn Fn(&str) -> Box<dyn Manager + Send + Sync> + Send + Sync>;
12
13fn registry() -> &'static Mutex<HashMap<String, ManagerFactory>> {
14    static REGISTRY: OnceLock<Mutex<HashMap<String, ManagerFactory>>> = OnceLock::new();
15    REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
16}
17
18/// Registers a deployer manager factory for a given type. Typically
19/// called from each deployer implementation's setup path.
20///
21/// # Panics
22///
23/// Panics if `deployer_type` is already registered.
24pub fn register(deployer_type: &str, factory: ManagerFactory) {
25    let mut reg = registry()
26        .lock()
27        .unwrap_or_else(std::sync::PoisonError::into_inner);
28
29    if reg.contains_key(deployer_type) {
30        panic!("deployer type {deployer_type:?} already registered");
31    }
32
33    reg.insert(deployer_type.to_string(), factory);
34}
35
36/// Creates a new deployer manager for the given type. Returns
37/// [`DeployerError::UnknownType`] if the type is not registered.
38pub fn new_manager(
39    deployer_type: &str,
40    kubeconfig: &str,
41) -> Result<Box<dyn Manager + Send + Sync>, DeployerError> {
42    let reg = registry()
43        .lock()
44        .unwrap_or_else(std::sync::PoisonError::into_inner);
45
46    let factory = reg.get(deployer_type).ok_or(DeployerError::UnknownType)?;
47
48    Ok(factory(kubeconfig))
49}
50
51/// Returns a list of all registered deployer types.
52#[must_use]
53pub fn list_types() -> Vec<String> {
54    let reg = registry()
55        .lock()
56        .unwrap_or_else(std::sync::PoisonError::into_inner);
57    reg.keys().cloned().collect()
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use crate::deployer::{State, Status};
64    use xianyun_core::config::latest::{CaproniConfig, DeployerConfig};
65
66    struct MockDeployer {
67        deployer_type: String,
68    }
69
70    impl Manager for MockDeployer {
71        fn deployer_type(&self) -> String {
72            self.deployer_type.clone()
73        }
74
75        fn deploy(
76            &self,
77            _deployer_config: &DeployerConfig,
78            _cfg: Option<&xianyun_core::config::container::Container>,
79        ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
80            Ok(())
81        }
82
83        fn undeploy(
84            &self,
85            _deployer_config: &DeployerConfig,
86            _caproni_config: &CaproniConfig,
87        ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
88            Ok(())
89        }
90
91        fn status(
92            &self,
93            _deployer_config: &DeployerConfig,
94            _caproni_config: &CaproniConfig,
95        ) -> Result<Status, Box<dyn std::error::Error + Send + Sync>> {
96            Ok(Status {
97                state: State::NotDeployed,
98                deployer_type: self.deployer_type.clone(),
99            })
100        }
101
102        fn validate(
103            &self,
104            _deployer_config: &DeployerConfig,
105            _caproni_config: &CaproniConfig,
106        ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
107            Ok(())
108        }
109    }
110
111    // Ported from internal/deployer/registry_test.go:42 (TestRegister)
112    #[test]
113    fn test_register() {
114        let deployer_type = "test-deployer";
115
116        register(
117            deployer_type,
118            Box::new(|_kubeconfig| {
119                Box::new(MockDeployer {
120                    deployer_type: "test-deployer".to_string(),
121                })
122            }),
123        );
124    }
125
126    // Ported from internal/deployer/registry_test.go:77 (TestNewManager)
127    #[test]
128    fn test_new_manager() {
129        let test_type = "test-new-manager";
130        register(
131            test_type,
132            Box::new(|_kubeconfig| {
133                Box::new(MockDeployer {
134                    deployer_type: "test-new-manager".to_string(),
135                })
136            }),
137        );
138
139        match new_manager(test_type, "") {
140            Ok(mgr) => assert_eq!(mgr.deployer_type(), test_type),
141            Err(e) => panic!("expected Ok, got {e}"),
142        }
143
144        match new_manager("unknown-type", "") {
145            Ok(_) => panic!("expected UnknownType error"),
146            Err(err) => assert!(matches!(err, DeployerError::UnknownType)),
147        }
148    }
149
150    // Ported from internal/deployer/registry_test.go:124 (TestListTypes)
151    #[test]
152    fn test_list_types() {
153        let type1 = "test-list-1";
154        let type2 = "test-list-2";
155
156        register(
157            type1,
158            Box::new(|_kubeconfig| {
159                Box::new(MockDeployer {
160                    deployer_type: "test-list-1".to_string(),
161                })
162            }),
163        );
164        register(
165            type2,
166            Box::new(|_kubeconfig| {
167                Box::new(MockDeployer {
168                    deployer_type: "test-list-2".to_string(),
169                })
170            }),
171        );
172
173        let types = list_types();
174        assert!(types.contains(&type1.to_string()));
175        assert!(types.contains(&type2.to_string()));
176    }
177}