1use crate::auth::config_loader::ConfigLoader;
6use crate::auth::key_loader::KeyLoader;
7use crate::client::signer::OciSigner;
8use crate::error::{Error, Result};
9use crate::services::email::EmailDelivery;
10use crate::services::object_storage::ObjectStorage;
11use reqwest::Client;
12use std::env;
13
14#[derive(Clone)]
16pub struct Oci {
17 client: Client,
19
20 region: String,
22
23 tenancy_id: String,
25
26 compartment_id: Option<String>,
28
29 signer: OciSigner,
31}
32
33impl Default for Oci {
34 fn default() -> Self {
35 Self::from_env().expect("Failed to create OCI client from environment")
36 }
37}
38
39impl Oci {
40 pub fn from_env() -> Result<Self> {
42 let partial_config = if let Ok(config_value) = env::var("OCI_CONFIG") {
44 Some(ConfigLoader::load_partial_from_env_var(&config_value)?)
45 } else {
46 None
47 };
48
49 let user_id = env::var("OCI_USER_ID")
51 .ok()
52 .or_else(|| partial_config.as_ref().and_then(|c| c.user_id.clone()))
53 .ok_or_else(|| {
54 Error::EnvError(
55 "OCI_USER_ID must be set (either directly or via OCI_CONFIG)".to_string(),
56 )
57 })?;
58
59 let tenancy_id = env::var("OCI_TENANCY_ID")
60 .ok()
61 .or_else(|| partial_config.as_ref().and_then(|c| c.tenancy_id.clone()))
62 .ok_or_else(|| {
63 Error::EnvError(
64 "OCI_TENANCY_ID must be set (either directly or via OCI_CONFIG)".to_string(),
65 )
66 })?;
67
68 let region = env::var("OCI_REGION")
69 .ok()
70 .or_else(|| partial_config.as_ref().and_then(|c| c.region.clone()))
71 .ok_or_else(|| {
72 Error::EnvError(
73 "OCI_REGION must be set (either directly or via OCI_CONFIG)".to_string(),
74 )
75 })?;
76
77 let fingerprint = env::var("OCI_FINGERPRINT")
78 .ok()
79 .or_else(|| partial_config.as_ref().and_then(|c| c.fingerprint.clone()))
80 .ok_or_else(|| {
81 Error::EnvError(
82 "OCI_FINGERPRINT must be set (either directly or via OCI_CONFIG)".to_string(),
83 )
84 })?;
85
86 let private_key = if let Ok(key_input) = env::var("OCI_PRIVATE_KEY") {
88 KeyLoader::load(&key_input)?
89 } else if let Ok(config_value) = env::var("OCI_CONFIG") {
90 let full_config = ConfigLoader::load_from_env_var(&config_value, None)?;
91 full_config.private_key
92 } else {
93 return Err(Error::EnvError(
94 "OCI_PRIVATE_KEY must be set (or key_file must be in OCI_CONFIG)".to_string(),
95 ));
96 };
97
98 let compartment_id = env::var("OCI_COMPARTMENT_ID").ok();
100
101 Self::builder()
102 .user_id(user_id)
103 .tenancy_id(tenancy_id)
104 .region(region)
105 .fingerprint(fingerprint)
106 .private_key(private_key)?
107 .compartment_id_opt(compartment_id)
108 .build()
109 }
110
111 pub fn builder() -> OciBuilder {
113 OciBuilder::default()
114 }
115
116 pub fn signer(&self) -> &OciSigner {
118 &self.signer
119 }
120
121 pub fn client(&self) -> &Client {
123 &self.client
124 }
125
126 pub fn region(&self) -> &str {
128 &self.region
129 }
130
131 pub fn tenancy_id(&self) -> &str {
133 &self.tenancy_id
134 }
135
136 pub fn compartment_id(&self) -> &str {
138 self.compartment_id.as_ref().unwrap_or(&self.tenancy_id)
139 }
140
141 pub async fn email_delivery(&self) -> Result<EmailDelivery> {
143 EmailDelivery::new(self.clone()).await
144 }
145
146 pub fn object_storage(&self, namespace: impl Into<String>) -> ObjectStorage {
148 ObjectStorage::new(self, namespace)
149 }
150}
151
152#[derive(Default)]
154pub struct OciBuilder {
155 user_id: Option<String>,
156 tenancy_id: Option<String>,
157 region: Option<String>,
158 fingerprint: Option<String>,
159 private_key: Option<String>,
160 compartment_id: Option<String>,
161}
162
163impl OciBuilder {
164 pub fn config(mut self, path: impl AsRef<std::path::Path>) -> Result<Self> {
166 let loaded = ConfigLoader::load_from_file(path.as_ref(), Some("DEFAULT"))?;
167
168 self.user_id = Some(loaded.user_id);
169 self.tenancy_id = Some(loaded.tenancy_id);
170 self.region = Some(loaded.region);
171 self.fingerprint = Some(loaded.fingerprint);
172 self.private_key = Some(loaded.private_key);
173
174 Ok(self)
175 }
176
177 pub fn user_id(mut self, user_id: impl Into<String>) -> Self {
178 self.user_id = Some(user_id.into());
179 self
180 }
181
182 pub fn tenancy_id(mut self, tenancy_id: impl Into<String>) -> Self {
183 self.tenancy_id = Some(tenancy_id.into());
184 self
185 }
186
187 pub fn region(mut self, region: impl Into<String>) -> Self {
188 self.region = Some(region.into());
189 self
190 }
191
192 pub fn fingerprint(mut self, fingerprint: impl Into<String>) -> Self {
193 self.fingerprint = Some(fingerprint.into());
194 self
195 }
196
197 pub fn private_key(mut self, private_key: impl Into<String>) -> Result<Self> {
198 let key_input = private_key.into();
199 let loaded_key = KeyLoader::load(&key_input)?;
200 self.private_key = Some(loaded_key);
201 Ok(self)
202 }
203
204 pub fn compartment_id(mut self, compartment_id: impl Into<String>) -> Self {
205 self.compartment_id = Some(compartment_id.into());
206 self
207 }
208
209 fn compartment_id_opt(mut self, compartment_id: Option<String>) -> Self {
211 self.compartment_id = compartment_id;
212 self
213 }
214
215 pub fn build(self) -> Result<Oci> {
216 let user_id = self
217 .user_id
218 .ok_or_else(|| Error::ConfigError("user_id is not set".to_string()))?;
219 let tenancy_id = self
220 .tenancy_id
221 .ok_or_else(|| Error::ConfigError("tenancy_id is not set".to_string()))?;
222 let region = self
223 .region
224 .ok_or_else(|| Error::ConfigError("region is not set".to_string()))?;
225 let fingerprint = self
226 .fingerprint
227 .ok_or_else(|| Error::ConfigError("fingerprint is not set".to_string()))?;
228 let private_key = self
229 .private_key
230 .ok_or_else(|| Error::ConfigError("private_key is not set".to_string()))?;
231
232 let signer = OciSigner::new(&user_id, &tenancy_id, &fingerprint, &private_key)?;
233 let client = Client::builder().build()?;
234
235 Ok(Oci {
236 client,
237 region,
238 tenancy_id,
239 compartment_id: self.compartment_id,
240 signer,
241 })
242 }
243}