scaleway_rs/
lib.rs

1//! Client library for the <https://www.scaleway.com/> API which
2//! is documented at <https://www.scaleway.com/en/developers/api/>
3//!
4//! # Example blocking
5//! It needs to have the feature "blocking" enabled.
6//! ```toml
7//! scaleway-rs = { version = "*", features = ["blocking"] }
8//! ```
9//! ```ignore
10//! use scaleway_rs::ScalewayApi;
11//! use scaleway_rs::ScalewayError;
12//!
13//! fn main() -> Result<(), ScalewayError> {
14//!     let region = "fr-par-2";
15//!     let api = ScalewayApi::new("<KEY>");
16//!     
17//!     let types = api.get_server_types(region)?;
18//!     println!("SERVERTYPES: {:#?}", types);
19//!     
20//!     let images = api.list_images(region).run()?;
21//!     println!("IMAGES: {:#?}", images);
22//!     
23//!     let instances = api.list_instances(region).order("creation_date_asc").run()?;
24//!     println!("INSTANCES: {:#?}", instances);
25//!     Ok(())
26//! }
27//! ```
28//!
29//! # Example async
30//! ```toml
31//! scaleway-rs = { version = "*" }
32//! ```
33//! ```no_run
34//! use scaleway_rs::ScalewayApi;
35//! use scaleway_rs::ScalewayError;
36//!
37//! #[async_std::main]
38//! async fn main() -> Result<(), ScalewayError> {
39//!     let region = "fr-par-2";
40//!     let api = ScalewayApi::new("<KEY>");
41//!     
42//!     let types = api.get_server_types_async(region).await?;
43//!     println!("SERVERTYPES: {:#?}", types);
44//!     
45//!     let images = api.list_images(region).run_async().await?;
46//!     println!("IMAGES: {:#?}", images);
47//!     
48//!     let instances = api
49//!         .list_instances(region)
50//!         .order("creation_date_asc")
51//!         .run_async()
52//!         .await?;
53//!     println!("INSTANCES: {:#?}", instances);
54//!     Ok(())
55//! }
56//! ```
57//! ## Features
58//! * "default" - use nativetls
59//! * "default-rustls" - use rusttls
60//! * "blocking" - enable blocking api
61//! * "rustls" - enable rustls for reqwest
62//! * "nativetls" - add support for nativetls DEFAULT
63//! * "gzip" - enable gzip in reqwest
64//! * "brotli" - enable brotli in reqwest
65//! * "deflate" - enable deflate in reqwest
66
67mod api_error;
68mod builder;
69mod data;
70mod scaleway_error;
71
72pub use api_error::ScalewayApiError;
73use builder::list_marketplace_image_versions_builder::ScalewayListMarketplaceImageVersionsBuilder;
74use data::availability::ScalewayAvailabilityRoot;
75use data::instance::ScalewayInstanceRoot;
76use data::server_type::ScalewayServerTypeRoot;
77use reqwest::header::CONTENT_TYPE;
78use serde::Serialize;
79use serde_json::json;
80use std::collections::HashMap;
81
82pub use builder::{
83    create_instance_builder::ScalewayCreateInstanceBuilder,
84    list_instance_builder::ScalewayListInstanceBuilder,
85    list_instance_images_builder::ScalewayListInstanceImagesBuilder,
86    list_marketplace_images_builder::ScalewayListMarketplaceImagesBuilder,
87    list_marketplace_local_images_builder::LocalImageListType,
88    list_marketplace_local_images_builder::ScalewayListMarketplaceLocalImagesBuilder,
89};
90pub use data::image::{
91    ScalewayImage, ScalewayImageBootscript, ScalewayImageExtraVolume,
92    ScalewayImageExtraVolumeServer, ScalewayImageExtraVolumes, ScalewayImageRootVolume,
93};
94pub use data::instance::{
95    ScalewayInstance, ScalewayInstanceLocation, ScalewayIpv6, ScalewayMaintenance,
96    ScalewayPlacementGroup, ScalewayPrivateNic, ScalewayPublicIP, ScalewaySecurityGroup,
97};
98pub use data::marketplace_image::ScalewayMarketplaceImage;
99pub use data::server_type::ServerType;
100pub use data::user_data::ScalewayUserData;
101pub use data::user_data::ScalewayUserDataKeyList;
102pub use scaleway_error::ScalewayError;
103
104#[derive(Clone)]
105pub struct ScalewayApi {
106    secret_key: String,
107}
108
109impl<'a> ScalewayApi {
110    pub fn new<S>(secret_key: S) -> ScalewayApi
111    where
112        S: Into<String>,
113    {
114        ScalewayApi {
115            secret_key: secret_key.into(),
116        }
117    }
118
119    async fn get_async(
120        &self,
121        url: &str,
122        query: Vec<(&'static str, String)>,
123    ) -> Result<reqwest::Response, ScalewayError> {
124        let client = reqwest::Client::new();
125        let resp = client
126            .get(url)
127            .header("X-Auth-Token", &self.secret_key)
128            .query(&query)
129            .send()
130            .await
131            .map_err(|e| ScalewayError::Reqwest(e))?;
132        let status = resp.status();
133        if status.is_client_error() {
134            let result: ScalewayApiError = resp.json().await?;
135            Err(ScalewayError::Api(result))
136        } else {
137            Ok(resp.error_for_status()?)
138        }
139    }
140
141    #[cfg(feature = "blocking")]
142    fn get(
143        &self,
144        url: &str,
145        query: Vec<(&'static str, String)>,
146    ) -> Result<reqwest::blocking::Response, ScalewayError> {
147        let client = reqwest::blocking::Client::new();
148        let resp = client
149            .get(url)
150            .header("X-Auth-Token", &self.secret_key)
151            .query(&query)
152            .send()?;
153        let status = resp.status();
154        if status.is_client_error() {
155            let result: ScalewayApiError = resp.json()?;
156            Err(ScalewayError::Api(result))
157        } else {
158            Ok(resp.error_for_status()?)
159        }
160    }
161
162    async fn post_async<T>(&self, url: &str, json: T) -> Result<reqwest::Response, ScalewayError>
163    where
164        T: Serialize + Sized,
165    {
166        let client = reqwest::Client::new();
167        let resp = client
168            .post(url)
169            .header("X-Auth-Token", &self.secret_key)
170            .json(&json)
171            .send()
172            .await?;
173        let status = resp.status();
174        if status.is_client_error() {
175            let result: ScalewayApiError = resp.json().await?;
176            Err(ScalewayError::Api(result))
177        } else {
178            Ok(resp.error_for_status()?)
179        }
180    }
181
182    #[cfg(feature = "blocking")]
183    fn post<T>(&self, url: &str, json: T) -> Result<reqwest::blocking::Response, ScalewayError>
184    where
185        T: Serialize + Sized,
186    {
187        let client = reqwest::blocking::Client::new();
188        let resp = client
189            .post(url)
190            .header("X-Auth-Token", &self.secret_key)
191            .json(&json)
192            .send()?;
193        let status = resp.status();
194        if status.is_client_error() {
195            let result: ScalewayApiError = resp.json()?;
196            Err(ScalewayError::Api(result))
197        } else {
198            Ok(resp.error_for_status()?)
199        }
200    }
201
202    async fn patch_async(
203        &self,
204        url: &str,
205        content: &str,
206    ) -> Result<reqwest::Response, ScalewayError> {
207        let client = reqwest::Client::new();
208        let resp = client
209            .patch(url)
210            .header(CONTENT_TYPE, "text/plain")
211            .header("X-Auth-Token", &self.secret_key)
212            .body(content.to_string())
213            .send()
214            .await?;
215        let status = resp.status();
216        if status.is_client_error() {
217            let result: ScalewayApiError = resp.json().await?;
218            Err(ScalewayError::Api(result))
219        } else {
220            Ok(resp.error_for_status()?)
221        }
222    }
223
224    #[cfg(feature = "blocking")]
225    fn patch(
226        &self,
227        url: &str,
228        content: &str,
229    ) -> Result<reqwest::blocking::Response, ScalewayError> {
230        let client = reqwest::blocking::Client::new();
231        let resp = client
232            .patch(url)
233            .header(CONTENT_TYPE, "text/plain")
234            .header("X-Auth-Token", &self.secret_key)
235            .body(content.to_string())
236            .send()?;
237        let status = resp.status();
238        if status.is_client_error() {
239            let result: ScalewayApiError = resp.json()?;
240            Err(ScalewayError::Api(result))
241        } else {
242            Ok(resp.error_for_status()?)
243        }
244    }
245
246    async fn delete_async(&self, url: &str) -> Result<reqwest::Response, ScalewayError> {
247        let client = reqwest::Client::new();
248        let resp = client
249            .delete(url)
250            .header("X-Auth-Token", &self.secret_key)
251            .send()
252            .await?;
253        let status = resp.status();
254        if status.is_client_error() {
255            let result: ScalewayApiError = resp.json().await?;
256            Err(ScalewayError::Api(result))
257        } else {
258            Ok(resp.error_for_status()?)
259        }
260    }
261
262    #[cfg(feature = "blocking")]
263    fn delete(&self, url: &str) -> Result<reqwest::blocking::Response, ScalewayError> {
264        let client = reqwest::blocking::Client::new();
265        let resp = client
266            .delete(url)
267            .header("X-Auth-Token", &self.secret_key)
268            .send()?;
269        let status = resp.status();
270        if status.is_client_error() {
271            let result: ScalewayApiError = resp.json()?;
272            Err(ScalewayError::Api(result))
273        } else {
274            Ok(resp.error_for_status()?)
275        }
276    }
277
278    pub fn az_list() -> Vec<&'static str> {
279        vec![
280            "fr-par-1", "fr-par-2", "fr-par-3", "nl-ams-1", "nl-ams-2", "pl-waw-1", "pl-waw-2",
281        ]
282    }
283
284    #[cfg(feature = "blocking")]
285    pub fn get_server_types(&self, zone: &str) -> Result<Vec<ServerType>, ScalewayError> {
286        let types: Vec<ServerType> = self
287            .get(
288                &format!(
289                    "https://api.scaleway.com/instance/v1/zones/{zone}/products/servers",
290                    zone = zone
291                ),
292                vec![],
293            )?
294            .json::<ScalewayServerTypeRoot>()?
295            .servers
296            .servers
297            .into_iter()
298            .map(|(id, item)| ServerType {
299                id,
300                location: zone.to_string(),
301                alt_names: item.alt_names,
302                arch: item.arch,
303                ncpus: item.ncpus,
304                ram: item.ram,
305                gpu: item.gpu,
306                baremetal: item.baremetal,
307                monthly_price: item.monthly_price,
308                hourly_price: item.hourly_price,
309                network: item.network,
310            })
311            .collect();
312        Ok(types)
313    }
314
315    pub async fn get_server_types_async(
316        &self,
317        zone: &str,
318    ) -> Result<Vec<ServerType>, ScalewayError> {
319        let types: Vec<ServerType> = self
320            .get_async(
321                &format!(
322                    "https://api.scaleway.com/instance/v1/zones/{zone}/products/servers",
323                    zone = zone
324                ),
325                vec![],
326            )
327            .await?
328            .json::<ScalewayServerTypeRoot>()
329            .await?
330            .servers
331            .servers
332            .into_iter()
333            .map(|(id, item)| ServerType {
334                id,
335                location: zone.to_string(),
336                alt_names: item.alt_names,
337                arch: item.arch,
338                ncpus: item.ncpus,
339                ram: item.ram,
340                gpu: item.gpu,
341                baremetal: item.baremetal,
342                monthly_price: item.monthly_price,
343                hourly_price: item.hourly_price,
344                network: item.network,
345            })
346            .collect();
347        Ok(types)
348    }
349
350    pub fn list_images(&self, zone: &str) -> ScalewayListInstanceImagesBuilder {
351        ScalewayListInstanceImagesBuilder::new(self.clone(), zone)
352    }
353
354    pub fn list_instances(&self, zone: &str) -> ScalewayListInstanceBuilder {
355        ScalewayListInstanceBuilder::new(self.clone(), zone)
356    }
357
358    pub fn list_marketplace_instances(&self) -> ScalewayListMarketplaceImagesBuilder {
359        ScalewayListMarketplaceImagesBuilder::new(self.clone())
360    }
361
362    pub fn list_marketplace_instance_versions(
363        &self,
364        image_id: &str,
365    ) -> ScalewayListMarketplaceImageVersionsBuilder {
366        ScalewayListMarketplaceImageVersionsBuilder::new(self.clone(), image_id)
367    }
368
369    pub fn list_marketplace_local_images(
370        &self,
371        list_type: LocalImageListType,
372    ) -> ScalewayListMarketplaceLocalImagesBuilder {
373        ScalewayListMarketplaceLocalImagesBuilder::new(self.clone(), list_type)
374    }
375
376    pub fn create_instance(
377        &self,
378        zone: &str,
379        name: &str,
380        commercial_type: &str,
381    ) -> ScalewayCreateInstanceBuilder {
382        ScalewayCreateInstanceBuilder::new(self.clone(), zone, name, commercial_type)
383    }
384
385    #[cfg(feature = "blocking")]
386    pub fn get_instance(
387        &self,
388        zone: &str,
389        server_id: &str,
390    ) -> Result<ScalewayInstance, ScalewayError> {
391        Ok(self
392            .get(
393                &format!(
394                    "https://api.scaleway.com/instance/v1/zones/{zone}/servers/{server_id}",
395                    zone = zone,
396                    server_id = server_id
397                ),
398                vec![],
399            )?
400            .json::<ScalewayInstanceRoot>()?
401            .server)
402    }
403
404    pub async fn get_instance_async(
405        &self,
406        zone: &str,
407        server_id: &str,
408    ) -> Result<ScalewayInstance, ScalewayError> {
409        Ok(self
410            .get_async(
411                &format!(
412                    "https://api.scaleway.com/instance/v1/zones/{zone}/servers/{server_id}",
413                    zone = zone,
414                    server_id = server_id
415                ),
416                vec![],
417            )
418            .await?
419            .json::<ScalewayInstanceRoot>()
420            .await?
421            .server)
422    }
423
424    #[cfg(feature = "blocking")]
425    pub fn delete_instance(&self, zone: &str, server_id: &str) -> Result<(), ScalewayError> {
426        self.delete(&format!(
427            "https://api.scaleway.com/instance/v1/zones/{zone}/servers/{server_id}",
428            zone = zone,
429            server_id = server_id
430        ))?
431        .error_for_status()?;
432        Ok(())
433    }
434
435    pub async fn delete_instance_async(
436        &self,
437        zone: &str,
438        server_id: &str,
439    ) -> Result<(), ScalewayError> {
440        self.delete_async(&format!(
441            "https://api.scaleway.com/instance/v1/zones/{zone}/servers/{server_id}",
442            zone = zone,
443            server_id = server_id
444        ))
445        .await?
446        .error_for_status()?;
447        Ok(())
448    }
449
450    #[cfg(feature = "blocking")]
451    pub fn perform_instance_action(
452        &self,
453        zone: &str,
454        server_id: &str,
455        action: &str,
456    ) -> Result<(), ScalewayError> {
457        self.post(
458            &format!(
459                "https://api.scaleway.com/instance/v1/zones/{zone}/servers/{server_id}/action",
460                zone = zone,
461                server_id = server_id
462            ),
463            json!({"action": action}),
464        )?
465        .error_for_status()?;
466        Ok(())
467    }
468
469    pub async fn perform_instance_action_async(
470        &self,
471        zone: &str,
472        server_id: &str,
473        action: &str,
474    ) -> Result<(), ScalewayError> {
475        self.post_async(
476            &format!(
477                "https://api.scaleway.com/instance/v1/zones/{zone}/servers/{server_id}/action",
478                zone = zone,
479                server_id = server_id
480            ),
481            json!({"action": action}),
482        )
483        .await?
484        .error_for_status()?;
485        Ok(())
486    }
487
488    #[cfg(feature = "blocking")]
489    pub fn list_availability(&self, zone: &str) -> Result<HashMap<String, bool>, ScalewayError> {
490        let servers = self
491            .get(
492                &format!(
493                "https://api.scaleway.com/instance/v1/zones/{zone}/products/servers/availability",
494                zone = zone
495            ),
496                vec![],
497            )?
498            .json::<ScalewayAvailabilityRoot>()?
499            .servers
500            .servers
501            .into_iter()
502            .map(|(id, available)| (id, available.availability == "available"))
503            .collect();
504        Ok(servers)
505    }
506
507    pub async fn list_availability_async(
508        &self,
509        zone: &str,
510    ) -> Result<HashMap<String, bool>, ScalewayError> {
511        let servers = self
512            .get_async(
513                &format!(
514                "https://api.scaleway.com/instance/v1/zones/{zone}/products/servers/availability",
515                zone = zone
516            ),
517                vec![],
518            )
519            .await?
520            .json::<ScalewayAvailabilityRoot>()
521            .await?
522            .servers
523            .servers
524            .into_iter()
525            .map(|(id, available)| (id, available.availability == "available"))
526            .collect();
527        Ok(servers)
528    }
529
530    #[cfg(feature = "blocking")]
531    pub fn list_userdata_keys(
532        &self,
533        zone: &str,
534        machine_id: &str,
535    ) -> Result<Vec<String>, ScalewayError> {
536        let user_data = self
537            .get(
538                &format!(
539                    "https://api.scaleway.com/instance/v1/zones/{zone}/servers/{server_id}/user_data",
540                    zone = zone,
541                    server_id = machine_id,
542            ),
543                vec![],
544            )?
545            .json::<ScalewayUserDataKeyList>()?
546            .user_data;
547        Ok(user_data)
548    }
549
550    pub async fn list_userdata_keys_async(
551        &self,
552        zone: &str,
553        machine_id: &str,
554    ) -> Result<Vec<String>, ScalewayError> {
555        let user_data = self
556            .get_async(
557                &format!(
558                "https://api.scaleway.com/instance/v1/zones/{zone}/servers/{server_id}/user_data",
559                zone = zone,
560                server_id = machine_id,
561            ),
562                vec![],
563            )
564            .await?
565            .json::<ScalewayUserDataKeyList>()
566            .await?
567            .user_data;
568        Ok(user_data)
569    }
570
571    #[cfg(feature = "blocking")]
572    pub fn get_userdata(
573        &self,
574        zone: &str,
575        machine_id: &str,
576        key: &str,
577    ) -> Result<ScalewayUserData, ScalewayError> {
578        let servers = self
579            .get(
580                &format!(
581                    "https://api.scaleway.com/instance/v1/zones/{zone}/servers/{server_id}/user_data/{key}",
582                    zone = zone,
583                    server_id = machine_id,
584                    key = key,
585            ),
586                vec![],
587            )?
588            .json::<ScalewayUserData>()?;
589        Ok(servers)
590    }
591
592    pub async fn get_userdata_async(
593        &self,
594        zone: &str,
595        machine_id: &str,
596        key: &str,
597    ) -> Result<ScalewayUserData, ScalewayError> {
598        let user_data = self
599            .get_async(
600                &format!(
601                "https://api.scaleway.com/instance/v1/zones/{zone}/servers/{server_id}/user_data/{key}",
602                zone = zone,
603                server_id = machine_id,
604                key = key,
605            ),
606                vec![],
607            )
608            .await?
609            .json::<ScalewayUserData>()
610            .await?;
611        Ok(user_data)
612    }
613
614    #[cfg(feature = "blocking")]
615    pub fn set_userdata(
616        &self,
617        zone: &str,
618        machine_id: &str,
619        key: &str,
620        value: &str,
621    ) -> Result<(), ScalewayError> {
622        self
623            .patch(
624                &format!(
625                    "https://api.scaleway.com/instance/v1/zones/{zone}/servers/{server_id}/user_data/{key}",
626                    zone = zone,
627                    server_id = machine_id,
628                    key = key,
629            ), value)?
630            .error_for_status()?;
631        Ok(())
632    }
633
634    pub async fn set_userdata_async(
635        &self,
636        zone: &str,
637        machine_id: &str,
638        key: &str,
639        value: &str,
640    ) -> Result<(), ScalewayError> {
641        self
642            .patch_async(
643                &format!(
644                "https://api.scaleway.com/instance/v1/zones/{zone}/servers/{server_id}/user_data/{key}",
645                zone = zone,
646                server_id = machine_id,
647                key = key,
648            ), value)
649            .await?.error_for_status()?;
650        Ok(())
651    }
652
653    #[cfg(feature = "blocking")]
654    pub fn delete_userdata(
655        &self,
656        zone: &str,
657        machine_id: &str,
658        key: &str,
659    ) -> Result<(), ScalewayError> {
660        self.delete(&format!(
661            "https://api.scaleway.com/instance/v1/zones/{zone}/servers/{server_id}/user_data/{key}",
662            zone = zone,
663            server_id = machine_id,
664            key = key,
665        ))?
666        .error_for_status()?;
667        Ok(())
668    }
669
670    pub async fn delete_userdata_async(
671        &self,
672        zone: &str,
673        machine_id: &str,
674        key: &str,
675    ) -> Result<(), ScalewayError> {
676        self.delete_async(&format!(
677            "https://api.scaleway.com/instance/v1/zones/{zone}/servers/{server_id}/user_data/{key}",
678            zone = zone,
679            server_id = machine_id,
680            key = key,
681        ))
682        .await?
683        .error_for_status()?;
684        Ok(())
685    }
686
687    #[cfg(feature = "blocking")]
688    pub fn delete_volume(&self, zone: &str, volume_id: &str) -> Result<(), ScalewayError> {
689        self.delete(&format!(
690            "https://api.scaleway.com/instance/v1/zones/{zone}/volumes/{volume_id}",
691            zone = zone,
692            volume_id = volume_id,
693        ))?
694        .error_for_status()?;
695        Ok(())
696    }
697
698    pub async fn delete_volume_async(
699        &self,
700        zone: &str,
701        volume_id: &str,
702    ) -> Result<(), ScalewayError> {
703        self.delete_async(&format!(
704            "https://api.scaleway.com/instance/v1/zones/{zone}/volumes/{volume_id}",
705            zone = zone,
706            volume_id = volume_id,
707        ))
708        .await?
709        .error_for_status()?;
710        Ok(())
711    }
712
713    #[cfg(feature = "blocking")]
714    pub fn delete_securitygroup(
715        &self,
716        zone: &str,
717        security_group_id: &str,
718    ) -> Result<(), ScalewayError> {
719        self.delete(&format!(
720            "https://api.scaleway.com/instance/v1/zones/{zone}/security_groups/{security_group_id}",
721            zone = zone,
722            security_group_id = security_group_id,
723        ))?
724        .error_for_status()?;
725        Ok(())
726    }
727
728    pub async fn delete_securitygroup_async(
729        &self,
730        zone: &str,
731        security_group_id: &str,
732    ) -> Result<(), ScalewayError> {
733        self.delete_async(&format!(
734            "https://api.scaleway.com/instance/v1/zones/{zone}/security_groups/{security_group_id}",
735            zone = zone,
736            security_group_id = security_group_id,
737        ))
738        .await?
739        .error_for_status()?;
740        Ok(())
741    }
742}