backend_dispatcher/types/cfs/
mod.rs

1pub mod cfs_configuration_request;
2
3use std::{collections::HashMap, fmt};
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Serialize, Deserialize, Clone)]
8pub struct CfsSessionGetResponseList {
9    pub sessions: Vec<CfsSessionGetResponse>,
10    pub next: Option<Next>,
11}
12
13#[derive(Debug, Serialize, Deserialize, Clone)] // TODO: investigate why serde can Deserialize dynamically syzed structs `Vec<Layer>`
14pub struct Next {
15    pub limit: Option<u8>,
16    pub after_id: Option<String>,
17    pub in_use: Option<bool>,
18}
19
20#[derive(Debug, Serialize, Deserialize, Clone)]
21pub struct Configuration {
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub name: Option<String>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub limit: Option<String>,
26}
27
28#[derive(Debug, Serialize, Deserialize, Clone)]
29pub struct Ansible {
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub config: Option<String>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub limit: Option<String>,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub verbosity: Option<u64>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub passthrough: Option<String>,
38}
39
40#[derive(Debug, Serialize, Deserialize, Clone)]
41pub struct Group {
42    pub name: String,
43    pub members: Vec<String>,
44}
45
46#[derive(Debug, Serialize, Deserialize, Clone)]
47pub struct ImageMap {
48    pub source_id: String,
49    pub result_name: String,
50}
51
52#[derive(Debug, Serialize, Deserialize, Clone, Default)]
53pub struct Target {
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub definition: Option<String>,
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub groups: Option<Vec<Group>>,
58    pub image_map: Option<Vec<ImageMap>>,
59}
60
61#[derive(Debug, Serialize, Deserialize, Clone)]
62pub struct Artifact {
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub image_id: Option<String>,
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub result_id: Option<String>,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub r#type: Option<String>,
69}
70
71#[derive(Debug, Serialize, Deserialize, Clone)]
72pub struct Session {
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub job: Option<String>,
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub ims_job: Option<String>,
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub completion_time: Option<String>,
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub start_time: Option<String>,
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub status: Option<String>,
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub succeeded: Option<String>,
85}
86
87#[derive(Debug, Serialize, Deserialize, Clone)]
88pub struct Status {
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub artifacts: Option<Vec<Artifact>>,
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub session: Option<Session>,
93}
94
95#[derive(Debug, Serialize, Deserialize, Clone)]
96pub struct CfsSessionGetResponse {
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub name: Option<String>,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub configuration: Option<Configuration>,
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub ansible: Option<Ansible>,
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub target: Option<Target>,
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub status: Option<Status>,
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub tags: Option<HashMap<String, String>>,
109    pub debug_on_failure: bool,
110    pub logs: Option<String>,
111}
112
113impl CfsSessionGetResponse {
114    /// Get start time
115    pub fn get_start_time(&self) -> Option<String> {
116        self.status.as_ref().and_then(|status| {
117            status
118                .session
119                .as_ref()
120                .and_then(|session| session.start_time.clone())
121        })
122    }
123
124    /// Returns list of result_ids
125    pub fn get_result_id_vec(&self) -> Vec<String> {
126        if let Some(status) = &self.status {
127            status
128                .artifacts
129                .as_ref()
130                .unwrap_or(&Vec::new())
131                .into_iter()
132                .filter(|artifact| artifact.result_id.is_some())
133                .map(|artifact| artifact.result_id.clone().unwrap())
134                .collect()
135        } else {
136            Vec::new()
137        }
138    }
139
140    /// Returns list of result_ids
141    pub fn get_first_result_id(&self) -> Option<String> {
142        CfsSessionGetResponse::get_result_id_vec(&self)
143            .first()
144            .cloned()
145    }
146
147    /* /// Returns list of result_ids
148    pub fn get_result_id(&self) -> Option<String> {
149        self.status.as_ref().and_then(|status| {
150            status.artifacts.as_ref().and_then(|artifacts| {
151                artifacts
152                    .first()
153                    .and_then(|artifact| artifact.result_id.clone())
154            })
155        })
156    } */
157
158    /// Returns list of targets (either groups or xnames)
159    pub fn get_targets(&self) -> Option<Vec<String>> {
160        Some(
161            self.get_target_hsm()
162                .unwrap_or(self.get_target_xname().unwrap()),
163        )
164    }
165
166    /// Returns list of HSM groups targeted
167    pub fn get_target_hsm(&self) -> Option<Vec<String>> {
168        self.target.as_ref().and_then(|target| {
169            target
170                .groups
171                .as_ref()
172                .map(|group_vec| group_vec.iter().map(|group| group.name.clone()).collect())
173        })
174    }
175
176    /// Returns list of xnames targeted
177    pub fn get_target_xname(&self) -> Option<Vec<String>> {
178        self.ansible.as_ref().and_then(|ansible| {
179            ansible.limit.as_ref().map(|limit| {
180                limit
181                    .split(',')
182                    .map(|xname| xname.trim().to_string())
183                    .collect()
184            })
185        })
186    }
187
188    /// Returns 'true' if the CFS session target definition is 'image'. Otherwise (target
189    /// definiton dynamic) will return 'false'
190    pub fn is_target_def_image(&self) -> bool {
191        self.get_target_def()
192            .is_some_and(|target_def| target_def == "image")
193    }
194
195    /// Returns target definition of the CFS session:
196    /// image --> CFS session to build an image
197    /// dynamic --> CFS session to configure a node
198    pub fn get_target_def(&self) -> Option<String> {
199        self.target
200            .as_ref()
201            .and_then(|target| target.definition.clone())
202    }
203
204    pub fn get_configuration_name(&self) -> Option<String> {
205        self.configuration
206            .as_ref()
207            .and_then(|configuration| configuration.name.clone())
208    }
209
210    /// Returns 'true' if CFS session succeeded
211    pub fn is_success(&self) -> bool {
212        self.status
213            .as_ref()
214            .unwrap()
215            .session
216            .as_ref()
217            .unwrap()
218            .succeeded
219            .as_ref()
220            .unwrap()
221            == "true"
222    }
223}
224
225#[derive(Debug, Serialize, Deserialize, Clone, Default)]
226pub struct CfsSessionPostRequest {
227    pub name: String,
228    pub configuration_name: String,
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub configuration_limit: Option<String>,
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub ansible_limit: Option<String>,
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub ansible_config: Option<String>,
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub ansible_verbosity: Option<u8>,
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub ansible_passthrough: Option<String>,
239    #[serde(default)]
240    pub target: Target,
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub tags: Option<HashMap<String, String>>,
243    pub debug_on_failure: bool,
244}
245
246impl CfsSessionPostRequest {
247    pub fn new(
248        name: String,
249        configuration_name: String,
250        configuration_limit: Option<String>,
251        ansible_limit: Option<String>,
252        ansible_config: Option<String>,
253        ansible_verbosity: Option<u8>,
254        ansible_passthrough: Option<String>,
255        is_target_definition_image: bool,
256        groups_name: Option<Vec<String>>,
257        base_image_id: Option<String>,
258        tags: Option<HashMap<String, String>>,
259        debug_on_failure: bool,
260        result_image_name: Option<String>,
261    ) -> Self {
262        // This code is fine... the fact that I put Self behind a variable is ok, since image param
263        // is not a default param, then doing things differently is not an issue. I checked with
264        // other Rust developers in their discord https://discord.com/channels/442252698964721669/448238009733742612/1081686300182188207
265        let mut cfs_session = Self {
266            name,
267            configuration_name,
268            configuration_limit,
269            ansible_config,
270            ansible_limit,
271            ansible_verbosity,
272            ansible_passthrough,
273            ..Default::default()
274        };
275
276        if is_target_definition_image {
277            let target_groups: Vec<Group> = groups_name
278                .unwrap()
279                .into_iter()
280                .map(|group_name| Group {
281                    name: group_name,
282                    members: vec![base_image_id.as_ref().unwrap().to_string()],
283                })
284                .collect();
285
286            cfs_session.target.definition = Some("image".to_string());
287            cfs_session.target.groups = Some(target_groups);
288            cfs_session.target.image_map = Some(vec![ImageMap {
289                            source_id: base_image_id.expect("ERROR - can't create a CFS session to build an image without base image id"),
290                            result_name: result_image_name.expect("ERROR - can't create a CFS sessions to build an image without result image name"),
291                        }]);
292        } else {
293            cfs_session.target.definition = Some("dynamic".to_string());
294            cfs_session.target.groups = None;
295            cfs_session.target.image_map = Some(Vec::new());
296        }
297
298        cfs_session.tags = tags;
299        cfs_session.debug_on_failure = debug_on_failure;
300
301        cfs_session
302    }
303}
304
305#[derive(Debug, Serialize, Deserialize, Clone, Default)]
306pub struct Layer {
307    pub name: String,
308    // #[serde(rename = "cloneUrl")]
309    pub clone_url: String,
310    pub source: Option<String>,
311    #[serde(skip_serializing_if = "Option::is_none")] // Either commit or branch is passed
312    pub commit: Option<String>,
313    pub playbook: String,
314    #[serde(skip_serializing_if = "Option::is_none")] // Either commit or branch is passed
315    pub branch: Option<String>,
316}
317
318#[derive(Debug, Serialize, Deserialize, Clone, Default)]
319pub struct AdditionalInventory {
320    #[serde(rename = "cloneUrl")]
321    pub clone_url: String,
322    #[serde(skip_serializing_if = "Option::is_none")] // Either commit or branch is passed
323    pub commit: Option<String>,
324    pub name: String,
325    #[serde(skip_serializing_if = "Option::is_none")] // Either commit or branch is passed
326    pub branch: Option<String>,
327}
328
329#[derive(Debug, Serialize, Deserialize, Clone)]
330pub struct CfsConfigurationResponse {
331    pub name: String,
332    // #[serde(rename = "lastUpdated")]
333    pub last_updated: String,
334    pub layers: Vec<Layer>,
335    #[serde(skip_serializing_if = "Option::is_none")] // Either commit or branch is passed
336    pub additional_inventory: Option<AdditionalInventory>,
337}
338
339#[derive(Debug, Serialize, Deserialize, Clone)]
340pub struct CfsConfigurationVecResponse {
341    pub configurations: Vec<CfsConfigurationResponse>,
342    pub next: Option<Next>,
343}
344
345pub struct LayerDetails {
346    pub name: String,
347    pub repo_name: String,
348    pub commit_id: String,
349    pub author: String,
350    pub commit_date: String,
351    pub branch: String,
352    pub tag: String,
353    pub playbook: String, // pub most_recent_commit: bool,
354}
355
356impl LayerDetails {
357    pub fn new(
358        name: &str,
359        repo_name: &str,
360        commit_id: &str,
361        author: &str,
362        commit_date: &str,
363        branch: &str,
364        tag: &str,
365        playbook: &str,
366        // most_recent_commit: bool,
367    ) -> Self {
368        Self {
369            name: String::from(name),
370            repo_name: String::from(repo_name),
371            commit_id: String::from(commit_id),
372            author: String::from(author),
373            commit_date: String::from(commit_date),
374            branch: branch.to_string(),
375            tag: tag.to_string(),
376            playbook: playbook.to_string(),
377            // most_recent_commit,
378        }
379    }
380}
381
382impl fmt::Display for LayerDetails {
383    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384        write!(
385            f,
386            "\n - name: {}\n - repo name: {}\n - commit id: {}\n - commit date: {}\n - author: {}\n - branch: {}\n - tag: {}\n - playbook: {}",
387            self.name, self.repo_name, self.commit_id, self.commit_date, self.author, self.branch, self.tag, self.playbook
388        )
389    }
390}
391
392/// Struct used by get_configuration when only one CFS configuration is fetched. This means we will
393/// CFS confiugration layers will have extra information from the VCS/Gitea1
394pub struct ConfigurationDetails {
395    pub name: String,
396    pub last_updated: String,
397    pub config_layers: Vec<LayerDetails>,
398}
399
400impl ConfigurationDetails {
401    pub fn new(name: &str, last_updated: &str, config_layers: Vec<LayerDetails>) -> Self {
402        Self {
403            name: String::from(name),
404            last_updated: String::from(last_updated),
405            config_layers,
406        }
407    }
408}
409
410impl fmt::Display for ConfigurationDetails {
411    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412        write!(
413            f,
414            "\nConfig Details:\n - name: {}\n - last updated: {}\nLayers:",
415            self.name, self.last_updated
416        )?;
417
418        for (i, config_layer) in self.config_layers.iter().enumerate() {
419            write!(f, "\n Layer {}:{}", i, config_layer)?;
420        }
421
422        Ok(())
423    }
424}