openshift_openapi/
lib.rs

1#![warn(rust_2018_idioms)]
2#![deny(clippy::all, clippy::pedantic)]
3#![allow(
4    clippy::cognitive_complexity,
5    clippy::default_trait_access,
6    clippy::doc_markdown,
7    clippy::large_enum_variant,
8    clippy::match_single_binding,
9    clippy::missing_errors_doc,
10    clippy::must_use_candidate,
11    clippy::single_match_else,
12    clippy::too_many_lines,
13    clippy::type_complexity,
14    clippy::use_self
15)]
16
17//! Bindings for the OpenShift client API, generated from the OpenAPI spec.
18//!
19//! Each supported version of OpenShift is represented by a feature name (like `v4_4`). Only one such feature can be enabled at a time.
20//! As this crate depends on the other Kubernetes APIs, it references the `k8s_openapi` crate for
21//! these types. Selecting a supported version for OpenShift (like `v4_4`) will automatically
22//! select the corresponding Kubernetes feature on the `k8s_openapi` crate. So for OpenShift
23//! `v4_4` it will automatically enable the feature `k8s_openapi/v1_17`.
24//!
25//! These docs have been generated with the `
26
27#![cfg_attr(feature = "v4_4", doc = "v4_4")]
28
29//! ` feature enabled. To see docs for one of the other supported versions, please generate the docs locally with `cargo doc --features 'v4_<>'`
30//!
31//!
32//! # Examples
33//!
34//! ## Resources
35//!
36//! This example creates an instance of [`api::core::v1::PodSpec`] with no other properties set, and pretty-prints it.
37//!
38//! ```rust
39//! use k8s_openapi::api::core::v1 as api;
40//!
41//! fn main() {
42//!     let pod_spec: api::PodSpec = Default::default();
43//!     println!("{:#?}", pod_spec);
44//! }
45//! ```
46//!
47//! ## Client API
48//!
49//! (This requires the `api` feature to be enabled. The feature is enabled by default. See ["Crate features"](#crate-features) below for more details.)
50//!
51//! This example executes the [`api::core::v1::Pod::list_namespaced_pod`] API operation to list all pods inside a namespace.
52//! It demonstrates the common patterns implemented by all API operation functions in this crate:
53//!
54//! 1. The API function has required parameters and optional parameters. All optional parameters are taken as a single struct with optional fields.
55//!
56//!    Specifically for the [`api::core::v1::Pod::list_namespaced_pod`] operation, the `namespace` parameter is required and taken by the function itself,
57//!    while other optional parameters like `field_selector` are fields of the [`ListOptional`] struct. An instance of
58//!    this struct is taken as the last parameter of `Pod::list_namespaced_pod`. This struct impls [`Default`] so that you can just pass in `Default::default()`
59//!    if you don't want to specify values for any of the optional parameters.
60//!
61//!    Some API operations have a single common type for optional parameters:
62//!
63//!    - All create API take optional parameters using the [`CreateOptional`] struct.
64//!    - All delete API take optional parameters using the [`DeleteOptional`] struct.
65//!    - All list API take optional parameters using the [`ListOptional`] struct.
66//!    - All patch API take optional parameters using the [`PatchOptional`] struct.
67//!    - All replace API take optional parameters using the [`ReplaceOptional`] struct.
68//!    - All watch API take optional parameters using the [`WatchOptional`] struct.
69//!    - All delete-collection API take optional parameters using the [`DeleteOptional`] struct for delete options and the [`ListOptional`] struct for list options.
70//!
71//!    Other API functions have their own `Optional` structs with fields corresponding to the specific parameters for those functions,
72//!    such as [`api::core::v1::ReadNamespacedPodOptional`] for [`api::core::v1::Pod::read_namespaced_pod`]
73//!
74//! 1. The function returns an [`http::Request`] value with the URL path, query string, and request body filled out according to the parameters
75//!    given to the function. The function does *not* execute this request. You can execute this `http::Request` using any HTTP client library you want to use.
76//!    It does not matter whether you use a synchronous client like `reqwest`, or an asynchronous client like `hyper`, or a mock client that returns bytes
77//!    read from a test file.
78//!
79//! 1. For each API operation function, there is a corresponding response type. For `Pod::list_namespaced_pod` this is [`ListResponse`]`<`[`api::core::v1::Pod`]`>`.
80//!    This is an enum with variants for each of the possible HTTP status codes that the operation can return, and contains the data that the API server would
81//!    return corresponding to that status code. For example, the list-namespaced-pod operation returns a pod list with HTTP 200 OK, so one of the variants of
82//!    that type is `Ok(`[`List`]`<`[`api::core::v1::Pod`]`>)`
83//!
84//! 1. The response types impl the [`Response`] trait, which contains a single [`Response::try_from_parts`] function. This function takes an [`http::StatusCode`]
85//!    and a `&u8` byte buffer, and tries to parse the byte buffer as the response type. For example, if you executed the request and received an HTTP 200 OK response
86//!    with some bytes, you could call `<ListResponse<Pod> as Response>::try_from_parts(status_code, buf)` and expect to get
87//!    `Ok(ListResponse::<Pod>::Ok(pod_list))` from it.
88//!
89//!    Once again, this design ensures that the crate is not tied to a specific HTTP client library or interface. It does not matter how you execute the HTTP request,
90//!    nor whether your library is synchronous or asynchronous, since every HTTP client library gives you a way to get the HTTP response status code and the bytes
91//!    of the response body.
92//!
93//! 1. The API operation function also returns another value next to the `http::Request`. This value is a function that takes an [`http::StatusCode`] and returns
94//!    a [`ResponseBody`]`<ListResponse<Pod>>`. As mentioned above, `Response::try_from_parts` requires you to maintain a byte buffer for the response body.
95//!    `ResponseBody` is a helper that maintains such a buffer internally. It provides an `append_slice()` function to append slices to this internal buffer,
96//!    and a `parse()` function to parse the buffer as the expected type (`ListResponse<Pod>` in this case).
97//!
98//!    It is not *necessary* to use the `ResponseBody` returned by the API operation function to parse the response. The `ResponseBody::parse` function is
99//!    only a wrapper around the underlying `Response::try_from_parts` function, and handles growing and shrinking its inner buffer as necessary. It also
100//!    helps ensure that the response body is parsed as the *correct* type for the operation, `ListResponse<Pod>` in this case, and not some other type.
101//!    However, you can instead use your own byte buffer instead of the `ResponseBody` value and call `ListResponse<Pod>::try_from_parts` yourself.
102//!
103//! 1. The response types are enums with variants corresponding to HTTP status codes. For example, the `ListResponse<Pod>::Ok` variant corresponds to the
104//!    HTTP 200 response of the list-namespaced-pod API.
105//!
106//!    Each response enum also has an `Other` variant, that is yielded when the response status code does not match any of the other variants.
107//!    This variant has a `Result<Option<`[`serde_json::Value`]`>, `[`serde_json::Error`]`>` value.
108//!
109//!    If the response body is empty, this value will be `Ok(None)`.
110//!
111//!    If the response body is not empty, this value will be an `Ok(Some(value))` or `Err(err)` from attempting to parse that body as a `serde_json::Value`.
112//!    If you expect the response body to be a specific JSON type such as [`apimachinery::pkg::apis::meta::v1::Status`], you can use the `serde_json::Value`
113//!    as a [`serde::Deserializer`] like `let status = <Status as Deserialize>::deserialize(value)?;`. On the other hand, if you expect the response body to not be
114//!    a JSON value, then ignore the `Err(err)` and parse the raw bytes of the response into the appropriate type.
115//!
116//! Also see the `get_single_value` and `get_multiple_values` functions in
117//! [the `k8s-openapi-tests` directory in the repository](https://github.com/Arnavion/k8s-openapi/tree/master/k8s-openapi-tests/src)
118//! for examples of how to use a synchronous client with this style of API.
119//!
120#![cfg_attr(feature = "api", doc = "```rust,no_run")]
121#![cfg_attr(not(feature = "api"), doc = "```rust,ignore")]
122//! // Re-export of the http crate since it's used in the public API
123//! use k8s_openapi::http;
124//!
125//! use k8s_openapi::api::core::v1 as api;
126//!
127//! # struct Response;
128//! # impl Response {
129//! #     fn status_code(&self) -> http::StatusCode {
130//! #         unimplemented!()
131//! #     }
132//! #     fn read_into(&self, _buf: &mut [u8]) -> std::io::Result<usize> {
133//! #         unimplemented!()
134//! #     }
135//! # }
136//! #
137//! // Assume `execute` is some function that takes an `http::Request` and
138//! // executes it synchronously or asynchronously to get a response. This is
139//! // provided by your HTTP client library.
140//! //
141//! // Note that the `http::Request` values returned by API operation functions
142//! // only have a URL path, query string and request body filled out. That is,
143//! // they do *not* have a URL host. So the real `execute` implementation
144//! // would first mutate the URL of the request to an absolute URL with
145//! // the API server's authority, add authorization headers, etc before
146//! // actually executing it.
147//! fn execute(req: http::Request<Vec<u8>>) -> Response { unimplemented!(); }
148//!
149//! fn main() -> Result<(), Box<dyn std::error::Error>> {
150//!     // Create a `http::Request` to list all the pods in the
151//!     // "kube-system" namespace.
152//!     let (request, response_body) =
153//!         api::Pod::list_namespaced_pod("kube-system", Default::default())?;
154//!
155//!     // Execute the request and get a response.
156//!     // If this is an asynchronous operation, you would await
157//!     // or otherwise yield to the event loop here.
158//!     let response = execute(request);
159//!
160//!     // Got a status code from executing the request.
161//!     let status_code: http::StatusCode = response.status_code();
162//!
163//!     // Construct the `ResponseBody<ListResponse<Pod>>` using the
164//!     // constructor returned by the API function.
165//!     let mut response_body = response_body(status_code);
166//!
167//!     // Buffer used for each read from the HTTP response.
168//!     let mut buf = Box::new([0u8; 4096]);
169//!
170//!     let pod_list = loop {
171//!         // Read some bytes from the HTTP response into the buffer.
172//!         // If this is an asynchronous operation, you would await or
173//!         // yield to the event loop here.
174//!         let read = response.read_into(&mut *buf)?;
175//!
176//!         // `buf` now contains some data read from the response. Append it
177//!         // to the `ResponseBody` and try to parse it into
178//!         // the response type.
179//!         response_body.append_slice(&buf[..read]);
180//!         let response = response_body.parse();
181//!         match response {
182//!             // Successful response (HTTP 200 and parsed successfully)
183//!             Ok(k8s_openapi::ListResponse::Ok(pod_list)) =>
184//!                 break pod_list,
185//!
186//!             // Some unexpected response
187//!             // (not HTTP 200, but still parsed successfully)
188//!             Ok(other) => return Err(format!(
189//!                 "expected Ok but got {} {:?}",
190//!                 status_code, other).into()),
191//!
192//!             // Need more response data.
193//!             // Read more bytes from the response into the `ResponseBody`
194//!             Err(k8s_openapi::ResponseError::NeedMoreData) => continue,
195//!
196//!             // Some other error, like the response body being
197//!             // malformed JSON or invalid UTF-8.
198//!             Err(err) => return Err(format!(
199//!                 "error: {} {:?}",
200//!                 status_code, err).into()),
201//!         }
202//!     };
203//!
204//!     for pod in pod_list.items {
205//!         println!("{:#?}", pod);
206//!     }
207//!
208//!     Ok(())
209//! }
210//! ```
211//!
212//!
213//! # Crate features
214//!
215//! - This crate contains several `v4_*` features. Enabling one of the `v4_*` features selects which version of the Kubernetes API server this crate should target.
216//!   For example, enabling the `v4_3` feature means the crate will only contain the API exposed by OpenShift 4.3. It will not expose API
217//!   that were removed in 4.3 or earlier, nor any API added in 4.4 or later.
218//!
219//! - The crate also contains a feature named `api`. If this feature is disabled, the library will only contain the resource types like [`api::core::v1::Pod`],
220//!   and not the associated operation functions like [`api::core::v1::Pod::read_namespaced_pod`]. The `Response` and `Optional` types for the operation functions
221//!   will also not be accessible.
222//!
223//!   This feature is enabled by default, but can be disabled if your crate does not need the operation functions to save on compile time and resources.
224//!
225//! One and only one of the `v1_*` features must be enabled at the same time, otherwise the crate will not compile. This ensures that all crates in the crate graph
226//! use the same types. If it was possible for one library crate to use `api::core::v1::Pod` corresponding to v1.15 and another to use the type
227//! corresponding to v1.16, an application would not be able to use the same `Pod` value with both.
228//!
229//! Thus, it is recommended that only application crates must enable one of the `v1_*` features, corresponding to the version of Kubernetes
230//! that the application wants to support.
231//!
232//! ```toml
233//! # For application crates
234//!
235//! [dependencies]
236//! k8s-openapi = { version = "...", features = ["v1_14"] }
237//! ```
238//!
239//! If you're writing a library crate, your crate *must not* enable any features of `k8s-openapi` directly. The choice of which feature to enable
240//! must be left to any application crates that use your library. This ensures that all `k8s-openapi`-using dependencies in that application crate's dependency graph
241//! use the same set of `k8s-openapi` types and are interoperable.
242//!
243//! If your library crate has tests or examples, you should also add a dev-dependency on `k8s-openapi` in addition to the direct dependency,
244//! and enable a version feature only for that dev-dependency.
245//!
246//! ```toml
247//! # For library crates
248//!
249//! [dependencies]
250//! k8s-openapi = "..."
251//!
252//! [dev-dependencies]
253//! k8s-openapi = { version = "...", features = ["v1_14"] }
254//! ```
255//!
256//!
257//! # Conditional compilation
258//!
259//! As the previous section explained, library crates must not enable any version features in their `k8s-openapi` dependency. However, your library crate may
260//! need to know about which version gets selected eventually.
261//!
262//! For example:
263//!
264//! 1. Your crate creates a custom resource definition using the apiextensions v1 API. This API is only available in Kubernetes 1.16+,
265//!    so your crate would fail to compile if a lower feature was enabled.
266//!
267//! 1. Your crate creates a custom resource definition. If the `v1_16` or later feature is enabled, your crate wants to use the apiextensions v1 API,
268//!    otherwise it falls back to the v1beta1 API.
269//!
270//! There are two ways for your crate to determine which feature of `k8s-openapi` is enabled:
271//!
272//! 1. The `k8s-openapi` crate exports [`k8s_if_*` macros,](#macros) which either expand to their contents or don't. See the docs of the macros for more details.
273//!
274//!    With these macros, the two cases above would be solved like this:
275//!
276//!    - ```rust,ignore
277//!      #[macro_use] extern crate k8s_openapi;
278//!
279//!      // The compile_error!() is only emitted if 1.15 or lower is selected.
280//!      k8s_if_le_1_15! {
281//!          compile_error!("This crate requires the v1_16 (or higher) feature to be enabled on the k8s-openapi crate.");
282//!      }
283//!      ```
284//!
285//!    - ```rust,ignore
286//!      #[macro_use] extern crate k8s_openapi;
287//!
288//!      k8s_if_le_1_15! {
289//!          use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1beta1 as apiextensions;
290//!      }
291//!      k8s_if_ge_1_16! {
292//!          use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1 as apiextensions;
293//!      }
294//!
295//!      // Common fields regardless of the apiextensions version
296//!      let custom_resource_definition_spec = apiextensions::CustomResourceDefinitionSpec {
297//!          group: ...,
298//!          names: ...,
299//!          scope: ...,
300//!          ..Default::default()
301//!      };
302//!
303//!      // Set v1beta1 `version` and `validation` fields on v1.15 and earlier.
304//!      k8s_if_le_1_15! {
305//!          let custom_resource_definition_spec = apiextensions::CustomResourceDefinitionSpec {
306//!              version: <FooBar as k8s_openapi::Resource>::VERSION.to_owned().into(),
307//!              validation: Some(custom_resource_validation),
308//!              ..custom_resource_definition_spec
309//!          };
310//!      }
311//!      // Set v1 `versions` field on v1.16 and later.
312//!      k8s_if_ge_1_16! {
313//!          let custom_resource_definition_spec = apiextensions::CustomResourceDefinitionSpec {
314//!              versions: vec![
315//!                  apiextensions::CustomResourceDefinitionVersion {
316//!                      name: <FooBar as k8s_openapi::Resource>::VERSION.to_owned(),
317//!                      schema: Some(custom_resource_validation),
318//!                      served: true,
319//!                      storage: true,
320//!                      ..Default::default()
321//!                  },
322//!              ].into(),
323//!              ..custom_resource_definition_spec
324//!          };
325//!      }
326//!      ```
327//!
328//! 1. The `k8s-openapi` crate emits the selected version number as metadata that your crate can read in a build script
329//!    from the `DEP_K8S_OPENAPI_*_VERSION` env var.
330//!
331//!    ```rust,no_run
332//!    // Your crate's build.rs
333//!
334//!    fn main() {
335//!        let k8s_openapi_version: u32 =
336//!            std::env::vars_os()
337//!            .find_map(|(key, value)| {
338//!                let key = key.into_string().ok()?;
339//!                if key.starts_with("DEP_K8S_OPENAPI_") && key.ends_with("_VERSION") {
340//!                    let value = value.into_string().ok()?;
341//!                    Some(value)
342//!                }
343//!                else {
344//!                    None
345//!                }
346//!            }).expect("DEP_K8S_OPENAPI_*_VERSION must have been set by k8s-openapi")
347//!            .parse().expect("DEP_K8S_OPENAPI_*_VERSION is malformed");
348//!
349//!        // k8s_openapi_version has the format 0x00_MM_NN_00.
350//!        //
351//!        // - MM is the major version.
352//!        // - NN is the minor version.
353//!        //
354//!        // Thus, if the v1_16 feature was enabled, k8s_openapi_version would be 0x00_01_10_00
355//!
356//!        // The build script can now do arbitrary things with the information.
357//!        // For example, it could define custom cfgs:
358//!        if k8s_openapi_version >= 0x00_01_10_00 {
359//!            println!(r#"cargo:rustc-cfg=k8s_apiextensions="v1""#);
360//!        }
361//!        else {
362//!            println!(r#"cargo:rustc-cfg=k8s_apiextensions="v1beta1""#);
363//!        }
364//!
365//!        // or emit new source code files under OUT_DIR, or anything else a build script can do.
366//!    }
367//!    ```
368//!
369//!    With these cfgs, the two cases above would be solved like this:
370//!
371//!    - ```rust,ignore
372//!      // Your crate's src/lib.rs
373//!
374//!      #[cfg(k8s_apiextensions = "v1")]
375//!      compile_error!("This crate requires the v1_16 (or higher) feature to be enabled on the k8s-openapi crate.");
376//!      ```
377//!
378//!    - ```rust,ignore
379//!      #[cfg(k8s_apiextensions = "v1beta1")]
380//!      use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1beta1 as apiextensions;
381//!      #[cfg(k8s_apiextensions = "v1")]
382//!      use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1 as apiextensions;
383//!
384//!      // Common fields regardless of the apiextensions version
385//!      let custom_resource_definition_spec = apiextensions::CustomResourceDefinitionSpec {
386//!          group: ...,
387//!          names: ...,
388//!          scope: ...,
389//!          ..Default::default()
390//!      };
391//!
392//!      // Set v1beta1 `version` and `validation` fields on v1.15 and earlier.
393//!      #[cfg(k8s_apiextensions = "v1beta1")]
394//!      let custom_resource_definition_spec = apiextensions::CustomResourceDefinitionSpec {
395//!          version: <FooBar as k8s_openapi::Resource>::VERSION.to_owned().into(),
396//!          validation: Some(custom_resource_validation),
397//!          ..custom_resource_definition_spec
398//!      };
399//!      // Set v1 `versions` field on v1.16 and later.
400//!      #[cfg(k8s_apiextensions = "v1")]
401//!      let custom_resource_definition_spec = apiextensions::CustomResourceDefinitionSpec {
402//!          versions: vec![
403//!              apiextensions::CustomResourceDefinitionVersion {
404//!                  name: <FooBar as k8s_openapi::Resource>::VERSION.to_owned(),
405//!                  schema: Some(custom_resource_validation),
406//!                  served: true,
407//!                  storage: true,
408//!                  ..Default::default()
409//!              },
410//!          ].into(),
411//!          ..custom_resource_definition_spec
412//!      };
413//!      ```
414//!
415//! Note that both approaches require your crate to have a direct dependency on the `k8s-openapi` crate. Neither approach is available if your crate
416//! only has a transitive dependency on the `k8s-openapi` crate.
417//!
418//! The macros approach is easier to use since it doesn't require a build script.
419//!
420//! The build script method lets you emit arbitrary cfgs, emit arbitrary source code, and generally gives you more options, at the cost of needing a build script.
421//! For example, `cfg()`s can be used in places where macros cannot, such as this example for conditionally setting attrs using `cfg_attr`:
422//!
423//! ```rust,ignore
424//! #[derive(
425//!     Clone, Debug, PartialEq,
426//!     k8s_openapi_derive::CustomResourceDefinition,
427//!     serde_derive::Deserialize, serde_derive::Serialize,
428//! )]
429//! #[custom_resource_definition(
430//!     group = "k8s-openapi-tests-custom-resource-definition.com",
431//!     version = "v1",
432//!     plural = "foobars",
433//!     namespaced,
434//! )]
435//! #[cfg_attr(k8s_apiextensions = "v1beta1", custom_resource_definition(has_subresources = "v1beta1"))]
436//! #[cfg_attr(k8s_apiextensions = "v1", custom_resource_definition(has_subresources = "v1"))]
437//! struct FooBarSpec {
438//!     prop1: String,
439//!     prop2: Vec<bool>,
440//!     #[serde(skip_serializing_if = "Option::is_none")]
441//!     prop3: Option<i32>,
442//! }
443//! ```
444//!
445//! It isn't possible to conditionally set attributes using macros, so the entire `struct FooBarSpec` declaration would have to be duplicated and wrapped inside
446//! `k8s_if_le_1_15! { }` and `k8s_if_ge_1_16! { }` respectively.
447//!
448//!
449//! # Custom resource definitions
450//!
451//! The [`k8s-openapi-derive` crate](https://crates.io/crates/k8s-openapi-derive) provides a custom derive for generating clientsets
452//! for custom resources. See that crate's docs for more information.
453
454pub use chrono;
455pub use serde_json;
456pub use serde_value;
457
458#[cfg(feature = "v4_3")]
459mod v4_3;
460#[cfg(feature = "v4_3")]
461pub use self::v4_3::*;
462
463#[cfg(feature = "v4_4")]
464mod v4_4;
465#[cfg(feature = "v4_4")]
466pub use self::v4_4::*;
467
468#[cfg(feature = "v4_5")]
469mod v4_5;
470#[cfg(feature = "v4_5")]
471pub use self::v4_5::*;
472
473include!(concat!(
474    env!("OUT_DIR"),
475    "/conditional_compilation_macros.rs"
476));