use crate::auth::config_loader::ConfigLoader;
use crate::auth::key_loader::KeyLoader;
use crate::client::signer::OciSigner;
use crate::error::{Error, Result};
use crate::services::email::EmailDelivery;
use crate::services::object_storage::ObjectStorage;
use reqwest::Client;
use std::env;
#[derive(Clone)]
pub struct Oci {
client: Client,
region: String,
tenancy_id: String,
compartment_id: Option<String>,
signer: OciSigner,
}
impl Default for Oci {
fn default() -> Self {
Self::from_env().expect("Failed to create OCI client from environment")
}
}
impl Oci {
pub fn from_env() -> Result<Self> {
let partial_config = if let Ok(config_value) = env::var("OCI_CONFIG") {
Some(ConfigLoader::load_partial_from_env_var(&config_value)?)
} else {
None
};
let user_id = env::var("OCI_USER_ID")
.ok()
.or_else(|| partial_config.as_ref().and_then(|c| c.user_id.clone()))
.ok_or_else(|| {
Error::EnvError(
"OCI_USER_ID must be set (either directly or via OCI_CONFIG)".to_string(),
)
})?;
let tenancy_id = env::var("OCI_TENANCY_ID")
.ok()
.or_else(|| partial_config.as_ref().and_then(|c| c.tenancy_id.clone()))
.ok_or_else(|| {
Error::EnvError(
"OCI_TENANCY_ID must be set (either directly or via OCI_CONFIG)".to_string(),
)
})?;
let region = env::var("OCI_REGION")
.ok()
.or_else(|| partial_config.as_ref().and_then(|c| c.region.clone()))
.ok_or_else(|| {
Error::EnvError(
"OCI_REGION must be set (either directly or via OCI_CONFIG)".to_string(),
)
})?;
let fingerprint = env::var("OCI_FINGERPRINT")
.ok()
.or_else(|| partial_config.as_ref().and_then(|c| c.fingerprint.clone()))
.ok_or_else(|| {
Error::EnvError(
"OCI_FINGERPRINT must be set (either directly or via OCI_CONFIG)".to_string(),
)
})?;
let private_key = if let Ok(key_input) = env::var("OCI_PRIVATE_KEY") {
KeyLoader::load(&key_input)?
} else if let Ok(config_value) = env::var("OCI_CONFIG") {
let full_config = ConfigLoader::load_from_env_var(&config_value, None)?;
full_config.private_key
} else {
return Err(Error::EnvError(
"OCI_PRIVATE_KEY must be set (or key_file must be in OCI_CONFIG)".to_string(),
));
};
let compartment_id = env::var("OCI_COMPARTMENT_ID").ok();
Self::builder()
.user_id(user_id)
.tenancy_id(tenancy_id)
.region(region)
.fingerprint(fingerprint)
.private_key(private_key)?
.compartment_id_opt(compartment_id)
.build()
}
pub fn builder() -> OciBuilder {
OciBuilder::default()
}
pub fn signer(&self) -> &OciSigner {
&self.signer
}
pub fn client(&self) -> &Client {
&self.client
}
pub fn region(&self) -> &str {
&self.region
}
pub fn tenancy_id(&self) -> &str {
&self.tenancy_id
}
pub fn compartment_id(&self) -> &str {
self.compartment_id.as_ref().unwrap_or(&self.tenancy_id)
}
pub async fn email_delivery(&self) -> Result<EmailDelivery> {
EmailDelivery::new(self.clone()).await
}
pub fn object_storage(&self, namespace: impl Into<String>) -> ObjectStorage {
ObjectStorage::new(self, namespace)
}
}
#[derive(Default)]
pub struct OciBuilder {
user_id: Option<String>,
tenancy_id: Option<String>,
region: Option<String>,
fingerprint: Option<String>,
private_key: Option<String>,
compartment_id: Option<String>,
}
impl OciBuilder {
pub fn config(mut self, path: impl AsRef<std::path::Path>) -> Result<Self> {
let loaded = ConfigLoader::load_from_file(path.as_ref(), Some("DEFAULT"))?;
self.user_id = Some(loaded.user_id);
self.tenancy_id = Some(loaded.tenancy_id);
self.region = Some(loaded.region);
self.fingerprint = Some(loaded.fingerprint);
self.private_key = Some(loaded.private_key);
Ok(self)
}
pub fn user_id(mut self, user_id: impl Into<String>) -> Self {
self.user_id = Some(user_id.into());
self
}
pub fn tenancy_id(mut self, tenancy_id: impl Into<String>) -> Self {
self.tenancy_id = Some(tenancy_id.into());
self
}
pub fn region(mut self, region: impl Into<String>) -> Self {
self.region = Some(region.into());
self
}
pub fn fingerprint(mut self, fingerprint: impl Into<String>) -> Self {
self.fingerprint = Some(fingerprint.into());
self
}
pub fn private_key(mut self, private_key: impl Into<String>) -> Result<Self> {
let key_input = private_key.into();
let loaded_key = KeyLoader::load(&key_input)?;
self.private_key = Some(loaded_key);
Ok(self)
}
pub fn compartment_id(mut self, compartment_id: impl Into<String>) -> Self {
self.compartment_id = Some(compartment_id.into());
self
}
fn compartment_id_opt(mut self, compartment_id: Option<String>) -> Self {
self.compartment_id = compartment_id;
self
}
pub fn build(self) -> Result<Oci> {
let user_id = self
.user_id
.ok_or_else(|| Error::ConfigError("user_id is not set".to_string()))?;
let tenancy_id = self
.tenancy_id
.ok_or_else(|| Error::ConfigError("tenancy_id is not set".to_string()))?;
let region = self
.region
.ok_or_else(|| Error::ConfigError("region is not set".to_string()))?;
let fingerprint = self
.fingerprint
.ok_or_else(|| Error::ConfigError("fingerprint is not set".to_string()))?;
let private_key = self
.private_key
.ok_or_else(|| Error::ConfigError("private_key is not set".to_string()))?;
let signer = OciSigner::new(&user_id, &tenancy_id, &fingerprint, &private_key)?;
let client = Client::builder().build()?;
Ok(Oci {
client,
region,
tenancy_id,
compartment_id: self.compartment_id,
signer,
})
}
}