oxide_api/lib.rs
1//! A fully generated, opinionated API client library for Oxide.
2//!
3//! [](https://docs.rs/oxide-api)
4//!
5//! ## API Details
6//!
7//! API for interacting with the Oxide control plane
8//!
9//!
10//!
11//! ### Contact
12//!
13//!
14//! | url | email |
15//! |----|----|
16//! | <https://oxide.computer> | api@oxide.computer |
17//!
18//!
19//!
20//! ## Client Details
21//!
22//! This client is generated from the [Oxide OpenAPI
23//! specs](https://github.com/oxidecomputer/omicron) based on API spec version `0.0.1`. This way it will remain
24//! up to date as features are added. The documentation for the crate is generated
25//! along with the code to make this library easy to use.
26//!
27//!
28//! To install the library, add the following to your `Cargo.toml` file.
29//!
30//! ```toml
31//! [dependencies]
32//! oxide-api = "0.1.0-rc.41"
33//! ```
34//!
35//! ## Basic example
36//!
37//! Typical use will require intializing a `Client`. This requires
38//! a user agent string and set of credentials.
39//!
40//! ```
41//! use oxide_api::Client;
42//!
43//! let oxide = Client::new(String::from("api-key"), String::from("host"));
44//! ```
45//!
46//! Alternatively, the library can search for most of the variables required for
47//! the client in the environment:
48//!
49//! - `OXIDE_TOKEN`
50//! - `OXIDE_HOST`
51//!
52//! And then you can create a client from the environment.
53//!
54//! ```
55//! use oxide_api::Client;
56//!
57//! let oxide = Client::new_from_env();
58//! ```
59#![feature(derive_default_enum)]
60#![allow(clippy::too_many_arguments)]
61#![allow(clippy::nonstandard_macro_braces)]
62#![allow(clippy::large_enum_variant)]
63#![allow(clippy::tabs_in_doc_comments)]
64#![allow(missing_docs)]
65#![cfg_attr(docsrs, feature(doc_cfg))]
66
67/// Virtual disks are used to store instance-local data which includes the operating system.
68///
69///FROM: http://oxide.computer/docs/#xxx
70pub mod disks;
71/// TODO operations that will not ship to customers.
72///
73///FROM: http://oxide.computer/docs/#xxx
74pub mod hidden;
75/// Images are read-only Virtual Disks that may be used to boot Virtual Machines.
76///
77///FROM: http://oxide.computer/docs/#xxx
78pub mod images;
79/// Images are read-only Virtual Disks that may be used to boot Virtual Machines. These images are scoped globally.
80///
81///FROM: http://oxide.computer/docs/#xxx
82pub mod images_global;
83/// Virtual machine instances are the basic unit of computation. These operations are used for provisioning, controlling, and destroying instances.
84///
85///FROM: http://oxide.computer/docs/#xxx
86pub mod instances;
87/// IP Pools contain external IP addresses that can be assigned to virtual machine Instances.
88///
89///FROM: http://oxide.computer/docs/#xxx
90pub mod ip_pools;
91/// Authentication endpoints.
92///
93///FROM: http://oxide.computer/docs/#xxx
94pub mod login;
95/// Metrics provide insight into the operation of the Oxide deployment. These include telemetry on hardware and software components that can be used to understand the current state as well as to diagnose issues.
96///
97///FROM: http://oxide.computer/docs/#xxx
98pub mod metrics;
99/// Organizations represent a subset of users and projects in an Oxide deployment.
100///
101///FROM: http://oxide.computer/docs/#xxx
102pub mod organizations;
103/// System-wide IAM policy.
104///
105///FROM: http://oxide.computer/docs/#xxx
106pub mod policy;
107/// Projects are a grouping of associated resources such as instances and disks within an organization for purposes of billing and access control.
108///
109///FROM: http://oxide.computer/docs/#xxx
110pub mod projects;
111/// These operations pertain to hardware inventory and management. Racks are the unit of expansion of an Oxide deployment. Racks are in turn composed of sleds, switches, power supplies, and a cabled backplane.
112///
113///FROM: http://oxide.computer/docs/#xxx
114pub mod racks;
115/// Roles are a component of Identity and Access Management (IAM) that allow a user or agent account access to additional permissions.
116///
117///FROM: http://oxide.computer/docs/#xxx
118pub mod roles;
119/// Routers direct the flow of network traffic into, out of, and within a VPC via routes.
120///
121///FROM: http://oxide.computer/docs/#xxx
122pub mod routers;
123/// Routes define router policy.
124///
125///FROM: http://oxide.computer/docs/#xxx
126pub mod routes;
127/// Sagas are the abstraction used to represent multi-step operations within the Oxide deployment. These operations can be used to query saga status and report errors.
128///
129///FROM: http://oxide.computer/docs/#xxx
130pub mod sagas;
131/// Silos represent a logical partition of users and resources.
132///
133///FROM: http://oxide.computer/docs/#xxx
134pub mod silos;
135/// This tag should be moved into hardware.
136///
137///FROM: http://oxide.computer/docs/#xxx
138pub mod sleds;
139/// Snapshots of Virtual Disks at a particular point in time.
140///
141///FROM: http://oxide.computer/docs/#xxx
142pub mod snapshots;
143/// Public SSH keys for an individual user.
144///
145///FROM: http://oxide.computer/docs/#xxx
146pub mod sshkeys;
147/// This tag should be moved into a generic network tag.
148///
149///FROM: http://oxide.computer/docs/#xxx
150pub mod subnets;
151/// Internal system information.
152///
153///FROM: http://oxide.computer/docs/#xxx
154pub mod system;
155#[cfg(test)]
156mod tests;
157pub mod types;
158/// This tag should be moved into a operations tag.
159///
160///FROM: http://oxide.computer/docs/#xxx
161pub mod updates;
162#[doc(hidden)]
163pub mod utils;
164/// A Virtual Private Cloud (VPC) is an isolated network environment that should probaby be moved into a more generic networking tag.
165///
166///FROM: http://oxide.computer/docs/#xxx
167pub mod vpcs;
168
169use anyhow::{anyhow, Error, Result};
170
171mod progenitor_support {
172 use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS};
173
174 const PATH_SET: &AsciiSet = &CONTROLS
175 .add(b' ')
176 .add(b'"')
177 .add(b'#')
178 .add(b'<')
179 .add(b'>')
180 .add(b'?')
181 .add(b'`')
182 .add(b'{')
183 .add(b'}');
184
185 #[allow(dead_code)]
186 pub(crate) fn encode_path(pc: &str) -> String {
187 utf8_percent_encode(pc, PATH_SET).to_string()
188 }
189}
190
191use std::env;
192
193/// Entrypoint for interacting with the API client.
194#[derive(Clone)]
195pub struct Client {
196 host: String,
197 token: String,
198
199 client: reqwest::Client,
200}
201
202impl Client {
203 /// Create a new Client struct. It takes a type that can convert into
204 /// an &str (`String` or `Vec<u8>` for example). As long as the function is
205 /// given a valid API key your requests will work.
206 pub fn new<T, H>(token: T, host: H) -> Self
207 where
208 T: ToString,
209 H: ToString,
210 {
211 let client = reqwest::Client::builder().build();
212 match client {
213 Ok(c) => Client {
214 host: host.to_string(),
215 token: token.to_string(),
216
217 client: c,
218 },
219 Err(e) => panic!("creating reqwest client failed: {:?}", e),
220 }
221 }
222
223 /// Create a new Client struct from environment variables: OXIDE_TOKEN and OXIDE_HOST.
224 pub fn new_from_env() -> Self {
225 let token = env::var("OXIDE_TOKEN").expect("must set OXIDE_TOKEN");
226 let host = env::var("OXIDE_HOST").expect("must set OXIDE_HOST");
227
228 Client::new(token, host)
229 }
230
231 async fn url_and_auth(&self, uri: &str) -> Result<(reqwest::Url, Option<String>)> {
232 let parsed_url = uri.parse::<reqwest::Url>();
233
234 let auth = format!("Bearer {}", self.token);
235 parsed_url.map(|u| (u, Some(auth))).map_err(Error::from)
236 }
237
238 pub async fn request_raw(
239 &self,
240 method: reqwest::Method,
241 uri: &str,
242 body: Option<reqwest::Body>,
243 ) -> Result<reqwest::RequestBuilder> {
244 let u = if uri.starts_with("https://") || uri.starts_with("http://") {
245 uri.to_string()
246 } else {
247 (self.host.clone() + uri).to_string()
248 };
249 let (url, auth) = self.url_and_auth(&u).await?;
250
251 let instance = <&Client>::clone(&self);
252
253 let mut req = instance.client.request(method.clone(), url);
254
255 // Set the default headers.
256 req = req.header(
257 reqwest::header::ACCEPT,
258 reqwest::header::HeaderValue::from_static("application/json"),
259 );
260 req = req.header(
261 reqwest::header::CONTENT_TYPE,
262 reqwest::header::HeaderValue::from_static("application/json"),
263 );
264
265 if let Some(auth_str) = auth {
266 req = req.header(http::header::AUTHORIZATION, &*auth_str);
267 }
268
269 if let Some(body) = body {
270 log::debug!(
271 "body: {:?}",
272 String::from_utf8(body.as_bytes().unwrap().to_vec()).unwrap()
273 );
274 req = req.body(body);
275 }
276 log::debug!("request: {:?}", &req);
277 Ok(req)
278 }
279
280 pub async fn response_raw(
281 &self,
282 method: reqwest::Method,
283 uri: &str,
284 body: Option<reqwest::Body>,
285 ) -> Result<reqwest::Response> {
286 let req = self.request_raw(method, uri, body).await?;
287 Ok(req.send().await?)
288 }
289
290 async fn request<Out>(
291 &self,
292 method: reqwest::Method,
293 uri: &str,
294 body: Option<reqwest::Body>,
295 ) -> Result<Out>
296 where
297 Out: serde::de::DeserializeOwned + 'static + Send,
298 {
299 let response = self.response_raw(method, uri, body).await?;
300
301 let status = response.status();
302
303 let response_body = response.bytes().await?;
304
305 if status.is_success() {
306 log::debug!(
307 "response payload {}",
308 String::from_utf8_lossy(&response_body)
309 );
310 let parsed_response = if status == http::StatusCode::NO_CONTENT
311 || std::any::TypeId::of::<Out>() == std::any::TypeId::of::<()>()
312 {
313 serde_json::from_str("null")
314 } else {
315 serde_json::from_slice::<Out>(&response_body)
316 };
317 parsed_response.map_err(Error::from)
318 } else {
319 let error: anyhow::Error = if response_body.is_empty() {
320 anyhow!("code: {}, empty response", status)
321 } else {
322 // Parse the error as the error type.
323 match serde_json::from_slice::<crate::types::ErrorResponse>(&response_body) {
324 Ok(resp) => {
325 let e: crate::types::Error = resp.into();
326 e.into()
327 }
328 Err(_) => {
329 anyhow!(
330 "code: {}, error: {:?}",
331 status,
332 String::from_utf8_lossy(&response_body),
333 )
334 }
335 }
336 };
337
338 Err(error)
339 }
340 }
341
342 async fn request_entity<D>(
343 &self,
344 method: http::Method,
345 uri: &str,
346 body: Option<reqwest::Body>,
347 ) -> Result<D>
348 where
349 D: serde::de::DeserializeOwned + 'static + Send,
350 {
351 let r = self.request(method, uri, body).await?;
352 Ok(r)
353 }
354
355 async fn get<D>(&self, uri: &str, message: Option<reqwest::Body>) -> Result<D>
356 where
357 D: serde::de::DeserializeOwned + 'static + Send,
358 {
359 self.request_entity(http::Method::GET, &(self.host.to_string() + uri), message)
360 .await
361 }
362
363 async fn post<D>(&self, uri: &str, message: Option<reqwest::Body>) -> Result<D>
364 where
365 D: serde::de::DeserializeOwned + 'static + Send,
366 {
367 self.request_entity(http::Method::POST, &(self.host.to_string() + uri), message)
368 .await
369 }
370
371 #[allow(dead_code)]
372 async fn patch<D>(&self, uri: &str, message: Option<reqwest::Body>) -> Result<D>
373 where
374 D: serde::de::DeserializeOwned + 'static + Send,
375 {
376 self.request_entity(http::Method::PATCH, &(self.host.to_string() + uri), message)
377 .await
378 }
379
380 async fn put<D>(&self, uri: &str, message: Option<reqwest::Body>) -> Result<D>
381 where
382 D: serde::de::DeserializeOwned + 'static + Send,
383 {
384 self.request_entity(http::Method::PUT, &(self.host.to_string() + uri), message)
385 .await
386 }
387
388 async fn delete<D>(&self, uri: &str, message: Option<reqwest::Body>) -> Result<D>
389 where
390 D: serde::de::DeserializeOwned + 'static + Send,
391 {
392 self.request_entity(
393 http::Method::DELETE,
394 &(self.host.to_string() + uri),
395 message,
396 )
397 .await
398 }
399
400 /// Virtual disks are used to store instance-local data which includes the operating system.
401 ///
402 ///FROM: http://oxide.computer/docs/#xxx
403 pub fn disks(&self) -> disks::Disks {
404 disks::Disks::new(self.clone())
405 }
406
407 /// TODO operations that will not ship to customers.
408 ///
409 ///FROM: http://oxide.computer/docs/#xxx
410 pub fn hidden(&self) -> hidden::Hidden {
411 hidden::Hidden::new(self.clone())
412 }
413
414 /// Images are read-only Virtual Disks that may be used to boot Virtual Machines.
415 ///
416 ///FROM: http://oxide.computer/docs/#xxx
417 pub fn images(&self) -> images::Images {
418 images::Images::new(self.clone())
419 }
420
421 /// Images are read-only Virtual Disks that may be used to boot Virtual Machines. These images are scoped globally.
422 ///
423 ///FROM: http://oxide.computer/docs/#xxx
424 pub fn images_global(&self) -> images_global::ImagesGlobal {
425 images_global::ImagesGlobal::new(self.clone())
426 }
427
428 /// Virtual machine instances are the basic unit of computation. These operations are used for provisioning, controlling, and destroying instances.
429 ///
430 ///FROM: http://oxide.computer/docs/#xxx
431 pub fn instances(&self) -> instances::Instances {
432 instances::Instances::new(self.clone())
433 }
434
435 /// IP Pools contain external IP addresses that can be assigned to virtual machine Instances.
436 ///
437 ///FROM: http://oxide.computer/docs/#xxx
438 pub fn ip_pools(&self) -> ip_pools::IpPools {
439 ip_pools::IpPools::new(self.clone())
440 }
441
442 /// Authentication endpoints.
443 ///
444 ///FROM: http://oxide.computer/docs/#xxx
445 pub fn login(&self) -> login::Login {
446 login::Login::new(self.clone())
447 }
448
449 /// Metrics provide insight into the operation of the Oxide deployment. These include telemetry on hardware and software components that can be used to understand the current state as well as to diagnose issues.
450 ///
451 ///FROM: http://oxide.computer/docs/#xxx
452 pub fn metrics(&self) -> metrics::Metrics {
453 metrics::Metrics::new(self.clone())
454 }
455
456 /// Organizations represent a subset of users and projects in an Oxide deployment.
457 ///
458 ///FROM: http://oxide.computer/docs/#xxx
459 pub fn organizations(&self) -> organizations::Organizations {
460 organizations::Organizations::new(self.clone())
461 }
462
463 /// System-wide IAM policy.
464 ///
465 ///FROM: http://oxide.computer/docs/#xxx
466 pub fn policy(&self) -> policy::Policy {
467 policy::Policy::new(self.clone())
468 }
469
470 /// Projects are a grouping of associated resources such as instances and disks within an organization for purposes of billing and access control.
471 ///
472 ///FROM: http://oxide.computer/docs/#xxx
473 pub fn projects(&self) -> projects::Projects {
474 projects::Projects::new(self.clone())
475 }
476
477 /// These operations pertain to hardware inventory and management. Racks are the unit of expansion of an Oxide deployment. Racks are in turn composed of sleds, switches, power supplies, and a cabled backplane.
478 ///
479 ///FROM: http://oxide.computer/docs/#xxx
480 pub fn racks(&self) -> racks::Racks {
481 racks::Racks::new(self.clone())
482 }
483
484 /// Roles are a component of Identity and Access Management (IAM) that allow a user or agent account access to additional permissions.
485 ///
486 ///FROM: http://oxide.computer/docs/#xxx
487 pub fn roles(&self) -> roles::Roles {
488 roles::Roles::new(self.clone())
489 }
490
491 /// Routers direct the flow of network traffic into, out of, and within a VPC via routes.
492 ///
493 ///FROM: http://oxide.computer/docs/#xxx
494 pub fn routers(&self) -> routers::Routers {
495 routers::Routers::new(self.clone())
496 }
497
498 /// Routes define router policy.
499 ///
500 ///FROM: http://oxide.computer/docs/#xxx
501 pub fn routes(&self) -> routes::Routes {
502 routes::Routes::new(self.clone())
503 }
504
505 /// Sagas are the abstraction used to represent multi-step operations within the Oxide deployment. These operations can be used to query saga status and report errors.
506 ///
507 ///FROM: http://oxide.computer/docs/#xxx
508 pub fn sagas(&self) -> sagas::Sagas {
509 sagas::Sagas::new(self.clone())
510 }
511
512 /// This tag should be moved into hardware.
513 ///
514 ///FROM: http://oxide.computer/docs/#xxx
515 pub fn sleds(&self) -> sleds::Sleds {
516 sleds::Sleds::new(self.clone())
517 }
518
519 /// Silos represent a logical partition of users and resources.
520 ///
521 ///FROM: http://oxide.computer/docs/#xxx
522 pub fn silos(&self) -> silos::Silos {
523 silos::Silos::new(self.clone())
524 }
525
526 /// Snapshots of Virtual Disks at a particular point in time.
527 ///
528 ///FROM: http://oxide.computer/docs/#xxx
529 pub fn snapshots(&self) -> snapshots::Snapshots {
530 snapshots::Snapshots::new(self.clone())
531 }
532
533 /// Public SSH keys for an individual user.
534 ///
535 ///FROM: http://oxide.computer/docs/#xxx
536 pub fn sshkeys(&self) -> sshkeys::Sshkeys {
537 sshkeys::Sshkeys::new(self.clone())
538 }
539
540 /// This tag should be moved into a generic network tag.
541 ///
542 ///FROM: http://oxide.computer/docs/#xxx
543 pub fn subnets(&self) -> subnets::Subnets {
544 subnets::Subnets::new(self.clone())
545 }
546
547 /// Internal system information.
548 ///
549 ///FROM: http://oxide.computer/docs/#xxx
550 pub fn system(&self) -> system::System {
551 system::System::new(self.clone())
552 }
553
554 /// This tag should be moved into a operations tag.
555 ///
556 ///FROM: http://oxide.computer/docs/#xxx
557 pub fn updates(&self) -> updates::Updates {
558 updates::Updates::new(self.clone())
559 }
560
561 /// A Virtual Private Cloud (VPC) is an isolated network environment that should probaby be moved into a more generic networking tag.
562 ///
563 ///FROM: http://oxide.computer/docs/#xxx
564 pub fn vpcs(&self) -> vpcs::Vpcs {
565 vpcs::Vpcs::new(self.clone())
566 }
567}