use crate::dsh_api_client::DshApiClient;
use crate::dsh_api_tenant::DshApiTenant;
use crate::error::DshApiResult;
use crate::platform::DshPlatform;
use crate::token_fetcher::TokenFetcher;
use crate::DshApiError;
use log::{debug, error};
use reqwest::Client;
use std::env;
use std::io::ErrorKind;
#[derive(Debug)]
pub struct DshApiClientFactory {
client: Option<Client>,
tenant: DshApiTenant,
static_token: Option<String>,
robot_password: Option<String>,
}
impl DshApiClientFactory {
pub fn new() -> DshApiClientFactory {
DshApiClientFactory::default()
}
pub fn create_with_token_fetcher<T>(tenant: DshApiTenant, robot_password: T) -> Self
where
T: Into<String>,
{
debug!("create dsh api client factory with token fetcher for '{}'", tenant);
DshApiClientFactory { client: None, tenant, static_token: None, robot_password: Some(robot_password.into()) }
}
pub fn create_from_static_token<T>(tenant: DshApiTenant, static_token: T) -> Self
where
T: Into<String>,
{
debug!("create dsh api client factory with static token for '{}'", tenant);
DshApiClientFactory { client: None, tenant, static_token: Some(static_token.into()), robot_password: None }
}
#[deprecated]
pub fn create(tenant: DshApiTenant, password: String) -> DshApiResult<Self> {
Ok(Self::create_with_token_fetcher(tenant, password))
}
pub fn try_default_with_token_fetcher() -> DshApiResult<Self> {
let tenant = DshApiTenant::try_default()?;
match get_robot_password(&tenant)? {
Some(robot_password) => {
debug!("default dsh api client factory with token fetcher");
Ok(DshApiClientFactory::create_with_token_fetcher(tenant, robot_password))
}
None => Err(DshApiError::configuration("missing default configuration for token fetcher")),
}
}
pub fn try_default_from_static_token() -> DshApiResult<Self> {
let tenant = DshApiTenant::try_default()?;
match get_static_token(&tenant)? {
Some(static_token) => {
debug!("default dsh api client factory with static token");
Ok(DshApiClientFactory::create_from_static_token(tenant, static_token))
}
None => Err(DshApiError::configuration("missing default configuration for static token")),
}
}
pub fn try_default() -> DshApiResult<Self> {
let tenant = DshApiTenant::try_default()?;
match get_robot_password(&tenant)? {
Some(robot_password) => {
debug!("default dsh api client factory with token fetcher");
Ok(DshApiClientFactory::create_with_token_fetcher(tenant, robot_password))
}
None => match get_static_token(&tenant)? {
Some(static_token) => {
debug!("default dsh api client factory with static token");
Ok(DshApiClientFactory::create_from_static_token(tenant, static_token))
}
None => Err(DshApiError::configuration("missing robot password or static token configuration")),
},
}
}
pub fn platform(&self) -> &DshPlatform {
self.tenant.platform()
}
pub fn tenant(&self) -> &DshApiTenant {
&self.tenant
}
pub fn tenant_name(&self) -> &str {
self.tenant.name()
}
pub async fn client(self) -> DshApiResult<DshApiClient> {
if let Some(robot_password) = self.robot_password {
let token_fetcher = TokenFetcher::new(self.tenant.clone(), robot_password, None, None);
Ok(DshApiClient::with_token_fetcher(token_fetcher, self.client, self.tenant.clone()))
} else if let Some(static_token) = self.static_token {
debug!("dsh api client created with static token");
Ok(DshApiClient::with_static_token(static_token, self.client, self.tenant.clone()))
} else {
unreachable!()
}
}
}
impl Default for DshApiClientFactory {
fn default() -> Self {
match Self::try_default() {
Ok(factory) => factory,
Err(error) => panic!("{}", error),
}
}
}
#[derive(Debug)]
pub struct DshApiPlatformClientFactory {
client: Client,
platform: DshPlatform,
static_token: String,
}
impl DshApiPlatformClientFactory {
pub fn create_from_static_token<T>(platform: DshPlatform, static_token: T) -> DshApiResult<Self>
where
T: Into<String>,
{
debug!("create dsh api platform client factory with static token for '{}'", platform);
#[cfg(not(target_arch = "wasm32"))]
let client = {
let dur = std::time::Duration::from_secs(15);
reqwest::ClientBuilder::new().connect_timeout(dur).timeout(dur).build()?
};
#[cfg(target_arch = "wasm32")]
let client = reqwest::ClientBuilder::new().build()?;
Ok(DshApiPlatformClientFactory { client, platform, static_token: static_token.into() })
}
pub fn platform(&self) -> &DshPlatform {
&self.platform
}
pub async fn client<T>(&self, tenant_name: T) -> DshApiResult<DshApiClient>
where
T: Into<String>,
{
let tenant = DshApiTenant::new(tenant_name.into(), self.platform.clone());
Ok(DshApiClient::with_static_token(self.static_token.clone(), Some(self.client.clone()), tenant))
}
}
const ENV_VAR_STATIC_TOKEN_PREFIX: &str = "DSH_API_STATIC_TOKEN";
const ENV_VAR_STATIC_TOKEN_FILE_PREFIX: &str = "DSH_API_STATIC_TOKEN_FILE";
fn get_static_token(tenant: &DshApiTenant) -> DshApiResult<Option<String>> {
get_password(tenant, ENV_VAR_STATIC_TOKEN_PREFIX, ENV_VAR_STATIC_TOKEN_FILE_PREFIX)
}
const ENV_VAR_PASSWORD_PREFIX: &str = "DSH_API_PASSWORD";
const ENV_VAR_PASSWORD_FILE_PREFIX: &str = "DSH_API_PASSWORD_FILE";
pub(crate) fn get_robot_password(tenant: &DshApiTenant) -> DshApiResult<Option<String>> {
get_password(tenant, ENV_VAR_PASSWORD_PREFIX, ENV_VAR_PASSWORD_FILE_PREFIX)
}
fn get_password(tenant: &DshApiTenant, password_env_var_prefix: &str, password_file_env_var_prefix: &str) -> DshApiResult<Option<String>> {
let password_file_env_var = environment_variable(password_file_env_var_prefix, tenant.platform(), tenant.name());
match env::var(&password_file_env_var) {
Ok(password_file_from_env_var) => match std::fs::read_to_string(&password_file_from_env_var) {
Ok(password_from_file) => {
let trimmed_password = password_from_file.trim();
if trimmed_password.is_empty() {
let message = format!(
"password file '{}' is empty (environment variable '{}')",
password_file_from_env_var, password_file_env_var
);
error!("{}", message);
Err(DshApiError::configuration(message))
} else {
debug!(
"password read from file '{}' (environment variable '{}')",
password_file_from_env_var, password_file_env_var
);
Ok(Some(trimmed_password.to_string()))
}
}
Err(io_error) => match io_error.kind() {
ErrorKind::NotFound => {
let message = format!(
"password file '{}' not found (environment variable '{}')",
password_file_from_env_var, password_file_env_var
);
error!("{}", message);
Err(DshApiError::NotFound { message: Some(message) })
}
_ => {
let message = format!(
"password file '{}' could not be read (environment variable '{}')",
password_file_from_env_var, password_file_env_var
);
error!("{}", message);
Err(DshApiError::Unexpected { message, cause: Some(io_error.to_string()) })
}
},
},
Err(_) => {
let password_env_var = environment_variable(password_env_var_prefix, tenant.platform(), tenant.name());
match env::var(&password_env_var) {
Ok(password_from_env_var) => {
debug!("password read (environment variable '{}')", password_env_var);
Ok(Some(password_from_env_var))
}
Err(_) => {
debug!("environment variable '{}' not set", password_env_var);
Ok(None)
}
}
}
}
}
fn environment_variable(env_var_prefix: &str, platform: &DshPlatform, tenant_name: &str) -> String {
format!(
"{}_{}_{}",
env_var_prefix,
platform.name().to_ascii_uppercase().replace('-', "_"),
tenant_name.to_ascii_uppercase().replace('-', "_")
)
}