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));