1pub mod client;
9pub mod error;
10pub mod models;
11pub mod region;
12pub mod requests;
13pub mod responses;
14pub mod retry;
15
16pub use error::{OciError, Result};
18pub use models::*;
19pub use region::Region;
20pub use requests::*;
21pub use responses::*;
22pub use retry::{Retrier, RetryConfiguration};
23
24use crate::auth::provider::AuthProvider;
25use client::http_client::OciClient;
26use std::sync::Arc;
27
28pub struct ClientConfig {
30 pub auth_provider: Arc<dyn AuthProvider>,
31 pub region: Region,
32 pub timeout: std::time::Duration,
33 pub retry: Retrier,
34}
35
36pub fn client(config: ClientConfig) -> Result<CoreClient> {
38 let endpoint = format!("https://iaas.{}.oraclecloud.com", config.region.id());
39 let client = OciClient::with_timeout(config.auth_provider, endpoint, config.timeout)?.with_retrier(config.retry);
40
41 Ok(CoreClient { client })
42}
43
44#[async_trait::async_trait]
45pub trait CoreApi: Send + Sync {
46 async fn list_instances(
47 &self,
48 request: ListInstancesRequest,
49 ) -> Result<ListInstancesResponse>;
50
51 async fn launch_instance(
52 &self,
53 request: LaunchInstanceRequest,
54 ) -> Result<LaunchInstanceResponse>;
55
56 async fn get_instance(&self, request: GetInstanceRequest) -> Result<GetInstanceResponse>;
57
58 async fn terminate_instance(
59 &self,
60 request: TerminateInstanceRequest,
61 ) -> Result<TerminateInstanceResponse>;
62
63 async fn list_vnic_attachments(
64 &self,
65 request: ListVnicAttachmentsRequest,
66 ) -> Result<ListVnicAttachmentsResponse>;
67
68 async fn get_vnic(&self, request: GetVnicRequest) -> Result<GetVnicResponse>;
69
70 async fn list_public_ips(
71 &self,
72 request: ListPublicIpsRequest,
73 ) -> Result<ListPublicIpsResponse>;
74}
75
76pub struct CoreClient {
78 client: OciClient,
79}
80
81#[async_trait::async_trait]
82impl CoreApi for CoreClient {
83 async fn list_instances(
84 &self,
85 request: ListInstancesRequest,
86 ) -> Result<ListInstancesResponse> {
87 let mut query_params = vec![("compartmentId".to_string(), request.compartment_id.clone())];
88
89 if let Some(av_domain) = &request.availability_domain {
90 query_params.push(("availabilityDomain".to_string(), av_domain.clone()));
91 }
92 if let Some(display_name) = &request.display_name {
93 query_params.push(("displayName".to_string(), display_name.clone()));
94 }
95 if let Some(limit) = request.limit {
96 query_params.push(("limit".to_string(), limit.to_string()));
97 }
98 if let Some(page) = &request.page {
99 query_params.push(("page".to_string(), page.clone()));
100 }
101 if let Some(lifecycle_state) = &request.lifecycle_state {
102 query_params.push(("lifecycleState".to_string(), lifecycle_state.clone()));
103 }
104
105 let query_string = if query_params.is_empty() {
106 String::new()
107 } else {
108 format!(
109 "?{}",
110 query_params
111 .iter()
112 .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
113 .collect::<Vec<_>>()
114 .join("&")
115 )
116 };
117
118 let path = format!("/20160918/instances{}", query_string);
119 let response = self.client.get(&path).await?;
120
121 Ok(ListInstancesResponse {
122 opc_next_page: response.get_header("opc-next-page"),
123 opc_request_id: response.get_header("opc-request-id"),
124 items: response.body,
125 })
126 }
127
128 async fn launch_instance(
129 &self,
130 request: LaunchInstanceRequest,
131 ) -> Result<LaunchInstanceResponse> {
132 let path = "/20160918/instances".to_string();
133 let response = self
134 .client
135 .post(&path, Some(&request.launch_instance_details))
136 .await?;
137
138 Ok(LaunchInstanceResponse {
139 etag: response.get_header("etag"),
140 opc_request_id: response.get_header("opc-request-id"),
141 opc_work_request_id: response.get_header("opc-work-request-id"),
142 instance: response.body,
143 })
144 }
145
146 async fn get_instance(&self, request: GetInstanceRequest) -> Result<GetInstanceResponse> {
147 let path = format!("/20160918/instances/{}", request.instance_id);
148 let response = self.client.get(&path).await?;
149
150 Ok(GetInstanceResponse {
151 etag: response.get_header("etag"),
152 opc_request_id: response.get_header("opc-request-id"),
153 instance: response.body,
154 })
155 }
156
157 async fn terminate_instance(
158 &self,
159 request: TerminateInstanceRequest,
160 ) -> Result<TerminateInstanceResponse> {
161 let mut query_params = Vec::new();
162 if let Some(preserve_boot_volume) = request.preserve_boot_volume {
163 query_params.push(("preserveBootVolume".to_string(), preserve_boot_volume.to_string()));
164 }
165
166 let query_string = if query_params.is_empty() {
167 String::new()
168 } else {
169 format!(
170 "?{}",
171 query_params
172 .iter()
173 .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
174 .collect::<Vec<_>>()
175 .join("&")
176 )
177 };
178
179 let path = format!("/20160918/instances/{}{}", request.instance_id, query_string);
180 let response: client::http_client::OciResponse<serde_json::Value> =
181 self.client.delete(&path).await?;
182
183 Ok(TerminateInstanceResponse {
184 opc_request_id: response.get_header("opc-request-id").unwrap_or_default(),
185 })
186 }
187
188 async fn list_vnic_attachments(
189 &self,
190 request: ListVnicAttachmentsRequest,
191 ) -> Result<ListVnicAttachmentsResponse> {
192 let mut query_params = vec![("compartmentId".to_string(), request.compartment_id.clone())];
193
194 if let Some(instance_id) = &request.instance_id {
195 query_params.push(("instanceId".to_string(), instance_id.clone()));
196 }
197 if let Some(limit) = request.limit {
198 query_params.push(("limit".to_string(), limit.to_string()));
199 }
200 if let Some(page) = &request.page {
201 query_params.push(("page".to_string(), page.clone()));
202 }
203
204 let query_string = format!(
205 "?{}",
206 query_params
207 .iter()
208 .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
209 .collect::<Vec<_>>()
210 .join("&")
211 );
212
213 let path = format!("/20160918/vnicAttachments{}", query_string);
214 let response = self.client.get(&path).await?;
215
216 Ok(ListVnicAttachmentsResponse {
217 opc_next_page: response.get_header("opc-next-page").unwrap_or_default(),
218 opc_request_id: response.get_header("opc-request-id").unwrap_or_default(),
219 items: response.body,
220 })
221 }
222
223 async fn get_vnic(&self, request: GetVnicRequest) -> Result<GetVnicResponse> {
224 let path = format!("/20160918/vnics/{}", request.vnic_id);
225 let response = self.client.get(&path).await?;
226
227 Ok(GetVnicResponse {
228 etag: response.get_header("etag").unwrap_or_default(),
229 opc_request_id: response.get_header("opc-request-id").unwrap_or_default(),
230 vnic: response.body,
231 })
232 }
233
234 async fn list_public_ips(
235 &self,
236 request: ListPublicIpsRequest,
237 ) -> Result<ListPublicIpsResponse> {
238 let mut query_params = vec![("scope".to_string(), format!("{:?}", request.scope))];
239 query_params.push(("compartmentId".to_string(), request.compartment_id.clone()));
240
241 if let Some(limit) = request.limit {
242 query_params.push(("limit".to_string(), limit.to_string()));
243 }
244 if let Some(page) = &request.page {
245 query_params.push(("page".to_string(), page.clone()));
246 }
247 if let Some(availability_domain) = &request.availability_domain {
248 query_params.push((
249 "availabilityDomain".to_string(),
250 availability_domain.clone(),
251 ));
252 }
253 if let Some(lifetime) = &request.lifetime {
254 query_params.push(("lifetime".to_string(), format!("{:?}", lifetime)));
255 }
256
257 let query_string = format!(
258 "?{}",
259 query_params
260 .iter()
261 .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
262 .collect::<Vec<_>>()
263 .join("&")
264 );
265
266 let path = format!("/20160918/publicIps{}", query_string);
267 let response = self.client.get(&path).await?;
268
269 Ok(ListPublicIpsResponse {
270 opc_next_page: response.get_header("opc-next-page"),
271 opc_request_id: response.get_header("opc-request-id"),
272 items: response.body,
273 })
274 }
275}