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
246#[derive(Debug, Serialize, Deserialize, Clone, Default)]
247pub struct Layer {
248    pub name: String,
249    // #[serde(rename = "cloneUrl")]
250    pub clone_url: String,
251    pub source: Option<String>,
252    #[serde(skip_serializing_if = "Option::is_none")] // Either commit or branch is passed
253    pub commit: Option<String>,
254    pub playbook: String,
255    #[serde(skip_serializing_if = "Option::is_none")] // Either commit or branch is passed
256    pub branch: Option<String>,
257}
258
259#[derive(Debug, Serialize, Deserialize, Clone, Default)]
260pub struct AdditionalInventory {
261    #[serde(rename = "cloneUrl")]
262    pub clone_url: String,
263    #[serde(skip_serializing_if = "Option::is_none")] // Either commit or branch is passed
264    pub commit: Option<String>,
265    pub name: String,
266    #[serde(skip_serializing_if = "Option::is_none")] // Either commit or branch is passed
267    pub branch: Option<String>,
268}
269
270#[derive(Debug, Serialize, Deserialize, Clone)]
271pub struct CfsConfigurationResponse {
272    pub name: String,
273    // #[serde(rename = "lastUpdated")]
274    pub last_updated: String,
275    pub layers: Vec<Layer>,
276    #[serde(skip_serializing_if = "Option::is_none")] // Either commit or branch is passed
277    pub additional_inventory: Option<AdditionalInventory>,
278}
279
280#[derive(Debug, Serialize, Deserialize, Clone)]
281pub struct CfsConfigurationVecResponse {
282    pub configurations: Vec<CfsConfigurationResponse>,
283    pub next: Option<Next>,
284}
285
286pub struct LayerDetails {
287    pub name: String,
288    pub repo_name: String,
289    pub commit_id: String,
290    pub author: String,
291    pub commit_date: String,
292    pub branch: String,
293    pub tag: String,
294    pub playbook: String, // pub most_recent_commit: bool,
295}
296
297impl LayerDetails {
298    pub fn new(
299        name: &str,
300        repo_name: &str,
301        commit_id: &str,
302        author: &str,
303        commit_date: &str,
304        branch: &str,
305        tag: &str,
306        playbook: &str,
307        // most_recent_commit: bool,
308    ) -> Self {
309        Self {
310            name: String::from(name),
311            repo_name: String::from(repo_name),
312            commit_id: String::from(commit_id),
313            author: String::from(author),
314            commit_date: String::from(commit_date),
315            branch: branch.to_string(),
316            tag: tag.to_string(),
317            playbook: playbook.to_string(),
318            // most_recent_commit,
319        }
320    }
321}
322
323impl fmt::Display for LayerDetails {
324    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325        write!(
326            f,
327            "\n - name: {}\n - repo name: {}\n - commit id: {}\n - commit date: {}\n - author: {}\n - branch: {}\n - tag: {}\n - playbook: {}",
328            self.name, self.repo_name, self.commit_id, self.commit_date, self.author, self.branch, self.tag, self.playbook
329        )
330    }
331}
332
333/// Struct used by get_configuration when only one CFS configuration is fetched. This means we will
334/// CFS confiugration layers will have extra information from the VCS/Gitea1
335pub struct ConfigurationDetails {
336    pub name: String,
337    pub last_updated: String,
338    pub config_layers: Vec<LayerDetails>,
339}
340
341impl ConfigurationDetails {
342    pub fn new(name: &str, last_updated: &str, config_layers: Vec<LayerDetails>) -> Self {
343        Self {
344            name: String::from(name),
345            last_updated: String::from(last_updated),
346            config_layers,
347        }
348    }
349}
350
351impl fmt::Display for ConfigurationDetails {
352    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353        write!(
354            f,
355            "\nConfig Details:\n - name: {}\n - last updated: {}\nLayers:",
356            self.name, self.last_updated
357        )?;
358
359        for (i, config_layer) in self.config_layers.iter().enumerate() {
360            write!(f, "\n Layer {}:{}", i, config_layer)?;
361        }
362
363        Ok(())
364    }
365}