1mod 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}