1use crate::api::machine::{
9 apply_configuration_request::Mode as ProtoMode, ApplyConfiguration as ProtoApplyConfiguration,
10 ApplyConfigurationRequest as ProtoRequest, ApplyConfigurationResponse as ProtoResponse,
11};
12use std::time::Duration;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
18pub enum ApplyMode {
19 Reboot,
21 #[default]
23 Auto,
24 NoReboot,
26 Staged,
28 Try,
30}
31
32impl From<ApplyMode> for i32 {
33 fn from(mode: ApplyMode) -> Self {
34 match mode {
35 ApplyMode::Reboot => ProtoMode::Reboot as i32,
36 ApplyMode::Auto => ProtoMode::Auto as i32,
37 ApplyMode::NoReboot => ProtoMode::NoReboot as i32,
38 ApplyMode::Staged => ProtoMode::Staged as i32,
39 ApplyMode::Try => ProtoMode::Try as i32,
40 }
41 }
42}
43
44impl From<i32> for ApplyMode {
45 fn from(value: i32) -> Self {
46 match value {
47 0 => ApplyMode::Reboot,
48 1 => ApplyMode::Auto,
49 2 => ApplyMode::NoReboot,
50 3 => ApplyMode::Staged,
51 4 => ApplyMode::Try,
52 _ => ApplyMode::Auto, }
54 }
55}
56
57impl std::fmt::Display for ApplyMode {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 match self {
60 ApplyMode::Reboot => write!(f, "reboot"),
61 ApplyMode::Auto => write!(f, "auto"),
62 ApplyMode::NoReboot => write!(f, "no-reboot"),
63 ApplyMode::Staged => write!(f, "staged"),
64 ApplyMode::Try => write!(f, "try"),
65 }
66 }
67}
68
69#[derive(Debug, Clone, Default)]
84pub struct ApplyConfigurationRequest {
85 pub data: Vec<u8>,
87 pub mode: ApplyMode,
89 pub dry_run: bool,
91 pub try_mode_timeout: Option<Duration>,
93}
94
95impl ApplyConfigurationRequest {
96 #[must_use]
98 pub fn builder() -> ApplyConfigurationRequestBuilder {
99 ApplyConfigurationRequestBuilder::default()
100 }
101
102 #[must_use]
104 pub fn from_yaml(yaml: impl AsRef<str>) -> Self {
105 Self {
106 data: yaml.as_ref().as_bytes().to_vec(),
107 mode: ApplyMode::Auto,
108 dry_run: false,
109 try_mode_timeout: None,
110 }
111 }
112
113 #[must_use]
115 pub fn from_bytes(data: Vec<u8>) -> Self {
116 Self {
117 data,
118 mode: ApplyMode::Auto,
119 dry_run: false,
120 try_mode_timeout: None,
121 }
122 }
123}
124
125impl From<ApplyConfigurationRequest> for ProtoRequest {
126 fn from(req: ApplyConfigurationRequest) -> Self {
127 ProtoRequest {
128 data: req.data,
129 mode: req.mode.into(),
130 dry_run: req.dry_run,
131 try_mode_timeout: req.try_mode_timeout.map(|d| prost_types::Duration {
132 seconds: d.as_secs() as i64,
133 nanos: d.subsec_nanos() as i32,
134 }),
135 }
136 }
137}
138
139#[derive(Debug, Clone, Default)]
141pub struct ApplyConfigurationRequestBuilder {
142 data: Vec<u8>,
143 mode: ApplyMode,
144 dry_run: bool,
145 try_mode_timeout: Option<Duration>,
146}
147
148impl ApplyConfigurationRequestBuilder {
149 #[must_use]
151 pub fn config_yaml(mut self, yaml: impl AsRef<str>) -> Self {
152 self.data = yaml.as_ref().as_bytes().to_vec();
153 self
154 }
155
156 #[must_use]
158 pub fn config_bytes(mut self, data: Vec<u8>) -> Self {
159 self.data = data;
160 self
161 }
162
163 pub fn config_file(mut self, path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
169 self.data = std::fs::read(path)?;
170 Ok(self)
171 }
172
173 #[must_use]
175 pub fn mode(mut self, mode: ApplyMode) -> Self {
176 self.mode = mode;
177 self
178 }
179
180 #[must_use]
182 pub fn dry_run(mut self, dry_run: bool) -> Self {
183 self.dry_run = dry_run;
184 self
185 }
186
187 #[must_use]
189 pub fn try_mode_timeout(mut self, timeout: Duration) -> Self {
190 self.try_mode_timeout = Some(timeout);
191 self
192 }
193
194 #[must_use]
196 pub fn build(self) -> ApplyConfigurationRequest {
197 ApplyConfigurationRequest {
198 data: self.data,
199 mode: self.mode,
200 dry_run: self.dry_run,
201 try_mode_timeout: self.try_mode_timeout,
202 }
203 }
204}
205
206#[derive(Debug, Clone)]
208pub struct ApplyConfigurationResult {
209 pub node: Option<String>,
211 pub warnings: Vec<String>,
213 pub mode: ApplyMode,
215 pub mode_details: String,
217}
218
219impl From<ProtoApplyConfiguration> for ApplyConfigurationResult {
220 fn from(proto: ProtoApplyConfiguration) -> Self {
221 Self {
222 node: proto.metadata.map(|m| m.hostname),
223 warnings: proto.warnings,
224 mode: proto.mode.into(),
225 mode_details: proto.mode_details,
226 }
227 }
228}
229
230#[derive(Debug, Clone)]
232pub struct ApplyConfigurationResponse {
233 pub results: Vec<ApplyConfigurationResult>,
235}
236
237impl From<ProtoResponse> for ApplyConfigurationResponse {
238 fn from(proto: ProtoResponse) -> Self {
239 Self {
240 results: proto.messages.into_iter().map(Into::into).collect(),
241 }
242 }
243}
244
245impl ApplyConfigurationResponse {
246 #[must_use]
248 pub fn is_success(&self) -> bool {
249 self.results.iter().all(|r| r.warnings.is_empty())
250 }
251
252 #[must_use]
254 pub fn all_warnings(&self) -> Vec<&str> {
255 self.results
256 .iter()
257 .flat_map(|r| r.warnings.iter().map(String::as_str))
258 .collect()
259 }
260
261 #[must_use]
263 pub fn first(&self) -> Option<&ApplyConfigurationResult> {
264 self.results.first()
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use super::*;
271
272 #[test]
273 fn test_apply_mode_conversion() {
274 assert_eq!(i32::from(ApplyMode::Reboot), 0);
275 assert_eq!(i32::from(ApplyMode::Auto), 1);
276 assert_eq!(i32::from(ApplyMode::NoReboot), 2);
277 assert_eq!(i32::from(ApplyMode::Staged), 3);
278 assert_eq!(i32::from(ApplyMode::Try), 4);
279
280 assert_eq!(ApplyMode::from(0), ApplyMode::Reboot);
281 assert_eq!(ApplyMode::from(1), ApplyMode::Auto);
282 assert_eq!(ApplyMode::from(2), ApplyMode::NoReboot);
283 assert_eq!(ApplyMode::from(3), ApplyMode::Staged);
284 assert_eq!(ApplyMode::from(4), ApplyMode::Try);
285 }
286
287 #[test]
288 fn test_builder_pattern() {
289 let request = ApplyConfigurationRequest::builder()
290 .config_yaml("machine:\n type: worker")
291 .mode(ApplyMode::NoReboot)
292 .dry_run(true)
293 .try_mode_timeout(Duration::from_secs(60))
294 .build();
295
296 assert_eq!(request.data, b"machine:\n type: worker");
297 assert_eq!(request.mode, ApplyMode::NoReboot);
298 assert!(request.dry_run);
299 assert_eq!(request.try_mode_timeout, Some(Duration::from_secs(60)));
300 }
301
302 #[test]
303 fn test_from_yaml() {
304 let request = ApplyConfigurationRequest::from_yaml("test: config");
305 assert_eq!(request.data, b"test: config");
306 assert_eq!(request.mode, ApplyMode::Auto);
307 assert!(!request.dry_run);
308 }
309
310 #[test]
311 fn test_proto_conversion() {
312 let request = ApplyConfigurationRequest::builder()
313 .config_yaml("test")
314 .mode(ApplyMode::Staged)
315 .dry_run(true)
316 .try_mode_timeout(Duration::from_secs(120))
317 .build();
318
319 let proto: ProtoRequest = request.into();
320 assert_eq!(proto.data, b"test");
321 assert_eq!(proto.mode, ProtoMode::Staged as i32);
322 assert!(proto.dry_run);
323 assert!(proto.try_mode_timeout.is_some());
324 }
325
326 #[test]
327 fn test_apply_mode_display() {
328 assert_eq!(ApplyMode::Reboot.to_string(), "reboot");
329 assert_eq!(ApplyMode::Auto.to_string(), "auto");
330 assert_eq!(ApplyMode::NoReboot.to_string(), "no-reboot");
331 assert_eq!(ApplyMode::Staged.to_string(), "staged");
332 assert_eq!(ApplyMode::Try.to_string(), "try");
333 }
334}