use crate::app::{app_resources, apps_that_use_resource};
use crate::application_types::{ApplicationValues, EnvVarInjection};
use crate::dsh_api_client::DshApiClient;
use crate::error::DshApiResult;
use crate::parse::parse_function1;
use crate::platform::CloudProvider;
use crate::types::{AppCatalogApp, AppCatalogAppResourcesValue, Application, Bucket, BucketStatus};
#[allow(unused_imports)]
use crate::DshApiError;
use crate::{Dependant, DependantApp, DependantApplication};
use futures::future::try_join_all;
use futures::try_join;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::{Display, Formatter};
pub const OBJECT_STORE_ACCESS_KEY_ID: &str = "system/objectstore/access_key_id";
pub const OBJECT_STORE_SECRET_ACCESS_KEY: &str = "system/objectstore/secret_access_key";
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub enum BucketInjection {
#[serde(rename = "env")]
EnvVar { env_var_name: String },
#[serde(rename = "variable")]
Variable { env_var_name: String },
}
impl BucketInjection {
pub(crate) fn env_var<T>(env_var: T) -> Self
where
T: Into<String>,
{
Self::EnvVar { env_var_name: env_var.into() }
}
pub(crate) fn variable<T>(env_var: T) -> Self
where
T: Into<String>,
{
Self::Variable { env_var_name: env_var.into() }
}
}
impl Display for BucketInjection {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
BucketInjection::EnvVar { env_var_name } => write!(f, "{}", env_var_name),
BucketInjection::Variable { env_var_name } => write!(f, "{{ bucket_name('{}') }}", env_var_name),
}
}
}
impl DshApiClient {
pub async fn bucket_ids_with_dependants(&self) -> DshApiResult<Vec<(String, Vec<Dependant<BucketInjection>>)>> {
let (bucket_ids, applications, apps, access_key_id) = try_join!(
self.get_bucket_ids(),
self.get_application_configuration_map(),
self.get_appcatalogapp_configuration_map(),
self.object_store_access_key_id_if_required()
)?;
let mut buckets = Vec::<(String, Vec<Dependant<BucketInjection>>)>::new();
for bucket_id in &bucket_ids {
let mut dependants: Vec<Dependant<BucketInjection>> = vec![];
let bucket_name = self.platform().bucket_name(self.tenant_name(), bucket_id, access_key_id.as_deref()).ok();
for application_injections in bucket_injections_from_applications(bucket_id, bucket_name.as_deref(), &applications) {
dependants.push(Dependant::service(
application_injections.id,
application_injections.application.instances,
application_injections.values,
));
}
for (app_id, _, resource_ids) in apps_that_use_resource(bucket_id.as_str(), &apps, &bucket_resources_from_app) {
dependants.push(Dependant::app(
app_id.to_string(),
resource_ids.iter().map(|resource_id| resource_id.to_string()).collect_vec(),
));
}
buckets.push((bucket_id.to_string(), dependants));
}
Ok(buckets)
}
pub async fn bucket_map(&self) -> DshApiResult<HashMap<String, BucketStatus>> {
Ok(self.buckets().await?.into_iter().collect())
}
pub async fn bucket_name(&self, bucket_id: &str) -> DshApiResult<String> {
match self.platform().cloud_provider() {
CloudProvider::Azure => match self.object_store_access_key_id_if_required().await {
Ok(Some(access_key_id)) => Ok(self.platform().bucket_name(self.tenant_name(), bucket_id, Some(access_key_id))?),
_ => Err(DshApiError::NotFound {
message: Some(format!(
"bucket name for azure requires the object store access key '{}'",
OBJECT_STORE_ACCESS_KEY_ID
)),
}),
},
CloudProvider::AWS => Ok(self.platform().bucket_name(self.tenant_name(), bucket_id, None::<String>)?.to_string()),
}
}
pub async fn bucket_with_dependants(&self, bucket_id: &str) -> DshApiResult<(BucketStatus, Vec<Dependant<BucketInjection>>)> {
let (bucket_status, application_configuration_map, appcatalogapp_configuration_map) = try_join!(
self.get_bucket(bucket_id),
self.get_application_configuration_map(),
self.get_appcatalogapp_configuration_map()
)?;
let mut dependants: Vec<Dependant<BucketInjection>> = vec![];
let bucket_name = self.bucket_name(bucket_id).await.ok();
for application in bucket_injections_from_applications(bucket_id, bucket_name.as_deref(), &application_configuration_map) {
dependants.push(Dependant::service(application.id, application.application.instances, application.values));
}
for (app_id, _, resource_ids) in apps_that_use_resource(bucket_id, &appcatalogapp_configuration_map, &bucket_resources_from_app) {
dependants.push(Dependant::app(
app_id.to_string(),
resource_ids.iter().map(|resource_id| resource_id.to_string()).collect_vec(),
));
}
Ok((bucket_status, dependants))
}
pub async fn buckets(&self) -> DshApiResult<Vec<(String, BucketStatus)>> {
let bucket_ids: Vec<String> = self.get_bucket_ids().await?;
let bucket_statuses = try_join_all(bucket_ids.iter().map(|bucket_id| self.get_bucket(bucket_id.as_str()))).await?;
Ok(bucket_ids.into_iter().zip(bucket_statuses).collect_vec())
}
pub async fn buckets_with_dependant_applications(&self) -> DshApiResult<Vec<(String, BucketStatus, Vec<DependantApplication<BucketInjection>>)>> {
let (buckets, application_configuration_map) = try_join!(self.buckets(), self.get_application_configuration_map())?;
let mut buckets_with_dependant_applications = Vec::<(String, BucketStatus, Vec<DependantApplication<BucketInjection>>)>::new();
for (ref bucket_id, bucket_status) in buckets {
let mut dependant_applications: Vec<DependantApplication<BucketInjection>> = vec![];
let bucket_name = self.bucket_name(bucket_id).await.ok();
for application in bucket_injections_from_applications(bucket_id.as_str(), bucket_name.as_deref(), &application_configuration_map) {
dependant_applications.push(DependantApplication::new(
application.id.to_string(),
application.application.instances,
application.values,
));
}
buckets_with_dependant_applications.push((bucket_id.to_string(), bucket_status, dependant_applications));
}
Ok(buckets_with_dependant_applications)
}
pub async fn buckets_with_dependant_apps(&self) -> DshApiResult<Vec<(String, BucketStatus, Vec<DependantApp>)>> {
let (buckets, appcatalogapp_configuration_map) = try_join!(self.buckets(), self.get_appcatalogapp_configuration_map())?;
let mut buckets_with_dependant_apps = Vec::<(String, BucketStatus, Vec<DependantApp>)>::new();
for (bucket_id, bucket_status) in buckets {
let mut dependant_apps: Vec<DependantApp> = vec![];
for (app_id, _, resource_ids) in apps_that_use_resource(bucket_id.as_str(), &appcatalogapp_configuration_map, &bucket_resources_from_app) {
dependant_apps.push(DependantApp::new(
app_id.to_string(),
resource_ids.iter().map(|resource_id| resource_id.to_string()).collect_vec(),
));
}
buckets_with_dependant_apps.push((bucket_id, bucket_status, dependant_apps));
}
Ok(buckets_with_dependant_apps)
}
pub async fn buckets_with_dependants(&self) -> DshApiResult<Vec<(String, BucketStatus, Vec<Dependant<BucketInjection>>)>> {
let (buckets, application_configuration_map, apps, access_key_id) = try_join!(
self.buckets(),
self.get_application_configuration_map(),
self.get_appcatalogapp_configuration_map(),
self.object_store_access_key_id_if_required()
)?;
let mut buckets_with_dependants = Vec::<(String, BucketStatus, Vec<Dependant<BucketInjection>>)>::new();
for (ref bucket_id, bucket_status) in buckets {
let mut dependants: Vec<Dependant<BucketInjection>> = vec![];
let bucket_name = self.platform().bucket_name(self.tenant_name(), bucket_id, access_key_id.as_deref()).ok();
for application in bucket_injections_from_applications(bucket_id.as_str(), bucket_name.as_deref(), &application_configuration_map) {
dependants.push(Dependant::service(application.id, application.application.instances, application.values));
}
for (app_id, _, resource_ids) in apps_that_use_resource(bucket_id.as_str(), &apps, &bucket_resources_from_app) {
dependants.push(Dependant::app(
app_id.to_string(),
resource_ids.iter().map(|resource_id| resource_id.to_string()).collect_vec(),
));
}
buckets_with_dependants.push((bucket_id.to_string(), bucket_status, dependants));
}
Ok(buckets_with_dependants)
}
pub async fn bucket_secrets(&self) -> DshApiResult<(String, String)> {
Ok(try_join!(
self.get_secret(OBJECT_STORE_ACCESS_KEY_ID),
self.get_secret(OBJECT_STORE_SECRET_ACCESS_KEY)
)?)
}
async fn object_store_access_key_id_if_required(&self) -> DshApiResult<Option<String>> {
match self.platform().cloud_provider() {
CloudProvider::AWS => Ok(None),
CloudProvider::Azure => self.get_secret(OBJECT_STORE_ACCESS_KEY_ID).await.map(Some),
}
}
}
pub fn bucket_injections_from_applications<'a>(
bucket_id: &str,
bucket_name: Option<&str>,
applications: &'a HashMap<String, Application>,
) -> Vec<ApplicationValues<'a, BucketInjection>> {
let mut application_injections: Vec<ApplicationValues<BucketInjection>> = vec![];
for (application_id, application) in applications {
let environment_variable_keys: Vec<BucketInjection> = bucket_injections_from_application(bucket_id, bucket_name, application);
if !environment_variable_keys.is_empty() {
application_injections.push(ApplicationValues::new(application_id, application, environment_variable_keys));
}
}
application_injections.sort();
application_injections
}
pub fn bucket_injections_from_application(bucket_id: &str, bucket_name: Option<&str>, application: &Application) -> Vec<BucketInjection> {
let mut env_var_keys = application
.env
.iter()
.filter_map(|(env_key, env_value)| match parse_function1(env_value, "bucket_name") {
Ok(bucket_string) => {
if bucket_id == bucket_string {
Some(BucketInjection::variable(env_key))
} else {
None
}
}
Err(_) => match bucket_name {
Some(name) => {
if env_value.contains(name) {
Some(BucketInjection::env_var(env_key))
} else {
None
}
}
None => None,
},
})
.collect_vec();
env_var_keys.sort();
env_var_keys
}
pub fn bucket_resources_from_app(app: &AppCatalogApp) -> Vec<(&str, &Bucket)> {
app_resources(app, &|resource_value| match resource_value {
AppCatalogAppResourcesValue::Bucket(bucket) => Some(bucket),
_ => None,
})
}
pub fn buckets_from_application(application: &Application) -> Vec<EnvVarInjection> {
let mut buckets = HashMap::<&str, Vec<&str>>::new();
for (env_key, env_value) in &application.env {
if let Ok(bucket_id) = parse_function1(env_value, "bucket_name") {
buckets.entry(bucket_id).or_default().push(env_key);
}
}
let mut sorted_buckets: Vec<EnvVarInjection> = buckets.into_iter().map(EnvVarInjection::from).collect_vec();
sorted_buckets.sort();
sorted_buckets
}
pub fn buckets_from_applications(applications: &HashMap<String, Application>) -> Vec<ApplicationValues<EnvVarInjection>> {
let mut application_tuples: Vec<ApplicationValues<EnvVarInjection>> = vec![];
for (application_id, application) in applications {
let injections: Vec<EnvVarInjection> = buckets_from_application(application);
if !injections.is_empty() {
application_tuples.push(ApplicationValues::new(application_id, application, injections));
}
}
application_tuples.sort();
application_tuples
}