linode_rs/
lib.rs

1//! Client library for the <https://www.linode.com/> API which
2//! is documented at <https://www.linode.com/docs/api/>
3//!
4//! # Example blocking
5//! It needs to have the feature "blocking" enabled.
6//! ```toml
7//! linode-rs = { version = "*", features = ["blocking"] }
8//! ```
9//! ```ignore
10//! use linode_rs::LinodeApi;
11//! use linode_rs::LinodeError;
12//!
13//! fn main() -> Result<(), LinodeError> {
14//!     let api = LinodeApi::new("<KEY>");
15//!     let list = api.list_os()?;
16//!     println!("OS {:#?}", list);
17//!     
18//!     let list = api.list_types()?;
19//!     println!("TYPES {:#?}", list);
20//!     
21//!     let list = api.list_instances()?;
22//!     println!("INSTANCES {:#?}", list);
23//!     Ok(())
24//! }
25//! ```
26//!
27//! # Example async
28//! ```toml
29//! linode-rs = { version = "*" }
30//! ```
31//! ```no_run
32//! use linode_rs::LinodeApi;
33//! use linode_rs::LinodeError;
34//!
35//! #[async_std::main]
36//! async fn main() -> Result<(), LinodeError> {
37//!     let api = LinodeApi::new("<KEY>");
38//!     let list = api.list_os_async().await?;
39//!     println!("OS {:#?}", list);
40//!     
41//!     let list = api.list_types_async().await?;
42//!     println!("TYPES {:#?}", list);
43//!     
44//!     let list = api.list_instances_async().await?;
45//!     println!("INSTANCES {:#?}", list);
46//!     Ok(())
47//! }
48//! ```
49//! ## Features
50//! * "default" - use nativetls
51//! * "default-rustls" - use rusttls
52//! * "blocking" - enable blocking api
53//! * "rustls" - enable rustls for reqwest
54//! * "nativetls" - add support for nativetls DEFAULT
55//! * "gzip" - enable gzip in reqwest
56//! * "brotli" - enable brotli in reqwest
57//! * "deflate" - enable deflate in reqwest
58
59mod api_error;
60mod builder;
61mod data;
62mod linode_error;
63
64use api_error::LinodeApiError;
65use data::linode_availability::LinodeAvailabilityListRoot;
66use data::linode_instance::LinodeInstanceListRoot;
67use data::linode_region::LinodeRegionListRoot;
68use data::PostEmpty;
69use data::{linode_os::LinodeOsListRoot, linode_types::LinodeTypeListRoot};
70use serde::Serialize;
71
72pub use builder::create_instance::LinodeCreateInstanceBuilder;
73pub use data::linode_availability::LinodeAvailability;
74pub use data::linode_instance::{
75    LinodeInstance, LinodeInstanceAlerts, LinodeInstanceBackupSchedule, LinodeInstanceBackups,
76    LinodeInstanceSpecs,
77};
78pub use data::linode_os::LinodeOs;
79pub use data::linode_region::{LinodeRegion, LinodeRegionResolver};
80pub use data::linode_types::{
81    LinodeType, LinodeTypeAddon, LinodeTypeAddons, LinodeTypePrice, LinodeTypeRegionPrice,
82};
83pub use linode_error::LinodeError;
84
85#[derive(Clone)]
86pub struct LinodeApi {
87    token: String,
88}
89
90impl<'a> LinodeApi {
91    pub fn new<S>(token: S) -> LinodeApi
92    where
93        S: Into<String>,
94    {
95        LinodeApi {
96            token: token.into(),
97        }
98    }
99
100    async fn get_async(&self, url: &str) -> Result<reqwest::Response, LinodeError> {
101        let client = reqwest::Client::new();
102        let resp = client
103            .get(url)
104            .bearer_auth(&self.token)
105            .send()
106            .await
107            .map_err(|e| LinodeError::Reqwest(e))?;
108        let status = resp.status();
109        if status.is_client_error() {
110            let result: LinodeApiError = resp.json().await?;
111            Err(LinodeError::Api(result))
112        } else {
113            Ok(resp.error_for_status()?)
114        }
115    }
116
117    #[cfg(feature = "blocking")]
118    fn get(&self, url: &str) -> Result<reqwest::blocking::Response, LinodeError> {
119        let client = reqwest::blocking::Client::new();
120        let resp = client.get(url).bearer_auth(&self.token).send()?;
121        let status = resp.status();
122        if status.is_client_error() {
123            let result: LinodeApiError = resp.json()?;
124            Err(LinodeError::Api(result))
125        } else {
126            Ok(resp.error_for_status()?)
127        }
128    }
129
130    async fn post_async<T>(&self, url: &str, json: T) -> Result<reqwest::Response, LinodeError>
131    where
132        T: Serialize + Sized,
133    {
134        let client = reqwest::Client::new();
135        let resp = client
136            .post(url)
137            .bearer_auth(&self.token)
138            .json(&json)
139            .send()
140            .await?;
141        let status = resp.status();
142        if status.is_client_error() {
143            let result: LinodeApiError = resp.json().await?;
144            Err(LinodeError::Api(result))
145        } else {
146            Ok(resp.error_for_status()?)
147        }
148    }
149
150    #[cfg(feature = "blocking")]
151    fn post<T>(&self, url: &str, json: T) -> Result<reqwest::blocking::Response, LinodeError>
152    where
153        T: Serialize + Sized,
154    {
155        let client = reqwest::blocking::Client::new();
156        let resp = client
157            .post(url)
158            .bearer_auth(&self.token)
159            .json(&json)
160            .send()?;
161        let status = resp.status();
162        if status.is_client_error() {
163            let result: LinodeApiError = resp.json()?;
164            Err(LinodeError::Api(result))
165        } else {
166            Ok(resp.error_for_status()?)
167        }
168    }
169
170    async fn delete_async(&self, url: &str) -> Result<reqwest::Response, LinodeError> {
171        let client = reqwest::Client::new();
172        let resp = client.delete(url).bearer_auth(&self.token).send().await?;
173        let status = resp.status();
174        if status.is_client_error() {
175            let result: LinodeApiError = resp.json().await?;
176            Err(LinodeError::Api(result))
177        } else {
178            Ok(resp.error_for_status()?)
179        }
180    }
181
182    #[cfg(feature = "blocking")]
183    fn delete(&self, url: &str) -> Result<reqwest::blocking::Response, LinodeError> {
184        let client = reqwest::blocking::Client::new();
185        let resp = client.delete(url).bearer_auth(&self.token).send()?;
186        let status = resp.status();
187        if status.is_client_error() {
188            let result: LinodeApiError = resp.json()?;
189            Err(LinodeError::Api(result))
190        } else {
191            Ok(resp.error_for_status()?)
192        }
193    }
194
195    pub async fn list_os_async(&self) -> Result<Vec<LinodeOs>, LinodeError> {
196        let mut list = vec![];
197        let mut page = 1;
198        loop {
199            let result = self
200                .get_async(&format!(
201                    "https://api.linode.com/v4/images?page={page}",
202                    page = page
203                ))
204                .await?
205                .json::<LinodeOsListRoot>()
206                .await?;
207            if result.data.len() > 0 {
208                list.extend(result.data.into_iter());
209            }
210            page += 1;
211            if page > result.pages {
212                break;
213            }
214        }
215        Ok(list)
216    }
217
218    #[cfg(feature = "blocking")]
219    pub fn list_os(&self) -> Result<Vec<LinodeOs>, LinodeError> {
220        let mut list = vec![];
221        let mut page = 1;
222        loop {
223            let result = self
224                .get(&format!(
225                    "https://api.linode.com/v4/images?page={page}",
226                    page = page
227                ))?
228                .json::<LinodeOsListRoot>()?;
229            if result.data.len() > 0 {
230                list.extend(result.data.into_iter());
231            }
232            page += 1;
233            if page > result.pages {
234                break;
235            }
236        }
237        Ok(list)
238    }
239
240    pub async fn list_types_async(&self) -> Result<Vec<LinodeType>, LinodeError> {
241        let mut list = vec![];
242        let mut page = 1;
243        loop {
244            let result = self
245                .get_async(&format!(
246                    "https://api.linode.com/v4/linode/types?page={page}",
247                    page = page
248                ))
249                .await?
250                .json::<LinodeTypeListRoot>()
251                .await?;
252            if result.data.len() > 0 {
253                list.extend(result.data.into_iter());
254            }
255            page += 1;
256            if page > result.pages {
257                break;
258            }
259        }
260        Ok(list)
261    }
262
263    #[cfg(feature = "blocking")]
264    pub fn list_types(&self) -> Result<Vec<LinodeType>, LinodeError> {
265        let mut list = vec![];
266        let mut page = 1;
267        loop {
268            let result = self
269                .get(&format!(
270                    "https://api.linode.com/v4/linode/types?page={page}",
271                    page = page
272                ))?
273                .json::<LinodeTypeListRoot>()?;
274            if result.data.len() > 0 {
275                list.extend(result.data.into_iter());
276            }
277            page += 1;
278            if page > result.pages {
279                break;
280            }
281        }
282        Ok(list)
283    }
284
285    pub async fn list_regions_async(&self) -> Result<Vec<LinodeRegion>, LinodeError> {
286        let mut list = vec![];
287        let mut page = 1;
288        loop {
289            let result = self
290                .get_async(&format!(
291                    "https://api.linode.com/v4/regions?page={page}",
292                    page = page
293                ))
294                .await?
295                .json::<LinodeRegionListRoot>()
296                .await?;
297            if result.data.len() > 0 {
298                list.extend(result.data.into_iter());
299            }
300            page += 1;
301            if page > result.pages {
302                break;
303            }
304        }
305        Ok(list)
306    }
307
308    #[cfg(feature = "blocking")]
309    pub fn list_regions(&self) -> Result<Vec<LinodeRegion>, LinodeError> {
310        let mut list = vec![];
311        let mut page = 1;
312        loop {
313            let result = self
314                .get(&format!(
315                    "https://api.linode.com/v4/regions?page={page}",
316                    page = page
317                ))?
318                .json::<LinodeRegionListRoot>()?;
319            if result.data.len() > 0 {
320                list.extend(result.data.into_iter());
321            }
322            page += 1;
323            if page > result.pages {
324                break;
325            }
326        }
327        Ok(list)
328    }
329
330    pub async fn list_availability_async(&self) -> Result<Vec<LinodeAvailability>, LinodeError> {
331        let mut list = vec![];
332        let mut page = 1;
333        loop {
334            let result = self
335                .get_async(&format!(
336                    "https://api.linode.com/v4/regions/availability?page={page}",
337                    page = page
338                ))
339                .await?
340                .json::<LinodeAvailabilityListRoot>()
341                .await?;
342            if result.data.len() > 0 {
343                list.extend(result.data.into_iter());
344            }
345            page += 1;
346            if page > result.pages {
347                break;
348            }
349        }
350        Ok(list)
351    }
352
353    #[cfg(feature = "blocking")]
354    pub fn list_availability(&self) -> Result<Vec<LinodeAvailability>, LinodeError> {
355        let mut list = vec![];
356        let mut page = 1;
357        loop {
358            let result = self
359                .get(&format!(
360                    "https://api.linode.com/v4/regions/availability?page={page}",
361                    page = page
362                ))?
363                .json::<LinodeAvailabilityListRoot>()?;
364            if result.data.len() > 0 {
365                list.extend(result.data.into_iter());
366            }
367            page += 1;
368            if page > result.pages {
369                break;
370            }
371        }
372        Ok(list)
373    }
374
375    pub async fn list_instances_async(&self) -> Result<Vec<LinodeInstance>, LinodeError> {
376        let mut list = vec![];
377        let mut page = 1;
378        loop {
379            let result = self
380                .get_async(&format!(
381                    "https://api.linode.com/v4/linode/instances?page={page}",
382                    page = page
383                ))
384                .await?
385                .json::<LinodeInstanceListRoot>()
386                .await?;
387            if result.data.len() > 0 {
388                list.extend(result.data.into_iter());
389            }
390            page += 1;
391            if page > result.pages {
392                break;
393            }
394        }
395        Ok(list)
396    }
397
398    #[cfg(feature = "blocking")]
399    pub fn list_instances(&self) -> Result<Vec<LinodeInstance>, LinodeError> {
400        let mut list = vec![];
401        let mut page = 1;
402        loop {
403            let result = self
404                .get(&format!(
405                    "https://api.linode.com/v4/linode/instances?page={page}",
406                    page = page
407                ))?
408                .json::<LinodeInstanceListRoot>()?;
409            if result.data.len() > 0 {
410                list.extend(result.data.into_iter());
411            }
412            page += 1;
413            if page > result.pages {
414                break;
415            }
416        }
417        Ok(list)
418    }
419
420    pub async fn get_instance_async(
421        &self,
422        instance_id: u64,
423    ) -> Result<LinodeInstance, LinodeError> {
424        let instance = self
425            .get_async(&format!(
426                "https://api.linode.com/v4/linode/instances/{instance_id}",
427                instance_id = instance_id,
428            ))
429            .await?
430            .json::<LinodeInstance>()
431            .await?;
432        Ok(instance)
433    }
434
435    #[cfg(feature = "blocking")]
436    pub fn get_instance(&self, instance_id: u64) -> Result<LinodeInstance, LinodeError> {
437        let instance = self
438            .get(&format!(
439                "https://api.linode.com/v4/linode/instances/{instance_id}",
440                instance_id = instance_id,
441            ))?
442            .json::<LinodeInstance>()?;
443        Ok(instance)
444    }
445
446    pub async fn delete_instance_async(&self, instance_id: u64) -> Result<(), LinodeError> {
447        self.delete_async(&format!(
448            "https://api.linode.com/v4/linode/instances/{instance_id}",
449            instance_id = instance_id,
450        ))
451        .await?
452        .error_for_status()?;
453        Ok(())
454    }
455
456    #[cfg(feature = "blocking")]
457    pub fn delete_instance(&self, instance_id: u64) -> Result<(), LinodeError> {
458        self.delete(&format!(
459            "https://api.linode.com/v4/linode/instances/{instance_id}",
460            instance_id = instance_id,
461        ))?
462        .error_for_status()?;
463        Ok(())
464    }
465
466    pub async fn shutdown_instance_async(&self, instance_id: u64) -> Result<(), LinodeError> {
467        self.post_async(
468            &format!(
469                "https://api.linode.com/v4/linode/instances/{instance_id}/shutdown",
470                instance_id = instance_id,
471            ),
472            PostEmpty {},
473        )
474        .await?
475        .error_for_status()?;
476        Ok(())
477    }
478
479    #[cfg(feature = "blocking")]
480    pub fn shutdown_instance(&self, instance_id: u64) -> Result<(), LinodeError> {
481        self.post(
482            &format!(
483                "https://api.linode.com/v4/linode/instances/{instance_id}/shutdown",
484                instance_id = instance_id,
485            ),
486            PostEmpty {},
487        )?
488        .error_for_status()?;
489        Ok(())
490    }
491
492    pub fn create_instance(&self, region: &str, ltype: &str) -> LinodeCreateInstanceBuilder {
493        LinodeCreateInstanceBuilder::new(self.clone(), region, ltype)
494    }
495}