xianyun_deploy/reloader/mod.rs
1//! Interfaces for running local development processes with hot-reload
2//! capabilities.
3//!
4//! Ported from `internal/reloader/{interface,errors}.go`.
5
6use thiserror::Error;
7use xianyun_core::config::latest::{CaproniConfig, ReloaderConfig};
8
9pub mod mirror;
10pub mod registry;
11pub mod skaffold;
12
13/// The current state of a reloader.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum State {
16 /// The target deployment doesn't exist.
17 NotDeployed,
18 /// The reloader cannot run (e.g. target resource missing).
19 NotReady,
20 /// The reloader is ready to run.
21 Ready,
22 /// The reloader is actively running with `caproni run`.
23 Editing,
24 /// The reloader failed.
25 Failed,
26}
27
28impl State {
29 /// The string form used in Go (`"not-deployed"`, etc.), for display
30 /// and any serialization boundary.
31 #[must_use]
32 pub fn as_str(&self) -> &'static str {
33 match self {
34 State::NotDeployed => "not-deployed",
35 State::NotReady => "not-ready",
36 State::Ready => "ready",
37 State::Editing => "editing",
38 State::Failed => "failed",
39 }
40 }
41
42 /// A human-readable label for this state. Ported from
43 /// `State.PrettyString`; the Go original also colors the string via
44 /// `github.com/gookit/color`, which has no equivalent dependency in
45 /// this crate yet, so this port returns the plain label. Adding
46 /// color is Phase 4 UI polish, not behavior this crate's tests
47 /// depend on.
48 #[must_use]
49 pub fn pretty_string(&self) -> &'static str {
50 match self {
51 State::NotDeployed => "Not deployed",
52 State::NotReady => "Not ready",
53 State::Ready => "Ready",
54 State::Editing => "Editing",
55 State::Failed => "Failed",
56 }
57 }
58}
59
60/// The current status of a reloader.
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct Status {
63 /// The current state of the reloader.
64 pub state: State,
65 /// The reloader type (e.g. `"mirror"`).
66 pub reloader_type: String,
67}
68
69/// Errors common across reloader managers.
70#[derive(Debug, Error)]
71pub enum ReloaderError {
72 /// Trying to create a reloader with an unknown type.
73 #[error("unknown reloader type")]
74 UnknownType,
75 /// A reloader run operation failed.
76 #[error("reloader run failed")]
77 RunFailed,
78 /// Unable to determine reloader status.
79 #[error("failed to get reloader status")]
80 StatusFailed,
81 /// The target resource doesn't exist.
82 #[error("target resource not found")]
83 TargetNotFound,
84}
85
86/// The interface for managing local development processes.
87/// Implementations are responsible for running processes with
88/// hot-reload capabilities using specific strategies (e.g. mirrord,
89/// skaffold).
90pub trait Manager {
91 /// The reloader type that this manager handles (e.g. `"mirror"`,
92 /// `"skaffold"`).
93 fn reloader_type(&self) -> String;
94
95 /// Verifies that the environment is ready for this reloader to
96 /// run. For example, mirror reloaders check that no stale
97 /// `*-caproni` deployments exist. Returns `Ok(())` if all
98 /// preconditions are met.
99 fn check_preconditions(
100 &self,
101 reloader_config: &ReloaderConfig,
102 caproni_config: &CaproniConfig,
103 get_tool_version: &dyn Fn(&str) -> Option<String>,
104 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
105
106 /// Runs the reloader processes, blocking until cancelled.
107 ///
108 /// `kubeconfig_path` is the path to a temporary kubeconfig for the
109 /// caproni-selected cluster (created and owned by the caller).
110 /// Implementations that launch external tooling should pin it to
111 /// this kubeconfig rather than relying on the user's global
112 /// current-context. It may be empty.
113 fn run(
114 &self,
115 reloader_config: &ReloaderConfig,
116 caproni_config: &CaproniConfig,
117 kubeconfig_path: &str,
118 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
119
120 /// Returns the current status of the reloader. Returns an error if
121 /// unable to determine reloader state.
122 fn status(
123 &self,
124 reloader_config: &ReloaderConfig,
125 caproni_config: &CaproniConfig,
126 ) -> Result<Status, Box<dyn std::error::Error + Send + Sync>>;
127
128 /// Validates the reloader configuration and referenced files.
129 /// Returns `Ok(())` if valid, or an error describing validation
130 /// failures. Files in uncloned repository directories should not
131 /// cause validation to fail.
132 fn validate(
133 &self,
134 reloader_config: &ReloaderConfig,
135 caproni_config: &CaproniConfig,
136 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 /// `State` has no Go counterpart to port a test from: Go's `State` is a
144 /// typed `string` constant, so there's no conversion function to call.
145 /// `as_str()` only exists because Rust's `State` is a real enum; this is
146 /// new coverage for it.
147 #[test]
148 fn state_as_str_matches_go_string_values() {
149 assert_eq!(State::NotDeployed.as_str(), "not-deployed");
150 assert_eq!(State::NotReady.as_str(), "not-ready");
151 assert_eq!(State::Ready.as_str(), "ready");
152 assert_eq!(State::Editing.as_str(), "editing");
153 assert_eq!(State::Failed.as_str(), "failed");
154 }
155}