xianyun_cluster/clusterdomain/
resolve.rs1use super::{ClusterDomainError, discover, sources_for};
12use crate::driver::{DnsReader, Driver, DriverError, State};
13use thiserror::Error;
14use xianyun_core::config::container::Container;
15use xianyun_core::config::global::{self, GlobalConfig};
16
17pub const CONFIGURED_SOURCE: &str = "cluster.network.cluster_domain";
19
20#[derive(Debug, Error, PartialEq, Eq)]
24pub enum ResolveError {
25 #[error("cluster DNS domain could not be determined: {0}")]
26 ClusterDomainUnknown(String),
27 #[error(
28 "cluster configuration mismatch: {zone_source} reports {discovered} but cluster.network.cluster_domain is set to {configured}"
29 )]
30 ConfigMismatch {
31 zone_source: String,
32 discovered: String,
33 configured: String,
34 },
35 #[error("cluster is not running (state: {0:?})")]
36 ClusterNotRunning(Option<State>),
37 #[error("{0}")]
38 Domain(#[from] ClusterDomainError),
39 #[error("{0}")]
40 Driver(#[from] DriverError),
41}
42
43pub fn resolve(
47 cfg: &mut Container,
48 mgr: Option<&dyn Driver>,
49 reader: &dyn DnsReader,
50) -> Result<(), ResolveError> {
51 let configured = configured_domain(cfg);
52 let discovered = discover(reader, &sources_for(mgr));
53
54 match &discovered {
55 Err(e) if configured.is_empty() => {
56 return Err(ResolveError::ClusterDomainUnknown(format!(
57 "{e}; set cluster.network.cluster_domain"
58 )));
59 }
60 Err(e) => {
61 tracing::warn!(
62 configured,
63 error = %e,
64 "could not confirm cluster.network.cluster_domain against cluster DNS"
65 );
66 }
67 Ok(result) if !configured.is_empty() && configured != result.zone => {
68 return Err(ResolveError::ConfigMismatch {
69 zone_source: result.source.clone(),
70 discovered: result.zone.clone(),
71 configured,
72 });
73 }
74 Ok(result) if configured.is_empty() => {
75 if let Some(network) = cfg.caproni.cluster.network.as_mut() {
76 network.cluster_domain = result.zone.clone();
77 }
78 cfg.global.cluster_domain = result.zone.clone();
79 cfg.global.cluster_domain_source = result.source.clone();
80
81 tracing::info!(
82 domain = result.zone,
83 source = result.source,
84 "cluster DNS domain read from the cluster"
85 );
86
87 return Ok(());
88 }
89 Ok(_) => {}
90 }
91
92 cfg.global.cluster_domain = configured;
93 cfg.global.cluster_domain_source = CONFIGURED_SOURCE.to_string();
94
95 Ok(())
96}
97
98pub fn resolve_and_reload(
110 cfg: &mut Container,
111 mgr: Option<&dyn Driver>,
112 reader: &dyn DnsReader,
113) -> Result<Container, ResolveError> {
114 resolve(cfg, mgr, reader)?;
115
116 let ctx = global::with_context(global::Context::default(), cfg.global.clone());
117
118 xianyun_core::config::caproni::load_from_context(&ctx)
119 .map_err(|e| ResolveError::ClusterDomainUnknown(format!("reloading configuration: {e}")))
120}
121
122pub type Connector = fn(&dyn Driver) -> Result<Box<dyn DnsReader>, ResolveError>;
125
126pub fn resolve_running(
131 cfg: &mut Container,
132 mgr: &dyn Driver,
133 connect: Connector,
134) -> (Option<ResolveError>, Option<ResolveError>) {
135 let status = match mgr.status() {
136 Ok(s) => s,
137 Err(e) => return (None, Some(ResolveError::Driver(e))),
138 };
139
140 if status.state != Some(State::Ready) {
141 let not_running = ResolveError::ClusterNotRunning(status.state);
142
143 let configured = configured_domain(cfg);
144 if configured.is_empty() {
145 return (
146 None,
147 Some(ResolveError::ClusterDomainUnknown(format!(
148 "cluster.network.cluster_domain is not set and the {not_running}; set it or run `caproni up`"
149 ))),
150 );
151 }
152
153 cfg.global.cluster_domain = configured.clone();
154 cfg.global.cluster_domain_source = CONFIGURED_SOURCE.to_string();
155
156 tracing::info!(
157 domain = configured,
158 reason = %not_running,
159 "cluster DNS domain left as configured"
160 );
161
162 return (Some(not_running), None);
163 }
164
165 match connect(mgr) {
166 Ok(reader) => (None, resolve(cfg, Some(mgr), reader.as_ref()).err()),
167 Err(e) => (None, Some(e)),
168 }
169}
170
171pub fn configured_domain(cfg: &Container) -> String {
174 cfg.caproni
175 .cluster
176 .network
177 .as_ref()
178 .map(|n| n.cluster_domain.clone())
179 .unwrap_or_default()
180}
181
182#[doc(hidden)]
186#[must_use]
187pub fn container_with_domain(driver: &str, profile_name: &str, configured: &str) -> Container {
188 use xianyun_core::config::latest::CaproniConfig;
189 use xianyun_core::config::types::{ClusterConfig, NetworkConfig};
190
191 let cfg = CaproniConfig {
192 cluster: ClusterConfig {
193 driver: driver.to_string(),
194 network: Some(NetworkConfig {
195 hostname: "gitlab.test".to_string(),
196 cluster_domain: configured.to_string(),
197 ..Default::default()
198 }),
199 ..Default::default()
200 },
201 ..Default::default()
202 };
203
204 let mut global = GlobalConfig::new(None);
205 global.profile_name = profile_name.to_string();
206
207 xianyun_core::config::container::new_container(global, cfg)
208}