1use error::Error;
2use std::time::Duration;
3use tokio::time::timeout;
4use url::Url;
5
6use hyper::body::Buf;
8use hyper::header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE, USER_AGENT};
9use hyper::{Method, Request};
10#[cfg(feature = "rustls")]
11type HttpsConnector = hyper_rustls::HttpsConnector<hyper::client::HttpConnector>;
12#[cfg(feature = "rust-native-tls")]
13use hyper_tls;
14#[cfg(feature = "rust-native-tls")]
15type HttpsConnector = hyper_tls::HttpsConnector<hyper::client::HttpConnector>;
16
17pub mod error;
18pub mod resource_url;
19
20pub mod cluster;
21pub mod kubeversion;
22pub mod node;
23pub mod nodegroup;
24pub mod task;
25
26static PKG_NAME: &str = env!("CARGO_PKG_NAME");
28static PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
29
30pub struct Client {
32 client: hyper::Client<HttpsConnector>,
33 token: String,
34 base_endpoint: url::Url,
35 user_agent: String,
36 timeout: Duration,
37}
38
39impl Client {
40 pub fn new(base_endpoint: &str, token: &str) -> Result<Client, Error> {
44 Client::with_builder(base_endpoint, token, Client::builder())
45 }
46
47 fn with_builder(base_endpoint: &str, token: &str, builder: Builder) -> Result<Client, Error> {
48 if token.is_empty() {
50 return Err(Error::EmptyTokenError);
51 }
52 let token = String::from(token);
53
54 let base_endpoint = Url::parse(base_endpoint).map_err(|_| Error::EndpointError)?;
56
57 let client = match builder.client {
59 Some(client) => client,
60 None => {
61 #[cfg(feature = "rustls")]
62 let client = hyper::Client::builder().build(HttpsConnector::new());
63 #[cfg(feature = "rust-native-tls")]
64 let client = hyper::Client::builder().build(HttpsConnector::new()?);
65
66 client
67 }
68 };
69
70 Ok(Client {
71 client,
72 token,
73 base_endpoint,
74 user_agent: Client::user_agent(),
75 timeout: builder.timeout,
76 })
77 }
78
79 fn user_agent() -> String {
80 format!("{}/{}", PKG_NAME, PKG_VERSION)
81 }
82
83 pub fn builder() -> Builder {
85 Builder::default()
86 }
87
88 fn new_request(
90 &self,
91 method: Method,
92 path: &str,
93 body: Option<String>,
94 ) -> Result<Request<hyper::Body>, Error> {
95 let uri = self.make_uri(path)?;
97
98 let mut req = Request::new(hyper::Body::empty());
100 *req.method_mut() = method;
101 *req.uri_mut() = uri;
102
103 req.headers_mut().insert(
105 USER_AGENT,
106 HeaderValue::from_str(&self.user_agent).map_err(|_| Error::RequestError)?,
107 );
108
109 req.headers_mut().insert(
111 "x-auth-token",
112 HeaderValue::from_str(&self.token).map_err(|_| Error::RequestError)?,
113 );
114
115 if let Some(body) = body {
117 let len =
119 HeaderValue::from_str(&body.len().to_string()).map_err(|_| Error::RequestError)?;
120 req.headers_mut().insert(CONTENT_LENGTH, len);
121
122 req.headers_mut().insert(
124 CONTENT_TYPE,
125 HeaderValue::from_str("application/json").map_err(|_| Error::RequestError)?,
126 );
127
128 *req.body_mut() = hyper::Body::from(body);
129 }
130
131 Ok(req)
132 }
133
134 #[tokio::main]
135 async fn do_request(&self, req: hyper::Request<hyper::Body>) -> Result<String, Error> {
136 let duration = self.timeout;
137 let handle = async {
138 let raw_resp = self.client.request(req).await?;
139
140 let status = raw_resp.status();
141 let body = hyper::body::aggregate(raw_resp).await?.to_bytes();
142 let body = String::from_utf8_lossy(&body);
143
144 Ok::<_, hyper::Error>((body.to_string(), status))
145 };
146
147 let raw_resp = timeout(duration, handle).await??;
148
149 let (body, status) = raw_resp;
150
151 if !status.is_success() {
152 return Err(Error::HttpError(status.as_u16(), body));
153 }
154
155 Ok(body)
156 }
157
158 fn make_uri(&self, path: &str) -> Result<hyper::Uri, Error> {
159 let url = self
160 .base_endpoint
161 .clone()
162 .join(path)
163 .map_err(|_| Error::UrlError)?;
164
165 url.as_str()
166 .parse::<hyper::Uri>()
167 .map_err(|_| Error::UrlError)
168 }
169}
170
171impl Client {
173 pub fn get_cluster(&self, cluster_id: &str) -> Result<cluster::schemas::Cluster, Error> {
175 cluster::api::get(self, cluster_id)
176 }
177
178 pub fn list_clusters(&self) -> Result<Vec<cluster::schemas::Cluster>, Error> {
180 cluster::api::list(self)
181 }
182
183 pub fn create_cluster(
185 &self,
186 opts: &cluster::schemas::CreateOpts,
187 ) -> Result<cluster::schemas::Cluster, Error> {
188 cluster::api::create(self, opts)
189 }
190
191 pub fn delete_cluster(&self, cluster_id: &str) -> Result<(), Error> {
193 cluster::api::delete(self, cluster_id)
194 }
195}
196
197impl Client {
199 pub fn list_kube_versions(&self) -> Result<Vec<kubeversion::schemas::KubeVersion>, Error> {
201 kubeversion::api::list(self)
202 }
203}
204
205impl Client {
207 pub fn get_node(
209 &self,
210 cluster_id: &str,
211 nodegroup_id: &str,
212 node_id: &str,
213 ) -> Result<node::schemas::Node, Error> {
214 node::api::get(self, cluster_id, nodegroup_id, node_id)
215 }
216
217 pub fn reinstall_node(
219 &self,
220 cluster_id: &str,
221 nodegroup_id: &str,
222 node_id: &str,
223 ) -> Result<(), Error> {
224 node::api::reinstall(self, cluster_id, nodegroup_id, node_id)
225 }
226}
227
228impl Client {
230 pub fn get_nodegroup(
232 &self,
233 cluster_id: &str,
234 nodegroup_id: &str,
235 ) -> Result<nodegroup::schemas::Nodegroup, Error> {
236 nodegroup::api::get(self, cluster_id, nodegroup_id)
237 }
238
239 pub fn list_nodegroups(
241 &self,
242 cluster_id: &str,
243 ) -> Result<Vec<nodegroup::schemas::Nodegroup>, Error> {
244 nodegroup::api::list(self, cluster_id)
245 }
246
247 pub fn create_nodegroup(
249 &self,
250 cluster_id: &str,
251 opts: &nodegroup::schemas::CreateOpts,
252 ) -> Result<(), Error> {
253 nodegroup::api::create(self, cluster_id, opts)
254 }
255
256 pub fn delete_nodegroup(&self, cluster_id: &str, nodegroup_id: &str) -> Result<(), Error> {
258 nodegroup::api::delete(self, cluster_id, nodegroup_id)
259 }
260
261 pub fn resize_nodegroup(
263 &self,
264 cluster_id: &str,
265 nodegroup_id: &str,
266 opts: &nodegroup::schemas::ResizeOpts,
267 ) -> Result<(), Error> {
268 nodegroup::api::resize(self, cluster_id, nodegroup_id, opts)
269 }
270
271 pub fn update_nodegroup(
273 &self,
274 cluster_id: &str,
275 nodegroup_id: &str,
276 opts: &nodegroup::schemas::UpdateOpts,
277 ) -> Result<(), Error> {
278 nodegroup::api::update(self, cluster_id, nodegroup_id, opts)
279 }
280}
281
282impl Client {
284 pub fn get_task(&self, cluster_id: &str, task_id: &str) -> Result<task::schemas::Task, Error> {
286 task::api::get(self, cluster_id, task_id)
287 }
288
289 pub fn list_tasks(&self, cluster_id: &str) -> Result<Vec<task::schemas::Task>, Error> {
291 task::api::list(self, cluster_id)
292 }
293}
294
295pub struct Builder {
297 client: Option<hyper::Client<HttpsConnector>>,
299
300 timeout: Duration,
302}
303
304const DEFAULT_TIMEOUT: u64 = 30;
306
307impl Default for Builder {
308 fn default() -> Self {
309 Self {
310 client: None,
311 timeout: Duration::from_secs(DEFAULT_TIMEOUT),
312 }
313 }
314}
315
316impl Builder {
317 pub fn with_client(mut self, client: hyper::Client<HttpsConnector>) -> Self {
322 self.client = Some(client);
323 self
324 }
325
326 pub fn with_timeout(mut self, timeout: Duration) -> Self {
330 self.timeout = timeout;
331 self
332 }
333
334 pub fn build(self, base_endpoint: &str, token: &str) -> Result<Client, Error> {
336 Client::with_builder(base_endpoint, token, self)
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 #[test]
345 fn new_client_default_builder() {
346 let client = Client::new("https://example.org", "token_a").unwrap();
347
348 assert_eq!(
349 client.base_endpoint,
350 Url::parse("https://example.org").unwrap()
351 );
352 assert_eq!(client.token, String::from("token_a"));
353 assert_eq!(client.user_agent, format!("{}/{}", PKG_NAME, PKG_VERSION));
354 assert_eq!(client.timeout, Duration::from_secs(DEFAULT_TIMEOUT));
355 }
356
357 #[test]
358 fn new_client_with_builder() {
359 let client = Client::builder()
360 .with_timeout(Duration::from_secs(10))
361 .build("https://example.com", "token_b")
362 .unwrap();
363
364 assert_eq!(
365 client.base_endpoint,
366 Url::parse("https://example.com").unwrap()
367 );
368 assert_eq!(client.token, String::from("token_b"));
369 assert_eq!(client.user_agent, format!("{}/{}", PKG_NAME, PKG_VERSION));
370 assert_eq!(client.timeout, Duration::from_secs(10));
371 }
372}