Skip to main content

xianyun_deploy/reloader/
registry.rs

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