Skip to main content

xianyun_deploy/reloader/mirror/
mod.rs

1//! The mirror reloader: runs local development processes against a
2//! `-caproni` deployment copy so the original workload stays untouched.
3//!
4//! Ported from `internal/reloader/mirror`.
5
6pub mod copy;
7pub mod manager;
8
9/// Hand-rolled test doubles for
10/// [`xianyun_backends::kubectl::Backend`], shared by this module's own
11/// tests (`copy`, `manager`). Mirrors the Go tests'
12/// mockery-generated `kubectlmocks.NewMockBackend`, but hand-rolled per
13/// the crate's existing convention (see
14/// `deployer::namespace::manager`'s `MockKubectlBackend` and
15/// `deployer::kustomize::test_support`) since no shared mock of the
16/// `Backend` trait exists yet anywhere in the workspace. Unlike
17/// `deployer::kustomize::test_support`'s single-expectation mock, this
18/// one queues expectations per method FIFO, since several tests here
19/// call the same method (e.g. `scale`) more than once with different
20/// expected arguments.
21#[cfg(test)]
22pub(crate) mod test_support {
23    use std::sync::Mutex;
24    use std::time::Duration;
25
26    use xianyun_backends::kubectl::{Backend as KubectlBackend, KubectlError, VersionResponse};
27
28    type Responder<T> = Box<dyn FnMut(&[&str]) -> T + Send>;
29
30    /// A FIFO queue of closures for one mocked method: each call to the
31    /// method pops and invokes the next queued closure, panicking if
32    /// the queue is empty.
33    struct Queue<T> {
34        method: &'static str,
35        items: Mutex<Vec<Responder<T>>>,
36    }
37
38    impl<T> Queue<T> {
39        fn new(method: &'static str) -> Self {
40            Self {
41                method,
42                items: Mutex::new(Vec::new()),
43            }
44        }
45
46        fn push(&self, f: Responder<T>) {
47            self.items.lock().unwrap().push(f);
48        }
49
50        fn call(&self, args: &[&str]) -> T {
51            let mut items = self.items.lock().unwrap();
52            if items.is_empty() {
53                panic!("MockKubectlBackend::{}: unexpected call", self.method);
54            }
55            let mut f = items.remove(0);
56            drop(items);
57            f(args)
58        }
59    }
60
61    /// A test double for [`KubectlBackend`] with FIFO-queued
62    /// expectations for the subset of methods this module's `Manager`
63    /// calls (`get`, `scale`, `apply`, `delete`, `wait`,
64    /// `wait_for_rollout_status`, `delete_pods_by_label`). Every other
65    /// method panics if called.
66    #[derive(Default)]
67    pub(crate) struct MockKubectlBackend {
68        get: Queue<Result<String, KubectlError>>,
69        scale: Queue<Result<(), KubectlError>>,
70        apply: Queue<Result<(), KubectlError>>,
71        delete: Queue<Result<(), KubectlError>>,
72        wait: Queue<Result<(), KubectlError>>,
73        wait_for_rollout_status: Queue<Result<(), KubectlError>>,
74        delete_pods_by_label: Queue<Result<usize, KubectlError>>,
75    }
76
77    impl<T> Default for Queue<T> {
78        fn default() -> Self {
79            Self::new("<unset>")
80        }
81    }
82
83    impl MockKubectlBackend {
84        pub(crate) fn new() -> Self {
85            Self {
86                get: Queue::new("get"),
87                scale: Queue::new("scale"),
88                apply: Queue::new("apply"),
89                delete: Queue::new("delete"),
90                wait: Queue::new("wait"),
91                wait_for_rollout_status: Queue::new("wait_for_rollout_status"),
92                delete_pods_by_label: Queue::new("delete_pods_by_label"),
93            }
94        }
95
96        pub(crate) fn expect_get<F>(self, mut f: F) -> Self
97        where
98            F: FnMut(&str, &str, &str) -> Result<String, KubectlError> + Send + 'static,
99        {
100            self.get
101                .push(Box::new(move |args| f(args[0], args[1], args[2])));
102            self
103        }
104
105        pub(crate) fn expect_scale<F>(self, mut f: F) -> Self
106        where
107            F: FnMut(&str, &str, &str, i64) -> Result<(), KubectlError> + Send + 'static,
108        {
109            self.scale.push(Box::new(move |args| {
110                f(args[0], args[1], args[2], args[3].parse().unwrap())
111            }));
112            self
113        }
114
115        pub(crate) fn expect_apply<F>(self, mut f: F) -> Self
116        where
117            F: FnMut(&[u8], &str) -> Result<(), KubectlError> + Send + 'static,
118        {
119            self.apply
120                .push(Box::new(move |args| f(args[0].as_bytes(), args[1])));
121            self
122        }
123
124        pub(crate) fn expect_delete<F>(self, mut f: F) -> Self
125        where
126            F: FnMut(&str, &str, &str) -> Result<(), KubectlError> + Send + 'static,
127        {
128            self.delete
129                .push(Box::new(move |args| f(args[0], args[1], args[2])));
130            self
131        }
132
133        pub(crate) fn expect_wait<F>(self, mut f: F) -> Self
134        where
135            F: FnMut(&str, &str, &str, &str) -> Result<(), KubectlError> + Send + 'static,
136        {
137            self.wait
138                .push(Box::new(move |args| f(args[0], args[1], args[2], args[3])));
139            self
140        }
141
142        pub(crate) fn expect_wait_for_rollout_status<F>(self, mut f: F) -> Self
143        where
144            F: FnMut(&str, &str, &str) -> Result<(), KubectlError> + Send + 'static,
145        {
146            self.wait_for_rollout_status
147                .push(Box::new(move |args| f(args[0], args[1], args[2])));
148            self
149        }
150
151        pub(crate) fn expect_delete_pods_by_label<F>(self, mut f: F) -> Self
152        where
153            F: FnMut(&str, &str) -> Result<usize, KubectlError> + Send + 'static,
154        {
155            self.delete_pods_by_label
156                .push(Box::new(move |args| f(args[0], args[1])));
157            self
158        }
159    }
160
161    impl KubectlBackend for MockKubectlBackend {
162        fn get(
163            &self,
164            resource_type: &str,
165            name: &str,
166            namespace: &str,
167        ) -> Result<String, KubectlError> {
168            self.get.call(&[resource_type, name, namespace])
169        }
170
171        fn delete_pods_by_label(
172            &self,
173            namespace: &str,
174            label_selector: &str,
175        ) -> Result<usize, KubectlError> {
176            self.delete_pods_by_label.call(&[namespace, label_selector])
177        }
178
179        fn get_version(
180            &self,
181            _timeout: Duration,
182            _interval: Duration,
183        ) -> Result<VersionResponse, KubectlError> {
184            panic!("MockKubectlBackend::get_version: unexpected call")
185        }
186
187        fn scale(
188            &self,
189            resource_type: &str,
190            name: &str,
191            namespace: &str,
192            replicas: i64,
193        ) -> Result<(), KubectlError> {
194            self.scale
195                .call(&[resource_type, name, namespace, &replicas.to_string()])
196        }
197
198        fn apply(&self, json_data: &[u8], namespace: &str) -> Result<(), KubectlError> {
199            self.apply.call(&[
200                std::str::from_utf8(json_data).unwrap_or_default(),
201                namespace,
202            ])
203        }
204
205        fn delete_manifest(&self, _data: &[u8], _namespace: &str) -> Result<(), KubectlError> {
206            panic!("MockKubectlBackend::delete_manifest: unexpected call")
207        }
208
209        fn delete(
210            &self,
211            resource_type: &str,
212            name: &str,
213            namespace: &str,
214        ) -> Result<(), KubectlError> {
215            self.delete.call(&[resource_type, name, namespace])
216        }
217
218        fn wait(
219            &self,
220            resource_type: &str,
221            name: &str,
222            namespace: &str,
223            condition: &str,
224            _timeout: Duration,
225        ) -> Result<(), KubectlError> {
226            self.wait.call(&[resource_type, name, namespace, condition])
227        }
228
229        fn wait_for_rollout_status(
230            &self,
231            resource_type: &str,
232            name: &str,
233            namespace: &str,
234            _timeout: Duration,
235        ) -> Result<(), KubectlError> {
236            self.wait_for_rollout_status
237                .call(&[resource_type, name, namespace])
238        }
239
240        fn wait_for_pod_ready_by_label(
241            &self,
242            _namespace: &str,
243            _label_selector: &str,
244            _timeout: Duration,
245        ) -> Result<(), KubectlError> {
246            panic!("MockKubectlBackend::wait_for_pod_ready_by_label: unexpected call")
247        }
248
249        fn wait_for_job_complete(
250            &self,
251            _name: &str,
252            _namespace: &str,
253            _timeout: Duration,
254        ) -> Result<(), KubectlError> {
255            panic!("MockKubectlBackend::wait_for_job_complete: unexpected call")
256        }
257
258        fn wait_for_job_created(
259            &self,
260            _name: &str,
261            _namespace: &str,
262            _timeout: Duration,
263        ) -> Result<(), KubectlError> {
264            panic!("MockKubectlBackend::wait_for_job_created: unexpected call")
265        }
266
267        fn list_resource_names(
268            &self,
269            _kind: &str,
270            _namespace: &str,
271            _label_selector: &str,
272        ) -> Result<Vec<String>, KubectlError> {
273            panic!("MockKubectlBackend::list_resource_names: unexpected call")
274        }
275
276        fn ensure_namespace(&self, _name: &str) -> Result<(), KubectlError> {
277            panic!("MockKubectlBackend::ensure_namespace: unexpected call")
278        }
279
280        fn delete_namespace(&self, _name: &str) -> Result<(), KubectlError> {
281            panic!("MockKubectlBackend::delete_namespace: unexpected call")
282        }
283
284        fn namespace_exists(&self, _name: &str) -> Result<bool, KubectlError> {
285            panic!("MockKubectlBackend::namespace_exists: unexpected call")
286        }
287
288        fn wait_for_namespace_ready(
289            &self,
290            _name: &str,
291            _timeout: Duration,
292        ) -> Result<(), KubectlError> {
293            panic!("MockKubectlBackend::wait_for_namespace_ready: unexpected call")
294        }
295
296        fn run_job(
297            &self,
298            _image: &str,
299            _namespace: &str,
300            _timeout: Duration,
301            _command: &[&str],
302        ) -> Result<String, KubectlError> {
303            panic!("MockKubectlBackend::run_job: unexpected call")
304        }
305
306        fn get_raw(&self, _path: &str) -> Result<String, KubectlError> {
307            panic!("MockKubectlBackend::get_raw: unexpected call")
308        }
309    }
310}