dsh_api/
bucket.rs

1//! # Additional methods to manage buckets
2//!
3//! Module that contains methods and functions to manage buckets.
4//!
5//! # Generated methods
6//!
7//! [`DshApiClient`] methods that are generated from the `openapi` specification.
8//!
9//! * [`delete_bucket_access_configuration(id, name)`](DshApiClient::delete_bucket_access_configuration)
10//! * [`delete_bucket_configuration(id)`](DshApiClient::delete_bucket_configuration)
11//! * [`delete_bucket_watch_configuration(id)`](DshApiClient::delete_bucket_watch_configuration)
12//! * [`get_bucket(id) -> BucketStatus`](DshApiClient::get_bucket)
13//! * [`get_bucket_access(id, name) -> BucketAccessStatus`](DshApiClient::get_bucket_access)
14//! * [`get_bucket_access_actual(id, name) -> BucketAccess`](DshApiClient::get_bucket_access_actual)
15//! * [`get_bucket_access_configuration(id, name) -> BucketAccessConfiguration`](DshApiClient::get_bucket_access_configuration)
16//! * [`get_bucket_access_ids(id) -> [id]`](DshApiClient::get_bucket_access_ids)
17//! * [`get_bucket_access_status(id, name) -> AllocationStatus`](DshApiClient::get_bucket_access_status)
18//! * [`get_bucket_actual(id) -> Bucket`](DshApiClient::get_bucket_actual)
19//! * [`get_bucket_configuration(id) -> Bucket`](DshApiClient::get_bucket_configuration)
20//! * [`get_bucket_fromthirdparty_ids() -> [id]`](DshApiClient::get_bucket_fromthirdparty_ids)
21//! * [`get_bucket_ids() -> [id]`](DshApiClient::get_bucket_ids)
22//! * [`get_bucket_status(id) -> AllocationStatus`](DshApiClient::get_bucket_status)
23//! * [`get_bucket_watch(id) -> BucketWatchStatus`](DshApiClient::get_bucket_watch)
24//! * [`get_bucket_watch_actual(id) -> BucketWatch`](DshApiClient::get_bucket_watch_actual)
25//! * [`get_bucket_watch_configuration(id) -> BucketWatch`](DshApiClient::get_bucket_watch_configuration)
26//! * [`get_bucket_watch_status(id) -> AllocationStatus`](DshApiClient::get_bucket_watch_status)
27//! * [`get_bucketaccess_ids() -> [id]`](DshApiClient::get_bucketaccess_ids)
28//! * [`get_bucketwatch_ids() -> [id]`](DshApiClient::get_bucketwatch_ids)
29//! * [`get_thirdpartybucket(id) -> ThirdPartyBucketConcessionStatus`](DshApiClient::get_thirdpartybucket)
30//! * [`get_thirdpartybucket_actual(id) -> ThirdPartyBucketConcession`](DshApiClient::get_thirdpartybucket_actual)
31//! * [`get_thirdpartybucket_configuration(id) -> ThirdPartyBucketConcessionConfiguration`](DshApiClient::get_thirdpartybucket_configuration)
32//! * [`get_thirdpartybucket_ids() -> [id]`](DshApiClient::get_thirdpartybucket_ids)
33//! * [`get_thirdpartybucket_status(id) -> AllocationStatus`](DshApiClient::get_thirdpartybucket_status)
34//! * [`post_thirdpartybucket(body)`](DshApiClient::post_thirdpartybucket)
35//! * [`put_bucket_access_configuration(id, name, body)`](DshApiClient::put_bucket_access_configuration)
36//! * [`put_bucket_configuration(id, body)`](DshApiClient::put_bucket_configuration)
37//! * [`put_bucket_watch_configuration(id)`](DshApiClient::put_bucket_watch_configuration)
38//!
39//! # Derived methods
40//!
41//! [`DshApiClient`] methods that add extra capabilities but do not directly call the
42//! DSH resource management API. These derived methods depend on the API methods for this.
43//!
44//! * [`bucket_ids_with_dependants() -> [(bucket id, [dependant])]`](DshApiClient::bucket_ids_with_dependants)
45//! * [`bucket_map() -> map(bucket id -> bucket)`](DshApiClient::bucket_map)
46//! * [`bucket_name(bucket id) -> bucket name`](DshApiClient::bucket_name)
47//! * [`bucket_with_dependants(bucket id) -> (bucket id, [dependant])]`](DshApiClient::bucket_with_dependants)
48//! * [`buckets() -> [(bucket id, bucket)]`](DshApiClient::buckets)
49//! * [`buckets_with_dependant_applications() -> [(bucket_id, bucket, [dependant application])]`](DshApiClient::buckets_with_dependant_applications)
50//! * [`buckets_with_dependant_apps() -> [(bucket id, bucket, [dependant app])]`](DshApiClient::buckets_with_dependant_apps)
51//! * [`buckets_with_dependants() -> [(bucket_id, [dependant])]`](DshApiClient::bucket_with_dependants)
52
53use crate::app::{app_resources, apps_that_use_resource};
54use crate::application_types::{ApplicationValues, EnvVarInjection};
55use crate::dsh_api_client::DshApiClient;
56use crate::parse::parse_function1;
57use crate::platform::CloudProvider;
58use crate::types::{AppCatalogApp, AppCatalogAppResourcesValue, Application, Bucket, BucketStatus};
59#[allow(unused_imports)]
60use crate::DshApiError;
61use crate::{Dependant, DependantApp, DependantApplication, DshApiResult};
62use futures::future::try_join_all;
63use futures::try_join;
64use itertools::Itertools;
65use serde::{Deserialize, Serialize};
66use std::collections::HashMap;
67use std::fmt::{Display, Formatter};
68
69/// Secret name for object store access key id
70pub const OBJECT_STORE_ACCESS_KEY_ID: &str = "system/objectstore/access_key_id";
71/// Secret name for object store secret access key
72pub const OBJECT_STORE_SECRET_ACCESS_KEY: &str = "system/objectstore/secret_access_key";
73
74/// # Describes an injection of a resource in an application
75#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
76pub enum BucketInjection {
77  /// Environment variable injection, where the value is the name of the environment variable.
78  #[serde(rename = "env")]
79  EnvVar(String),
80  /// Variable function, where the value is the name of the environment variable.
81  #[serde(rename = "variable")]
82  Variable(String),
83}
84
85impl Display for BucketInjection {
86  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
87    match self {
88      BucketInjection::EnvVar(env_var) => write!(f, "{}", env_var),
89      BucketInjection::Variable(variable) => write!(f, "{{ bucket_name('{}') }}", variable),
90    }
91  }
92}
93
94/// # Additional methods to manage buckets
95///
96/// Module that contains methods and functions to manage buckets.
97/// * Derived methods - DshApiClient methods that add extra capabilities
98///   but depend on the API methods.
99///
100/// # Derived methods
101///
102/// [`DshApiClient`] methods that add extra capabilities but do not directly call the
103/// DSH resource management API. These derived methods depend on the API methods for this.
104///
105/// * [`bucket_ids_with_dependants() -> [(bucket id, [dependant])]`](DshApiClient::bucket_ids_with_dependants)
106/// * [`bucket_map() -> map(bucket id -> bucket)`](DshApiClient::bucket_map)
107/// * [`bucket_name(bucket id) -> bucket name`](DshApiClient::bucket_name)
108/// * [`bucket_with_dependants(bucket id) -> (bucket id, [dependant])]`](DshApiClient::bucket_with_dependants)
109/// * [`buckets() -> [(bucket id, bucket)]`](DshApiClient::buckets)
110/// * [`buckets_with_dependant_applications() -> [(bucket_id, bucket, [dependant application])]`](DshApiClient::buckets_with_dependant_applications)
111/// * [`buckets_with_dependant_apps() -> [(bucket id, bucket, [dependant app])]`](DshApiClient::buckets_with_dependant_apps)
112/// * [`buckets_with_dependants() -> [(bucket_id, [dependant])]`](DshApiClient::bucket_with_dependants)
113impl DshApiClient {
114  /// # Returns all bucket identifiers with dependant applications and apps
115  ///
116  /// Returns a sorted list of all bucket ids together with the applications and apps that use them.
117  pub async fn bucket_ids_with_dependants(&self) -> DshApiResult<Vec<(String, Vec<Dependant<BucketInjection>>)>> {
118    let (bucket_ids, applications, apps, access_key_id) = try_join!(
119      self.get_bucket_ids(),
120      self.get_application_configuration_map(),
121      self.get_appcatalogapp_configuration_map(),
122      self.object_store_access_key_id_if_required()
123    )?;
124    let mut buckets = Vec::<(String, Vec<Dependant<BucketInjection>>)>::new();
125    for bucket_id in &bucket_ids {
126      let mut dependants: Vec<Dependant<BucketInjection>> = vec![];
127      let bucket_name = self.platform().bucket_name(self.tenant_name(), bucket_id, access_key_id.as_deref()).ok();
128      for application_injections in bucket_injections_from_applications(bucket_id, bucket_name.as_deref(), &applications) {
129        dependants.push(Dependant::application(
130          application_injections.id.to_string(),
131          application_injections.application.instances,
132          application_injections.values,
133        ));
134      }
135      for (app_id, _, resource_ids) in apps_that_use_resource(bucket_id.as_str(), &apps, &bucket_resources_from_app) {
136        dependants.push(Dependant::app(
137          app_id.to_string(),
138          resource_ids.iter().map(|resource_id| resource_id.to_string()).collect_vec(),
139        ));
140      }
141      buckets.push((bucket_id.to_string(), dependants));
142    }
143    Ok(buckets)
144  }
145
146  /// Return a map of all bucket ids and buckets
147  ///
148  /// # Returns
149  /// * `HashMap<String, `[`BucketStatus`]`>` - `HashMap` that maps all bucket ids to buckets.
150  /// * `Err<`[`DshApiError`]`>` - when the request could not be processed by the DSH
151  pub async fn bucket_map(&self) -> DshApiResult<HashMap<String, BucketStatus>> {
152    Ok(self.buckets().await?.into_iter().collect())
153  }
154
155  /// Create a bucket name from a bucket id
156  ///
157  /// # Returns
158  /// * `Ok<String>` - When the key was successfully craeted.
159  /// * `Err<`[`DshApiError::NotFound`]`>` - When on Azure the bucket secret is not set.
160  /// * `Err<`[`DshApiError`]`>` - When the request could not be processed.
161  pub async fn bucket_name(&self, bucket_id: &str) -> DshApiResult<String> {
162    match self.platform().cloud_provider() {
163      CloudProvider::Azure => match self.object_store_access_key_id_if_required().await {
164        Ok(Some(access_key_id)) => Ok(self.platform().bucket_name(self.tenant_name(), bucket_id, Some(access_key_id))?),
165        _ => Err(DshApiError::NotFound(Some(format!(
166          "bucket name for azure requires the object store access key '{}'",
167          OBJECT_STORE_ACCESS_KEY_ID
168        )))),
169      },
170      CloudProvider::AWS => Ok(self.platform().bucket_name(self.tenant_name(), bucket_id, None::<String>)?.to_string()),
171    }
172  }
173
174  /// # Returns all buckets with dependant applications and apps
175  ///
176  /// # Parameters
177  /// * `bucket_id` - Identifier of the requested bucket.
178  ///
179  /// Returns a bucket with the applications and apps that use it.
180  pub async fn bucket_with_dependants(&self, bucket_id: &str) -> DshApiResult<(BucketStatus, Vec<Dependant<BucketInjection>>)> {
181    let (bucket_status, application_configuration_map, appcatalogapp_configuration_map) = try_join!(
182      self.get_bucket(bucket_id),
183      self.get_application_configuration_map(),
184      self.get_appcatalogapp_configuration_map()
185    )?;
186    let mut dependants: Vec<Dependant<BucketInjection>> = vec![];
187    let bucket_name = self.bucket_name(bucket_id).await.ok();
188    for application in bucket_injections_from_applications(bucket_id, bucket_name.as_deref(), &application_configuration_map) {
189      dependants.push(Dependant::application(
190        application.id.to_string(),
191        application.application.instances,
192        application.values,
193      ));
194    }
195    for (app_id, _, resource_ids) in apps_that_use_resource(bucket_id, &appcatalogapp_configuration_map, &bucket_resources_from_app) {
196      dependants.push(Dependant::app(
197        app_id.to_string(),
198        resource_ids.iter().map(|resource_id| resource_id.to_string()).collect_vec(),
199      ));
200    }
201    Ok((bucket_status, dependants))
202  }
203
204  /// Return a list of all buckets
205  ///
206  /// # Returns
207  /// * `Vec<(String, `[`BucketStatus`]`)>` - list of tuples that describe the buckets,
208  ///   ordered by bucket id. Each tuple consist of
209  ///   * `String` - id of the bucket,
210  ///   * `BucketStatus` - the bucket data.
211  /// * `Err<`[`DshApiError`]`>` - when the request could not be processed by the DSH
212  pub async fn buckets(&self) -> DshApiResult<Vec<(String, BucketStatus)>> {
213    let bucket_ids: Vec<String> = self.get_bucket_ids().await?;
214    let bucket_statuses = try_join_all(bucket_ids.iter().map(|bucket_id| self.get_bucket(bucket_id.as_str()))).await?;
215    Ok(bucket_ids.into_iter().zip(bucket_statuses).collect_vec())
216  }
217
218  /// # Returns all buckets with dependant applications
219  ///
220  /// Returns a sorted list of all buckets ids, bucket statuses and applications that use them.
221  pub async fn buckets_with_dependant_applications(&self) -> DshApiResult<Vec<(String, BucketStatus, Vec<DependantApplication<BucketInjection>>)>> {
222    let (buckets, application_configuration_map) = try_join!(self.buckets(), self.get_application_configuration_map())?;
223    let mut buckets_with_dependant_applications = Vec::<(String, BucketStatus, Vec<DependantApplication<BucketInjection>>)>::new();
224    for (ref bucket_id, bucket_status) in buckets {
225      let mut dependant_applications: Vec<DependantApplication<BucketInjection>> = vec![];
226      let bucket_name = self.bucket_name(bucket_id).await.ok();
227      for application in bucket_injections_from_applications(bucket_id.as_str(), bucket_name.as_deref(), &application_configuration_map) {
228        dependant_applications.push(DependantApplication::new(
229          application.id.to_string(),
230          application.application.instances,
231          application.values,
232        ));
233      }
234      buckets_with_dependant_applications.push((bucket_id.to_string(), bucket_status, dependant_applications));
235    }
236    Ok(buckets_with_dependant_applications)
237  }
238
239  /// # Returns all buckets with dependant apps
240  ///
241  /// Returns a sorted list of all buckets ids, buckets and apps that use them.
242  pub async fn buckets_with_dependant_apps(&self) -> DshApiResult<Vec<(String, BucketStatus, Vec<DependantApp>)>> {
243    let (buckets, appcatalogapp_configuration_map) = try_join!(self.buckets(), self.get_appcatalogapp_configuration_map())?;
244    let mut buckets_with_dependant_apps = Vec::<(String, BucketStatus, Vec<DependantApp>)>::new();
245    for (bucket_id, bucket_status) in buckets {
246      let mut dependant_apps: Vec<DependantApp> = vec![];
247      for (app_id, _, resource_ids) in apps_that_use_resource(bucket_id.as_str(), &appcatalogapp_configuration_map, &bucket_resources_from_app) {
248        dependant_apps.push(DependantApp::new(
249          app_id.to_string(),
250          resource_ids.iter().map(|resource_id| resource_id.to_string()).collect_vec(),
251        ));
252      }
253      buckets_with_dependant_apps.push((bucket_id, bucket_status, dependant_apps));
254    }
255    Ok(buckets_with_dependant_apps)
256  }
257
258  /// # Returns all buckets with dependant applications and apps
259  ///
260  /// Returns a sorted list of all buckets ids, buckets and applications and apps that use them.
261  ///
262  /// # Returns
263  /// Tuples describing the buckets. Each tuple contains:
264  /// * bucket identifier,
265  /// * bucket status,
266  /// * list of dependants.
267  pub async fn buckets_with_dependants(&self) -> DshApiResult<Vec<(String, BucketStatus, Vec<Dependant<BucketInjection>>)>> {
268    let (buckets, application_configuration_map, apps, access_key_id) = try_join!(
269      self.buckets(),
270      self.get_application_configuration_map(),
271      self.get_appcatalogapp_configuration_map(),
272      self.object_store_access_key_id_if_required()
273    )?;
274    let mut buckets_with_dependants = Vec::<(String, BucketStatus, Vec<Dependant<BucketInjection>>)>::new();
275    for (ref bucket_id, bucket_status) in buckets {
276      let mut dependants: Vec<Dependant<BucketInjection>> = vec![];
277      let bucket_name = self.platform().bucket_name(self.tenant_name(), bucket_id, access_key_id.as_deref()).ok();
278      for application in bucket_injections_from_applications(bucket_id.as_str(), bucket_name.as_deref(), &application_configuration_map) {
279        dependants.push(Dependant::application(
280          application.id.to_string(),
281          application.application.instances,
282          application.values,
283        ));
284      }
285      for (app_id, _, resource_ids) in apps_that_use_resource(bucket_id.as_str(), &apps, &bucket_resources_from_app) {
286        dependants.push(Dependant::app(
287          app_id.to_string(),
288          resource_ids.iter().map(|resource_id| resource_id.to_string()).collect_vec(),
289        ));
290      }
291      buckets_with_dependants.push((bucket_id.to_string(), bucket_status, dependants));
292    }
293    Ok(buckets_with_dependants)
294  }
295
296  /// # Returns the object store secrets
297  ///
298  /// Returns the object store `access_key_id` and `secret_access_key`.
299  pub async fn bucket_secrets(&self) -> DshApiResult<(String, String)> {
300    Ok(try_join!(
301      self.get_secret(OBJECT_STORE_ACCESS_KEY_ID),
302      self.get_secret(OBJECT_STORE_SECRET_ACCESS_KEY)
303    )?)
304  }
305
306  /// Get object store access key
307  async fn object_store_access_key_id_if_required(&self) -> DshApiResult<Option<String>> {
308    match self.platform().cloud_provider() {
309      CloudProvider::AWS => Ok(None),
310      CloudProvider::Azure => self.get_secret(OBJECT_STORE_ACCESS_KEY_ID).await.map(Some),
311    }
312  }
313}
314
315/// # Get applications environment variables referencing bucket
316///
317/// Get all environment variables referencing bucket `bucket_id` or (if available) `bucket_name`
318/// from multiple `Application`s. Applications are only included if they reference bucket
319/// `bucket_id` or `bucket_name` at least once.
320///
321/// # Parameters
322/// * `bucket_id` - Identifies the bucket to look for.
323/// * `bucket_name` - Optional full bucket name for the platform.
324/// * `applications` - Hashmap containing id/application pairs.
325///
326/// # Returns
327/// List of tuples containing:
328/// * application id,
329/// * application reference,
330/// * sorted list of environment variable keys that reference bucket `bucket_id`.
331///
332/// The list is sorted by application id.
333pub fn bucket_injections_from_applications<'a>(
334  bucket_id: &str,
335  bucket_name: Option<&str>,
336  applications: &'a HashMap<String, Application>,
337) -> Vec<ApplicationValues<'a, BucketInjection>> {
338  let mut application_injections: Vec<ApplicationValues<BucketInjection>> = vec![];
339  for (application_id, application) in applications {
340    let environment_variable_keys: Vec<BucketInjection> = bucket_injections_from_application(bucket_id, bucket_name, application);
341    if !environment_variable_keys.is_empty() {
342      application_injections.push(ApplicationValues::new(application_id, application, environment_variable_keys));
343    }
344  }
345  application_injections.sort();
346  application_injections
347}
348
349/// # Get application environment variables referencing bucket
350///
351/// Get all environment variables referencing bucket `bucket_id` from an `Application`.
352/// When the application does not reference the bucket, an empty list will be returned.
353///
354/// # Parameters
355/// * `bucket_id` - id of the bucket to look for
356/// * `application` - reference to the `Application`
357///
358/// # Returns
359/// `Vec<EnvVarKey>` - list of all environment variables referencing bucket `bucket_id`
360///
361/// The list is sorted by environment variable key.
362pub fn bucket_injections_from_application(bucket_id: &str, bucket_name: Option<&str>, application: &Application) -> Vec<BucketInjection> {
363  let mut env_var_keys = application
364    .env
365    .iter()
366    .filter_map(|(env_key, env_value)| match parse_function1(env_value, "bucket_name") {
367      Ok(bucket_string) => {
368        if bucket_id == bucket_string {
369          Some(BucketInjection::Variable(env_key.to_string()))
370        } else {
371          None
372        }
373      }
374      Err(_) => match bucket_name {
375        Some(name) => {
376          if env_value.contains(name) {
377            Some(BucketInjection::EnvVar(env_key.to_string()))
378          } else {
379            None
380          }
381        }
382        None => None,
383      },
384    })
385    .collect_vec();
386  env_var_keys.sort();
387  env_var_keys
388}
389
390/// Get bucket resources from `AppCatalogApp`
391///
392/// # Parameters
393/// * `app` - app to get the bucket resources from
394///
395/// # Returns
396/// Either `None` when the `app` does not have any bucket resources,
397/// or a `Some` that contains tuples describing the bucket resources:
398/// * resource id
399/// * reference to the `Bucket`
400pub fn bucket_resources_from_app(app: &AppCatalogApp) -> Vec<(&str, &Bucket)> {
401  app_resources(app, &|resource_value| match resource_value {
402    AppCatalogAppResourcesValue::Bucket(bucket) => Some(bucket),
403    _ => None,
404  })
405}
406
407/// # Get application environment variables referencing buckets
408///
409/// Get all environment variables from an `Application` that reference a bucket.
410/// When the application does not reference any buckets, an empty list will be returned.
411///
412/// # Parameters
413/// * `application` - reference to the `Application`
414///
415/// # Returns
416/// `Vec<EnvInjection>` - list of tuples containing:
417/// * bucket id
418/// * list of environment variables referencing the bucket
419///
420/// The list is sorted by bucket id.
421pub fn buckets_from_application(application: &Application) -> Vec<EnvVarInjection> {
422  let mut buckets = HashMap::<&str, Vec<&str>>::new();
423  for (env_key, env_value) in &application.env {
424    if let Ok(bucket_id) = parse_function1(env_value, "bucket_name") {
425      buckets.entry(bucket_id).or_default().push(env_key);
426    }
427  }
428  let mut sorted_buckets: Vec<EnvVarInjection> = buckets.into_iter().map(EnvVarInjection::from).collect_vec();
429  sorted_buckets.sort();
430  sorted_buckets
431}
432
433/// # Get applications environment variables referencing buckets
434///
435/// Get all environment variables referencing buckets from all `Application`s.
436/// Applications are only included if they reference at least one bucket.
437///
438/// # Parameters
439/// * `applications` - hashmap containing id/application pairs
440///
441/// # Returns
442/// `Vec<ApplicationTuple<EnvInjection>>` - list of tuples containing:
443/// * application id
444/// * application
445/// * list of pairs of bucket ids and environment variables referencing those buckets,
446///   sorted by bucket id
447///
448/// The list is sorted by application id.
449pub fn buckets_from_applications(applications: &HashMap<String, Application>) -> Vec<ApplicationValues<EnvVarInjection>> {
450  let mut application_tuples: Vec<ApplicationValues<EnvVarInjection>> = vec![];
451  for (application_id, application) in applications {
452    let injections: Vec<EnvVarInjection> = buckets_from_application(application);
453    if !injections.is_empty() {
454      application_tuples.push(ApplicationValues::new(application_id, application, injections));
455    }
456  }
457  application_tuples.sort();
458  application_tuples
459}
460
461// /// # Parse bucket string
462// ///
463// /// # Example
464// ///
465// /// ```
466// /// # use std::str::FromStr;
467// /// use dsh_api::bucket::parse_bucket_string;
468// /// assert_eq!(parse_bucket_string("{ bucket_name('my_bucket_name') }"), Ok("my_bucket_name"));
469// /// ```
470// ///
471// /// # Parameters
472// /// * `bucket_string` - the bucket string to be parsed
473// ///
474// /// # Returns
475// /// When the provided string is valid, the method returns the bucket name
476// pub fn parse_bucket_string(bucket_string: &str) -> Result<&str, String> {
477//   parse_function1(bucket_string, "bucket_name")
478// }
479//
480// #[test]
481// fn test_parse_bucket_string() {
482//   assert_eq!(parse_bucket_string("{ bucket_name('my_bucket_name') }"), Ok("my_bucket_name"));
483// }