mesa 0.43.18

A library for Shasta
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
use serde_json::Value;

use crate::{
    bos,
    bss::bootparameters::http_client::get_raw,
    hsm::group::utils::get_member_vec_from_hsm_name_vec,
    ims::{self, image::r#struct::Image, public_keys::http_client::v3::get},
};

// Get Image using fuzzy finder, meaning returns any image which name contains a specific
// string.
// Used to find an image created through a CFS session and has not been renamed because manta
// does not rename the images as SAT tool does for the sake of keeping the original image ID in
// the CFS session which created the image.
pub async fn get_fuzzy(
    shasta_token: &str,
    shasta_base_url: &str,
    shasta_root_cert: &[u8],
    hsm_name_available_vec: &[String],
    image_name_opt: Option<&str>,
    limit_number_opt: Option<&u8>,
) -> Result<Vec<Image>, reqwest::Error> {
    let mut image_available_vec: Vec<Image> = get_image_available_vec(
        shasta_token,
        shasta_base_url,
        shasta_root_cert,
        hsm_name_available_vec,
        None, // NOTE: don't put any limit here since we may be looking in a large number of
              // HSM groups and we will filter the results by image name below
    )
    .await;

    if let Some(image_name) = image_name_opt {
        image_available_vec.retain(|image| image.name.contains(image_name));
    }

    if let Some(limit_number) = limit_number_opt {
        // Limiting the number of results to return to client
        image_available_vec = image_available_vec[image_available_vec
            .len()
            .saturating_sub(*limit_number as usize)..]
            .to_vec();
    }

    Ok(image_available_vec.to_vec())
}

/// Returns a tuple like(Image sruct, cfs configuration name, list of target - either hsm group name
/// or xnames, bool - indicates if image is used to boot a node or not)
/// This method tries to filter by HSM group which means it will make use of:
///  - CFS sessions to find which image id was created against which HSM group
///  - BOS sessiontemplates to find the HSM group related to nodes being rebooted in the past
///  - Image ids in boot params for nodes in HSM groups we are looking for (This is needed to not miss
/// images currenly used which name may not have HSM group we are looking for included not CFS
/// session nor BOS sessiontemplate)
///  - Image names with HSM group name included (This is a bad practice because this is a free text
/// prone to human errors)
pub async fn filter(
    shasta_token: &str,
    shasta_base_url: &str,
    shasta_root_cert: &[u8],
    image_vec: &mut Vec<Image>,
    hsm_group_name_vec: &[String],
    limit_number_opt: Option<&u8>,
) -> Vec<(Image, String, String, bool)> {
    if let Some(limit_number) = limit_number_opt {
        // Limiting the number of results to return to client
        *image_vec = image_vec[image_vec.len().saturating_sub(*limit_number as usize)..].to_vec();
    }

    // Fetch and filter BOS session templates
    //
    // We need BOS session templates to find an image created by SAT
    let mut bos_sessiontemplate_value_vec = crate::bos::template::mesa::http_client::get(
        shasta_token,
        shasta_base_url,
        shasta_root_cert,
        None,
    )
    .await
    .unwrap();

    bos::template::mesa::utils::filter(
        &mut bos_sessiontemplate_value_vec,
        hsm_group_name_vec,
        &Vec::new(),
        // None,
        None,
    )
    .await;

    // Fetch and filter CFS sessions
    //
    // We need CFS sessions to find images without a BOS session template (hopefully the CFS
    // session has not been deleted by CSCS staff, otherwise it will be technically impossible to
    // find unless we search images by HSM name and expect HSM name to be in image name...)
    let mut cfs_session_vec = crate::cfs::session::mesa::http_client::get(
        shasta_token,
        shasta_base_url,
        shasta_root_cert,
        None,
        None,
        None,
        None,
        Some(true),
    )
    .await
    .unwrap();

    crate::cfs::session::mesa::utils::filter_by_hsm(
        shasta_token,
        shasta_base_url,
        shasta_root_cert,
        &mut cfs_session_vec,
        hsm_group_name_vec,
        None,
        crate::common::jwt_ops::is_user_admin(shasta_token).unwrap(),
    )
    .await;

    // Get IMAGES in nodes boot params
    let mut image_id_cfs_configuration_from_cfs_session: Vec<(String, String, Vec<String>)> =
        crate::cfs::session::mesa::utils::get_image_id_cfs_configuration_target_for_existing_images_tuple_vec(
            &cfs_session_vec,
        );

    image_id_cfs_configuration_from_cfs_session
        .retain(|(image_id, _cfs_configuration, _hsm_groups)| !image_id.is_empty());

    /* let mut image_id_cfs_configuration_from_cfs_session_vec: Vec<(String, String, Vec<String>)> =
        crate::cfs::session::mesa::utils::get_image_id_cfs_configuration_target_for_existing_images_tuple_vec(
            &cfs_session_vec,
        );

    image_id_cfs_configuration_from_cfs_session_vec
        .retain(|(image_id, _cfs_confguration, _hsm_groups)| !image_id.is_empty()); */

    // Get IMAGES in nodes boot params. This is because CSCS staff deletes the CFS sessions and/or
    // BOS sessiontemplate breaking the history with actual state, therefore I need to go to boot
    // params to get the image id used to boot the nodes belonging to a HSM group
    let hsm_member_vec = get_member_vec_from_hsm_name_vec(
        shasta_token,
        shasta_base_url,
        shasta_root_cert,
        hsm_group_name_vec.to_vec(),
    )
    .await;

    let boot_param_vec = get_raw(
        shasta_token,
        shasta_base_url,
        shasta_root_cert,
        &hsm_member_vec,
    )
    .await
    .unwrap_or(Vec::new());

    let image_id_from_boot_params: Vec<String> = boot_param_vec
        .iter()
        .map(|boot_param| boot_param.get_boot_image())
        .collect();

    // Get Image details from IMS images API endpoint
    let mut image_detail_vec: Vec<(Image, String, String, bool)> = Vec::new();

    for image in image_vec {
        let image_id = image.id.as_ref().unwrap();

        let target_group_name_vec: Vec<String>;
        let cfs_configuration: String;
        let target_groups: String;
        let boot_image: bool;

        if let Some(tuple) = image_id_cfs_configuration_from_cfs_session
            .iter()
            .find(|tuple| tuple.0.eq(image_id))
        {
            // Image details in CFS session
            cfs_configuration = tuple.clone().1;
            target_group_name_vec = tuple.2.clone();
            target_groups = target_group_name_vec.join(", ");
        /* } else if let Some(tuple) = image_id_cfs_configuration_from_cfs_session
            .iter()
            .find(|tuple| tuple.0.eq(image_id))
        {
            // Image details in BOS session template
            cfs_configuration = tuple.clone().1;
            target_group_name_vec = tuple.2.clone();
            target_groups = target_group_name_vec.join(", "); */
        } else if let Some(boot_params) = boot_param_vec
            .iter()
            .find(|boot_params| boot_params.get_boot_image().eq(image_id))
        {
            // Image details where image is found in a node boot param related to HSM we are
            // working with
            // Boot params don't have CFS configuration information
            cfs_configuration = "Not found".to_string();
            target_groups = boot_params.hosts.clone().join(",");
        } else if hsm_group_name_vec
            .iter()
            .any(|hsm_group_name| image.name.contains(hsm_group_name))
        {
            // Image details where image name contains HSM group name available to the user.
            // Boot params don't have CFS configuration information
            // NOTE: CSCS specific
            cfs_configuration = "Not found".to_string();

            target_groups = "Not found".to_string();
        } else {
            continue;
        }

        // NOTE: 'boot_image' needs to be processed outside the 'if' statement. Otherwise we may
        // miss images used to boot nodes filtered by a different branch in the 'if' statement
        boot_image = if image_id_from_boot_params.contains(image_id) {
            true
        } else {
            false
        };

        image_detail_vec.push((
            image.clone(),
            cfs_configuration.to_string(),
            target_groups.clone(),
            boot_image,
        ));
    }

    image_detail_vec
}

/// Returns a tuple like (Image struct, cfs configuration, target groups) with the cfs
/// configuration related to that image struct and the target groups booting that image
/// This list is filtered by the HSM groups the user has access to
/// Exception are images containing 'generic' in their names since those could be used by anyone
pub async fn get_image_available_vec(
    shasta_token: &str,
    shasta_base_url: &str,
    shasta_root_cert: &[u8],
    hsm_name_available_vec: &[String],
    limit_number_opt: Option<&u8>,
) -> Vec<Image> {
    let mut image_vec: Vec<Image> =
        super::mesa::http_client::get(shasta_token, shasta_base_url, shasta_root_cert, None)
            .await
            .unwrap();

    ims::image::mesa::utils::filter(&mut image_vec).await;

    // We need BOS session templates to find an image created by SAT
    let mut bos_sessiontemplate_vec = bos::template::mesa::http_client::get(
        shasta_token,
        shasta_base_url,
        shasta_root_cert,
        None,
    )
    .await
    .unwrap();

    // Filter BOS sessiontemplates to the ones the user has access to
    bos::template::mesa::utils::filter(
        &mut bos_sessiontemplate_vec,
        hsm_name_available_vec,
        &Vec::new(),
        None,
    )
    .await;

    // We need CFS sessions to find images without a BOS session template
    let mut cfs_session_vec = crate::cfs::session::mesa::http_client::get(
        shasta_token,
        shasta_base_url,
        shasta_root_cert,
        None,
        None,
        None,
        None,
        Some(true),
    )
    .await
    .unwrap();

    // Filter CFS sessions to the ones the user has access to
    crate::cfs::session::mesa::utils::filter_by_hsm(
        shasta_token,
        shasta_base_url,
        shasta_root_cert,
        &mut cfs_session_vec,
        hsm_name_available_vec,
        None,
        true,
    )
    .await;

    let mut image_id_cfs_configuration_from_bos_sessiontemplate: Vec<(
        String,
        String,
        Vec<String>,
    )> = crate::bos::template::mesa::utils::get_image_id_cfs_configuration_target_tuple_vec(
        bos_sessiontemplate_vec,
    );

    image_id_cfs_configuration_from_bos_sessiontemplate
        .retain(|(image_id, _cfs_configuration, _hsm_groups)| !image_id.is_empty());

    let mut image_id_cfs_configuration_from_cfs_session_vec: Vec<(String, String, Vec<String>)> =
        crate::cfs::session::mesa::utils::get_image_id_cfs_configuration_target_for_existing_images_tuple_vec(
            &cfs_session_vec,
        );

    image_id_cfs_configuration_from_cfs_session_vec
        .retain(|(image_id, _cfs_confguration, _hsm_groups)| !image_id.is_empty());

    let mut image_available_vec: Vec<Image> = Vec::new();

    for image in &image_vec {
        let image_id = image.id.as_ref().unwrap();

        if image_id_cfs_configuration_from_bos_sessiontemplate
            .iter()
            .any(|tuple| tuple.0.eq(image_id))
        {
            // If image is related to a BOS sessiontemplate related to a HSM group the user has
            // access to, then, we include this image to the list of images available to the user
            image_available_vec.push(image.clone());
        } else if image_id_cfs_configuration_from_cfs_session_vec
            .iter()
            .any(|tuple| tuple.0.eq(image_id))
        {
            // If image was created using a CFS session with HSM groups related to the user, then
            // we include this image to the list of images available to the user
            // FIXME: this needs to go away if we extend groups in CFS sessions to technology
            // rather than clusters
            image_available_vec.push(image.clone());
        } else if hsm_name_available_vec
            .iter()
            .any(|hsm_group_name| image.name.contains(hsm_group_name))
        {
            // If image name contains HSM group the user is working on, then, we include the image
            // to the list of images available to the user
            // FIXME: this should not be allowed... but CSCS staff deletes the CFS sessions so we
            // are extending the rules that defines if a user has access to an image
            image_available_vec.push(image.clone());
        } else if image.name.to_lowercase().contains("generic") {
            // If image is generic (meaning image name contains the word "generic"), then, the image
            // will be available to everyone, therefore it should be included to the list of images
            // available to the user
            // FIXME: This is should not be allowed since it is too vague, we concept of generic is
            // not limited to anything, a tenant may create an image which name contains "generic"
            // but they don't want to share it with other tenants meaning the scope of generic here
            // does not moves across tenants boundaries
            image_available_vec.push(image.clone())
        } else {
            continue;
        }

        // let target_groups = target_group_name_vec.join(", ");
    }

    if let Some(limit_number) = limit_number_opt {
        // Limiting the number of results to return to client
        image_available_vec = image_available_vec[image_available_vec
            .len()
            .saturating_sub(*limit_number as usize)..]
            .to_vec();
    }

    image_available_vec
}

/// Register a new image in IMS --> https://github.com/Cray-HPE/docs-csm/blob/release/1.5/api/ims.md#post_v2_image
pub async fn register_new_image(
    shasta_token: &str,
    shasta_base_url: &str,
    shasta_root_cert: &[u8],
    ims_image: &Image,
) -> Result<Value, reqwest::Error> {
    let client;

    let client_builder = reqwest::Client::builder()
        .add_root_certificate(reqwest::Certificate::from_pem(shasta_root_cert)?);

    // Build client
    if std::env::var("SOCKS5").is_ok() {
        // socks5 proxy
        log::debug!("SOCKS5 enabled");
        let socks5proxy = reqwest::Proxy::all(std::env::var("SOCKS5").unwrap())?;

        // rest client to authenticate
        client = client_builder.proxy(socks5proxy).build()?;
    } else {
        client = client_builder.build()?;
    }

    let api_url = shasta_base_url.to_owned() + "/ims/v3/images";

    client
        .post(api_url)
        .header("Authorization", format!("Bearer {}", shasta_token))
        .json(&ims_image)
        .send()
        .await?
        .error_for_status()?
        .json()
        .await
}

/// Returns the first user public key in IMS is can find
pub async fn get_single(
    shasta_token: &str,
    shasta_base_url: &str,
    shasta_root_cert: &[u8],
    username_opt: &str,
) -> Option<Value> {
    if let Ok(public_key_value_list) = get(
        shasta_token,
        shasta_base_url,
        shasta_root_cert,
        Some(username_opt),
    )
    .await
    {
        if public_key_value_list.len() == 1 {
            return public_key_value_list.first().cloned();
        };
    }

    None
}