mesa/ims/image/
utils.rs

1use serde_json::Value;
2
3use crate::{
4    bos,
5    bss::bootparameters::http_client::get_raw,
6    hsm::group::utils::get_member_vec_from_hsm_name_vec,
7    ims::{self, image::r#struct::Image, public_keys::http_client::v3::get},
8};
9
10// Get Image using fuzzy finder, meaning returns any image which name contains a specific
11// string.
12// Used to find an image created through a CFS session and has not been renamed because manta
13// does not rename the images as SAT tool does for the sake of keeping the original image ID in
14// the CFS session which created the image.
15pub async fn get_fuzzy(
16    shasta_token: &str,
17    shasta_base_url: &str,
18    shasta_root_cert: &[u8],
19    hsm_name_available_vec: &[String],
20    image_name_opt: Option<&str>,
21    limit_number_opt: Option<&u8>,
22) -> Result<Vec<Image>, reqwest::Error> {
23    let mut image_available_vec: Vec<Image> = get_image_available_vec(
24        shasta_token,
25        shasta_base_url,
26        shasta_root_cert,
27        hsm_name_available_vec,
28        None, // NOTE: don't put any limit here since we may be looking in a large number of
29              // HSM groups and we will filter the results by image name below
30    )
31    .await;
32
33    if let Some(image_name) = image_name_opt {
34        image_available_vec.retain(|image| image.name.contains(image_name));
35    }
36
37    if let Some(limit_number) = limit_number_opt {
38        // Limiting the number of results to return to client
39        image_available_vec = image_available_vec[image_available_vec
40            .len()
41            .saturating_sub(*limit_number as usize)..]
42            .to_vec();
43    }
44
45    Ok(image_available_vec.to_vec())
46}
47
48/// Returns a tuple like(Image sruct, cfs configuration name, list of target - either hsm group name
49/// or xnames, bool - indicates if image is used to boot a node or not)
50/// This method tries to filter by HSM group which means it will make use of:
51///  - CFS sessions to find which image id was created against which HSM group
52///  - BOS sessiontemplates to find the HSM group related to nodes being rebooted in the past
53///  - Image ids in boot params for nodes in HSM groups we are looking for (This is needed to not miss
54/// images currenly used which name may not have HSM group we are looking for included not CFS
55/// session nor BOS sessiontemplate)
56///  - Image names with HSM group name included (This is a bad practice because this is a free text
57/// prone to human errors)
58pub async fn filter(
59    shasta_token: &str,
60    shasta_base_url: &str,
61    shasta_root_cert: &[u8],
62    image_vec: &mut Vec<Image>,
63    hsm_group_name_vec: &[String],
64    limit_number_opt: Option<&u8>,
65) -> Vec<(Image, String, String, bool)> {
66    if let Some(limit_number) = limit_number_opt {
67        // Limiting the number of results to return to client
68        *image_vec = image_vec[image_vec.len().saturating_sub(*limit_number as usize)..].to_vec();
69    }
70
71    // Fetch and filter BOS session templates
72    //
73    // We need BOS session templates to find an image created by SAT
74    let mut bos_sessiontemplate_value_vec = crate::bos::template::mesa::http_client::get(
75        shasta_token,
76        shasta_base_url,
77        shasta_root_cert,
78        None,
79    )
80    .await
81    .unwrap();
82
83    bos::template::mesa::utils::filter(
84        &mut bos_sessiontemplate_value_vec,
85        hsm_group_name_vec,
86        &Vec::new(),
87        // None,
88        None,
89    )
90    .await;
91
92    // Fetch and filter CFS sessions
93    //
94    // We need CFS sessions to find images without a BOS session template (hopefully the CFS
95    // session has not been deleted by CSCS staff, otherwise it will be technically impossible to
96    // find unless we search images by HSM name and expect HSM name to be in image name...)
97    let mut cfs_session_vec = crate::cfs::session::mesa::http_client::get(
98        shasta_token,
99        shasta_base_url,
100        shasta_root_cert,
101        None,
102        None,
103        None,
104        None,
105        Some(true),
106    )
107    .await
108    .unwrap();
109
110    crate::cfs::session::mesa::utils::filter_by_hsm(
111        shasta_token,
112        shasta_base_url,
113        shasta_root_cert,
114        &mut cfs_session_vec,
115        hsm_group_name_vec,
116        None,
117        crate::common::jwt_ops::is_user_admin(shasta_token).unwrap(),
118    )
119    .await;
120
121    // Get IMAGES in nodes boot params
122    let mut image_id_cfs_configuration_from_cfs_session: Vec<(String, String, Vec<String>)> =
123        crate::cfs::session::mesa::utils::get_image_id_cfs_configuration_target_for_existing_images_tuple_vec(
124            &cfs_session_vec,
125        );
126
127    image_id_cfs_configuration_from_cfs_session
128        .retain(|(image_id, _cfs_configuration, _hsm_groups)| !image_id.is_empty());
129
130    /* let mut image_id_cfs_configuration_from_cfs_session_vec: Vec<(String, String, Vec<String>)> =
131        crate::cfs::session::mesa::utils::get_image_id_cfs_configuration_target_for_existing_images_tuple_vec(
132            &cfs_session_vec,
133        );
134
135    image_id_cfs_configuration_from_cfs_session_vec
136        .retain(|(image_id, _cfs_confguration, _hsm_groups)| !image_id.is_empty()); */
137
138    // Get IMAGES in nodes boot params. This is because CSCS staff deletes the CFS sessions and/or
139    // BOS sessiontemplate breaking the history with actual state, therefore I need to go to boot
140    // params to get the image id used to boot the nodes belonging to a HSM group
141    let hsm_member_vec = get_member_vec_from_hsm_name_vec(
142        shasta_token,
143        shasta_base_url,
144        shasta_root_cert,
145        hsm_group_name_vec.to_vec(),
146    )
147    .await;
148
149    let boot_param_vec = get_raw(
150        shasta_token,
151        shasta_base_url,
152        shasta_root_cert,
153        &hsm_member_vec,
154    )
155    .await
156    .unwrap_or(Vec::new());
157
158    let image_id_from_boot_params: Vec<String> = boot_param_vec
159        .iter()
160        .map(|boot_param| boot_param.get_boot_image())
161        .collect();
162
163    // Get Image details from IMS images API endpoint
164    let mut image_detail_vec: Vec<(Image, String, String, bool)> = Vec::new();
165
166    for image in image_vec {
167        let image_id = image.id.as_ref().unwrap();
168
169        let target_group_name_vec: Vec<String>;
170        let cfs_configuration: String;
171        let target_groups: String;
172        let boot_image: bool;
173
174        if let Some(tuple) = image_id_cfs_configuration_from_cfs_session
175            .iter()
176            .find(|tuple| tuple.0.eq(image_id))
177        {
178            // Image details in CFS session
179            cfs_configuration = tuple.clone().1;
180            target_group_name_vec = tuple.2.clone();
181            target_groups = target_group_name_vec.join(", ");
182        /* } else if let Some(tuple) = image_id_cfs_configuration_from_cfs_session
183            .iter()
184            .find(|tuple| tuple.0.eq(image_id))
185        {
186            // Image details in BOS session template
187            cfs_configuration = tuple.clone().1;
188            target_group_name_vec = tuple.2.clone();
189            target_groups = target_group_name_vec.join(", "); */
190        } else if let Some(boot_params) = boot_param_vec
191            .iter()
192            .find(|boot_params| boot_params.get_boot_image().eq(image_id))
193        {
194            // Image details where image is found in a node boot param related to HSM we are
195            // working with
196            // Boot params don't have CFS configuration information
197            cfs_configuration = "Not found".to_string();
198            target_groups = boot_params.hosts.clone().join(",");
199        } else if hsm_group_name_vec
200            .iter()
201            .any(|hsm_group_name| image.name.contains(hsm_group_name))
202        {
203            // Image details where image name contains HSM group name available to the user.
204            // Boot params don't have CFS configuration information
205            // NOTE: CSCS specific
206            cfs_configuration = "Not found".to_string();
207
208            target_groups = "Not found".to_string();
209        } else {
210            continue;
211        }
212
213        // NOTE: 'boot_image' needs to be processed outside the 'if' statement. Otherwise we may
214        // miss images used to boot nodes filtered by a different branch in the 'if' statement
215        boot_image = if image_id_from_boot_params.contains(image_id) {
216            true
217        } else {
218            false
219        };
220
221        image_detail_vec.push((
222            image.clone(),
223            cfs_configuration.to_string(),
224            target_groups.clone(),
225            boot_image,
226        ));
227    }
228
229    image_detail_vec
230}
231
232/// Returns a tuple like (Image struct, cfs configuration, target groups) with the cfs
233/// configuration related to that image struct and the target groups booting that image
234/// This list is filtered by the HSM groups the user has access to
235/// Exception are images containing 'generic' in their names since those could be used by anyone
236pub async fn get_image_available_vec(
237    shasta_token: &str,
238    shasta_base_url: &str,
239    shasta_root_cert: &[u8],
240    hsm_name_available_vec: &[String],
241    limit_number_opt: Option<&u8>,
242) -> Vec<Image> {
243    let mut image_vec: Vec<Image> =
244        super::mesa::http_client::get(shasta_token, shasta_base_url, shasta_root_cert, None)
245            .await
246            .unwrap();
247
248    ims::image::mesa::utils::filter(&mut image_vec).await;
249
250    // We need BOS session templates to find an image created by SAT
251    let mut bos_sessiontemplate_vec = bos::template::mesa::http_client::get(
252        shasta_token,
253        shasta_base_url,
254        shasta_root_cert,
255        None,
256    )
257    .await
258    .unwrap();
259
260    // Filter BOS sessiontemplates to the ones the user has access to
261    bos::template::mesa::utils::filter(
262        &mut bos_sessiontemplate_vec,
263        hsm_name_available_vec,
264        &Vec::new(),
265        None,
266    )
267    .await;
268
269    // We need CFS sessions to find images without a BOS session template
270    let mut cfs_session_vec = crate::cfs::session::mesa::http_client::get(
271        shasta_token,
272        shasta_base_url,
273        shasta_root_cert,
274        None,
275        None,
276        None,
277        None,
278        Some(true),
279    )
280    .await
281    .unwrap();
282
283    // Filter CFS sessions to the ones the user has access to
284    crate::cfs::session::mesa::utils::filter_by_hsm(
285        shasta_token,
286        shasta_base_url,
287        shasta_root_cert,
288        &mut cfs_session_vec,
289        hsm_name_available_vec,
290        None,
291        true,
292    )
293    .await;
294
295    let mut image_id_cfs_configuration_from_bos_sessiontemplate: Vec<(
296        String,
297        String,
298        Vec<String>,
299    )> = crate::bos::template::mesa::utils::get_image_id_cfs_configuration_target_tuple_vec(
300        bos_sessiontemplate_vec,
301    );
302
303    image_id_cfs_configuration_from_bos_sessiontemplate
304        .retain(|(image_id, _cfs_configuration, _hsm_groups)| !image_id.is_empty());
305
306    let mut image_id_cfs_configuration_from_cfs_session_vec: Vec<(String, String, Vec<String>)> =
307        crate::cfs::session::mesa::utils::get_image_id_cfs_configuration_target_for_existing_images_tuple_vec(
308            &cfs_session_vec,
309        );
310
311    image_id_cfs_configuration_from_cfs_session_vec
312        .retain(|(image_id, _cfs_confguration, _hsm_groups)| !image_id.is_empty());
313
314    let mut image_available_vec: Vec<Image> = Vec::new();
315
316    for image in &image_vec {
317        let image_id = image.id.as_ref().unwrap();
318
319        if image_id_cfs_configuration_from_bos_sessiontemplate
320            .iter()
321            .any(|tuple| tuple.0.eq(image_id))
322        {
323            // If image is related to a BOS sessiontemplate related to a HSM group the user has
324            // access to, then, we include this image to the list of images available to the user
325            image_available_vec.push(image.clone());
326        } else if image_id_cfs_configuration_from_cfs_session_vec
327            .iter()
328            .any(|tuple| tuple.0.eq(image_id))
329        {
330            // If image was created using a CFS session with HSM groups related to the user, then
331            // we include this image to the list of images available to the user
332            // FIXME: this needs to go away if we extend groups in CFS sessions to technology
333            // rather than clusters
334            image_available_vec.push(image.clone());
335        } else if hsm_name_available_vec
336            .iter()
337            .any(|hsm_group_name| image.name.contains(hsm_group_name))
338        {
339            // If image name contains HSM group the user is working on, then, we include the image
340            // to the list of images available to the user
341            // FIXME: this should not be allowed... but CSCS staff deletes the CFS sessions so we
342            // are extending the rules that defines if a user has access to an image
343            image_available_vec.push(image.clone());
344        } else if image.name.to_lowercase().contains("generic") {
345            // If image is generic (meaning image name contains the word "generic"), then, the image
346            // will be available to everyone, therefore it should be included to the list of images
347            // available to the user
348            // FIXME: This is should not be allowed since it is too vague, we concept of generic is
349            // not limited to anything, a tenant may create an image which name contains "generic"
350            // but they don't want to share it with other tenants meaning the scope of generic here
351            // does not moves across tenants boundaries
352            image_available_vec.push(image.clone())
353        } else {
354            continue;
355        }
356
357        // let target_groups = target_group_name_vec.join(", ");
358    }
359
360    if let Some(limit_number) = limit_number_opt {
361        // Limiting the number of results to return to client
362        image_available_vec = image_available_vec[image_available_vec
363            .len()
364            .saturating_sub(*limit_number as usize)..]
365            .to_vec();
366    }
367
368    image_available_vec
369}
370
371/// Register a new image in IMS --> https://github.com/Cray-HPE/docs-csm/blob/release/1.5/api/ims.md#post_v2_image
372pub async fn register_new_image(
373    shasta_token: &str,
374    shasta_base_url: &str,
375    shasta_root_cert: &[u8],
376    ims_image: &Image,
377) -> Result<Value, reqwest::Error> {
378    let client;
379
380    let client_builder = reqwest::Client::builder()
381        .add_root_certificate(reqwest::Certificate::from_pem(shasta_root_cert)?);
382
383    // Build client
384    if std::env::var("SOCKS5").is_ok() {
385        // socks5 proxy
386        log::debug!("SOCKS5 enabled");
387        let socks5proxy = reqwest::Proxy::all(std::env::var("SOCKS5").unwrap())?;
388
389        // rest client to authenticate
390        client = client_builder.proxy(socks5proxy).build()?;
391    } else {
392        client = client_builder.build()?;
393    }
394
395    let api_url = shasta_base_url.to_owned() + "/ims/v3/images";
396
397    client
398        .post(api_url)
399        .header("Authorization", format!("Bearer {}", shasta_token))
400        .json(&ims_image)
401        .send()
402        .await?
403        .error_for_status()?
404        .json()
405        .await
406}
407
408/// Returns the first user public key in IMS is can find
409pub async fn get_single(
410    shasta_token: &str,
411    shasta_base_url: &str,
412    shasta_root_cert: &[u8],
413    username_opt: &str,
414) -> Option<Value> {
415    if let Ok(public_key_value_list) = get(
416        shasta_token,
417        shasta_base_url,
418        shasta_root_cert,
419        Some(username_opt),
420    )
421    .await
422    {
423        if public_key_value_list.len() == 1 {
424            return public_key_value_list.first().cloned();
425        };
426    }
427
428    None
429}