diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 54c983a1b..61585a9a7 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/rust:1.75.0-bullseye +FROM docker.io/rust:1.77.2-bullseye ENV DEBIAN_FRONTEND=noninteractive RUN apt update && apt upgrade -y @@ -44,16 +44,16 @@ RUN scurl https://raw.githubusercontent.com/rancher/k3d/main/install.sh \ RUN rustup component add clippy rls rust-src rustfmt # Install cargo-deny -ARG CARGO_DENY_VERSION=0.11.4 +ARG CARGO_DENY_VERSION=0.16.1 RUN scurl "https://github.com/EmbarkStudios/cargo-deny/releases/download/${CARGO_DENY_VERSION}/cargo-deny-${CARGO_DENY_VERSION}-x86_64-unknown-linux-musl.tar.gz" \ | tar zvxf - --strip-components=1 -C $HOME/bin "cargo-deny-${CARGO_DENY_VERSION}-x86_64-unknown-linux-musl/cargo-deny" # Install cargo-tarpaulin -ARG CARGO_TARPAULIN_VERSION=0.20.0 -RUN scurl "https://github.com/xd009642/tarpaulin/releases/download/${CARGO_TARPAULIN_VERSION}/cargo-tarpaulin-${CARGO_TARPAULIN_VERSION}-travis.tar.gz" \ +ARG CARGO_TARPAULIN_VERSION=0.31.2 +RUN scurl "https://github.com/xd009642/tarpaulin/releases/download/${CARGO_TARPAULIN_VERSION}/cargo-tarpaulin-x86_64-unknown-linux-musl.tar.gz" \ | tar xzvf - -C $HOME/bin -ARG JUST_VERSION=1.1.3 +ARG JUST_VERSION=1.35.0 RUN scurl https://github.com/casey/just/releases/download/${JUST_VERSION}/just-${JUST_VERSION}-x86_64-unknown-linux-musl.tar.gz \ | tar xzvf - -C $HOME/bin diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9c6b70ee..66e280efa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -140,7 +140,7 @@ jobs: fail-fast: false matrix: # Run these tests against older clusters as well - k8s: [v1.25, v1.29] + k8s: [v1.26, v1.30] steps: - uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 @@ -214,7 +214,7 @@ jobs: - uses: nolar/setup-k3d-k3s@v1 with: - version: v1.25 + version: v1.26 # k3d-kube k3d-name: kube # Used to avoid rate limits when fetching the releases from k3s repo. diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 0b1d89f31..88076c809 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -25,7 +25,7 @@ jobs: tool: cargo-tarpaulin@0.28.0 - uses: nolar/setup-k3d-k3s@v1 with: - version: v1.25 + version: v1.26 # k3d-kube k3d-name: kube # Used to avoid rate limits when fetching the releases from k3s repo. diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d7696eb6..064c43728 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,23 @@ UNRELEASED =================== - * see https://github.com/kube-rs/kube/compare/0.94.2...main + * see https://github.com/kube-rs/kube/compare/0.95.0...main +[0.95.0](https://github.com/kube-rs/kube/releases/tag/0.95.0) / 2024-09-16 +=================== + +## Kubernetes `v1_31` support via `k8s-openapi` [0.23](https://github.com/Arnavion/k8s-openapi/releases/tag/v0.23.0) +Please [upgrade k8s-openapi along with kube](https://kube.rs/upgrading/) to avoid conflicts. + +New minimum versions: [MSRV](https://kube.rs/rust-version/) 1.77.2, [MK8SV](https://kube.rs/kubernetes-version/): 1.26 + +## What's Changed +### Changed +* Update tokio-tungstenite requirement from 0.23.0 to 0.24.0 by @dependabot in https://github.com/kube-rs/kube/pull/1579 +* Bump `k8s-openapi` to 0.23 for Kubernetes 1.31 support by @clux in https://github.com/kube-rs/kube/pull/1581 + + +**Full Changelog**: https://github.com/kube-rs/kube/compare/0.94.2...0.95.0 [0.94.2](https://github.com/kube-rs/kube/releases/tag/0.94.2) / 2024-09-13 =================== diff --git a/Cargo.toml b/Cargo.toml index 4940296fc..388c7bca1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ members = [ ] [workspace.package] -version = "0.94.2" +version = "0.95.0" authors = [ "clux ", "Natalie Klestrup Röijezon ", @@ -24,7 +24,7 @@ repository = "https://github.com/kube-rs/kube" readme = "README.md" license = "Apache-2.0" edition = "2021" -rust-version = "1.75.0" +rust-version = "1.77.2" [workspace.lints.rust] unsafe_code = "forbid" @@ -42,7 +42,7 @@ base64 = "0.22.0" bytes = "1.1.0" chrono = { version = "0.4.34", default-features = false } darling = "0.20.3" -derivative = "2.1.1" +educe = { version = "0.6.0", default-features = false } either = "1.6.1" form_urlencoded = "1.2.0" futures = { version = "0.3.17", default-features = false } @@ -52,7 +52,7 @@ http = "1.1.0" http-body = "1.0.0" http-body-util = "0.1.2" hyper = "1.2.0" -hyper-util = "0.1.3" +hyper-util = "0.1.9" hyper-openssl = "0.10.2" hyper-rustls = { version = "0.27.1", default-features = false } hyper-socks2 = { version = "0.9.0", default-features = false } @@ -60,7 +60,7 @@ hyper-timeout = "0.5.1" json-patch = "2.0.0" jsonptr = "0.4.7" jsonpath-rust = "0.5.0" -k8s-openapi = { version = "0.22.0", default-features = false } +k8s-openapi = { version = "0.23.0", default-features = false } openssl = "0.10.36" parking_lot = "0.12.0" pem = "3.0.1" @@ -71,7 +71,7 @@ rand = "0.8.3" rustls = { version = "0.23.0", default-features = false } rustls-pemfile = "2.0.0" schemars = "0.8.6" -secrecy = "0.8.0" +secrecy = "0.10.2" serde = "1.0.130" serde_json = "1.0.68" serde-value = "0.7.0" @@ -82,10 +82,10 @@ tempfile = "3.1.0" thiserror = "1.0.29" tokio = "1.14.0" tokio-test = "0.4.0" -tokio-tungstenite = "0.23.0" +tokio-tungstenite = "0.24.0" tokio-util = "0.7.0" -tower = "0.4.13" -tower-http = "0.5.2" +tower = "0.5.1" +tower-http = "0.6.1" tower-test = "0.4.0" tracing = "0.1.36" tracing-subscriber = "0.3.17" diff --git a/README.md b/README.md index dd49a5e7b..9a64c66dc 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # kube-rs [![Crates.io](https://img.shields.io/crates/v/kube.svg)](https://crates.io/crates/kube) -[![Rust 1.75](https://img.shields.io/badge/MSRV-1.75-dea584.svg)](https://github.com/rust-lang/rust/releases/tag/1.75.0) -[![Tested against Kubernetes v1_25 and above](https://img.shields.io/badge/MK8SV-v1_25-326ce5.svg)](https://kube.rs/kubernetes-version) +[![Rust 1.77](https://img.shields.io/badge/MSRV-1.77-dea584.svg)](https://github.com/rust-lang/rust/releases/tag/1.77.2) +[![Tested against Kubernetes v1_26 and above](https://img.shields.io/badge/MK8SV-v1_26-326ce5.svg)](https://kube.rs/kubernetes-version) [![Best Practices](https://bestpractices.coreinfrastructure.org/projects/5413/badge)](https://bestpractices.coreinfrastructure.org/projects/5413) [![Discord chat](https://img.shields.io/discord/500028886025895936.svg?logo=discord&style=plastic)](https://discord.gg/tokio) @@ -16,8 +16,8 @@ Select a version of `kube` along with the generated [k8s-openapi](https://github ```toml [dependencies] -kube = { version = "0.94.2", features = ["runtime", "derive"] } -k8s-openapi = { version = "0.22.0", features = ["latest"] } +kube = { version = "0.95.0", features = ["runtime", "derive"] } +k8s-openapi = { version = "0.23.0", features = ["latest"] } ``` See [features](https://kube.rs/features/) for a quick overview of default-enabled / opt-in functionality. @@ -43,7 +43,7 @@ For real world projects see [ADOPTERS](https://kube.rs/adopters/). ## Api -The [`Api`](https://docs.rs/kube/*/kube/struct.Api.html) is what interacts with Kubernetes resources, and is generic over [`Resource`](https://docs.rs/kube/*/kube/trait.Resource.html): +The [`Api`](https://docs.rs/kube/latest/kube/struct.Api.html) is what interacts with Kubernetes resources, and is generic over [`Resource`](https://docs.rs/kube/latest/kube/trait.Resource.html): ```rust use k8s_openapi::api::core::v1::Pod; @@ -102,7 +102,7 @@ A streaming interface (similar to informers) that presents [`watcher::Event`](ht ```rust let api = Api::::default_namespaced(client); -let stream = watcher(api, Config::default()).applied_objects(); +let stream = watcher(api, Config::default()).default_backoff().applied_objects(); ``` This now gives a continual stream of events and you do not need to care about the watch having to restart, or connections dropping. @@ -113,6 +113,7 @@ while let Some(event) = stream.try_next().await? { } ``` + Note the base items from a `watcher` stream are an abstraction above the native `WatchEvent` to allow for store buffering. If you are following along to "see what changed", you can use utilities from [`WatchStreamExt`](https://docs.rs/kube/latest/kube/runtime/trait.WatchStreamExt.html), such as `applied_objects` to get a more conventional stream. ## Reflectors @@ -155,8 +156,8 @@ By default [rustls](https://github.com/rustls/rustls) is used for TLS, but `open ```toml [dependencies] -kube = { version = "0.94.2", default-features = false, features = ["client", "openssl-tls"] } -k8s-openapi = { version = "0.22.0", features = ["latest"] } +kube = { version = "0.95.0", default-features = false, features = ["client", "openssl-tls"] } +k8s-openapi = { version = "0.23.0", features = ["latest"] } ``` This will pull in `openssl` and `hyper-openssl`. If `default-features` is left enabled, you will pull in two TLS stacks, and the default will remain as `rustls`. diff --git a/deny.toml b/deny.toml index c58216897..0e5655f50 100644 --- a/deny.toml +++ b/deny.toml @@ -40,6 +40,9 @@ exceptions = [ # this is not a problem for us. See https://github.com/dtolnay/unicode-ident/pull/9/files # for more details. { allow = ["Unicode-DFS-2016"], name = "unicode-ident" }, + # Pulled in via hyper-rustls when using the webpki-roots feature, + # which is off by default. + { allow = ["MPL-2.0"], name = "webpki-roots" }, ] [[licenses.clarify]] @@ -58,19 +61,15 @@ allow-git = ["https://github.com/tyrone-wu/runtime-macros.git"] [bans] multiple-versions = "deny" -[[bans.skip]] -name = "hermit-abi" - [[bans.skip]] name = "rustls-native-certs" [[bans.skip]] -# Needs a complicated upgrade -name = "syn" +# blocked on us swapping out serde_yaml +name = "hashbrown" [[bans.skip]] -# waiting for pem to bump base64 -# https://github.com/jcreekmore/pem-rs/blob/master/Cargo.toml#L16 +# base64 did some annoying breaking changes name = "base64" [[bans.skip]] @@ -78,10 +77,6 @@ name = "base64" # newer via tower-http (we have latest) name = "bitflags" -[[bans.skip]] -# deep in dependency tree, only dual use via dev dependency -name = "redox_syscall" - [[bans.skip-tree]] name = "windows-sys" diff --git a/e2e/Cargo.toml b/e2e/Cargo.toml index ecc4d5b39..6135f4a87 100644 --- a/e2e/Cargo.toml +++ b/e2e/Cargo.toml @@ -19,7 +19,7 @@ path = "boot.rs" [features] latest = ["k8s-openapi/latest"] -mk8sv = ["k8s-openapi/v1_25"] +mk8sv = ["k8s-openapi/v1_26"] rustls = ["kube/rustls-tls"] openssl = ["kube/openssl-tls"] @@ -27,7 +27,7 @@ openssl = ["kube/openssl-tls"] anyhow.workspace = true tracing.workspace = true tracing-subscriber.workspace = true -kube = { path = "../kube", version = "^0.94.2", default-features = false, features = ["client", "runtime", "ws", "admission", "gzip"] } +kube = { path = "../kube", version = "^0.95.0", default-features = false, features = ["client", "runtime", "ws", "admission", "gzip"] } k8s-openapi.workspace = true serde_json.workspace = true tokio = { workspace = true, features = ["full"] } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 8dfb1c0ea..d2b9832e1 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -29,8 +29,8 @@ anyhow.workspace = true futures = { workspace = true, features = ["async-await"] } jsonpath-rust.workspace = true jsonptr.workspace = true -kube = { path = "../kube", version = "^0.94.2", default-features = false, features = ["admission"] } -kube-derive = { path = "../kube-derive", version = "^0.94.2", default-features = false } # only needed to opt out of schema +kube = { path = "../kube", version = "^0.95.0", default-features = false, features = ["admission"] } +kube-derive = { path = "../kube-derive", version = "^0.95.0", default-features = false } # only needed to opt out of schema k8s-openapi.workspace = true serde = { workspace = true, features = ["derive"] } serde_json.workspace = true diff --git a/examples/custom_client.rs b/examples/custom_client.rs index 795b0421a..0b61a8810 100644 --- a/examples/custom_client.rs +++ b/examples/custom_client.rs @@ -1,6 +1,7 @@ use hyper_util::rt::TokioExecutor; // Minimal custom client example. use k8s_openapi::api::core::v1::Pod; +use tower::BoxError; use tracing::*; use kube::{client::ConfigExt, Api, Client, Config, ResourceExt}; @@ -15,6 +16,7 @@ async fn main() -> anyhow::Result<()> { let service = tower::ServiceBuilder::new() .layer(config.base_uri_layer()) .option_layer(config.auth_layer()?) + .map_err(BoxError::from) .service(hyper_util::client::legacy::Client::builder(TokioExecutor::new()).build(https)); let client = Client::new(service, config.default_namespace); diff --git a/examples/custom_client_trace.rs b/examples/custom_client_trace.rs index 866a5dbd3..25cb1aa6c 100644 --- a/examples/custom_client_trace.rs +++ b/examples/custom_client_trace.rs @@ -4,7 +4,7 @@ use hyper::body::Incoming; use hyper_util::rt::TokioExecutor; use k8s_openapi::api::core::v1::Pod; use std::time::Duration; -use tower::ServiceBuilder; +use tower::{BoxError, ServiceBuilder}; use tower_http::{decompression::DecompressionLayer, trace::TraceLayer}; use tracing::{Span, *}; @@ -54,6 +54,7 @@ async fn main() -> anyhow::Result<()> { tracing::debug!("finished in {}ms", latency.as_millis()) }), ) + .map_err(BoxError::from) .service(hyper_util::client::legacy::Client::builder(TokioExecutor::new()).build(https)); let client = Client::new(service, config.default_namespace); diff --git a/kube-client/Cargo.toml b/kube-client/Cargo.toml index d802443f7..c7c262933 100644 --- a/kube-client/Cargo.toml +++ b/kube-client/Cargo.toml @@ -14,6 +14,7 @@ categories = ["web-programming::http-client", "network-programming", "api-bindin [features] default = ["client"] rustls-tls = ["rustls", "rustls-pemfile", "hyper-rustls", "hyper-http-proxy?/rustls-tls-native-roots"] +webpki-roots = ["hyper-rustls/webpki-roots"] aws-lc-rs = ["rustls?/aws-lc-rs"] openssl-tls = ["openssl", "hyper-openssl"] ws = ["client", "tokio-tungstenite", "rand", "kube-core/ws", "tokio/macros"] @@ -59,7 +60,7 @@ rustls = { workspace = true, optional = true } rustls-pemfile = { workspace = true, optional = true } bytes = { workspace = true, optional = true } tokio = { workspace = true, features = ["time", "signal", "sync"], optional = true } -kube-core = { path = "../kube-core", version = "=0.94.2" } +kube-core = { path = "../kube-core", version = "=0.95.0" } jsonpath-rust = { workspace = true, optional = true } tokio-util = { workspace = true, features = ["io", "codec"], optional = true } hyper = { workspace = true, features = ["client", "http1"], optional = true } @@ -73,7 +74,7 @@ tower-http = { workspace = true, features = ["auth", "map-response-body", "trace hyper-timeout = { workspace = true, optional = true } tame-oauth = { workspace = true, features = ["gcp"], optional = true } rand = { workspace = true, optional = true } -secrecy = { workspace = true, features = ["alloc", "serde"] } +secrecy = { workspace = true } tracing = { workspace = true, features = ["log"], optional = true } hyper-openssl = { workspace = true, features = ["client-legacy"], optional = true } form_urlencoded = { workspace = true, optional = true } diff --git a/kube-client/src/api/mod.rs b/kube-client/src/api/mod.rs index 24088e71a..020d95337 100644 --- a/kube-client/src/api/mod.rs +++ b/kube-client/src/api/mod.rs @@ -11,14 +11,9 @@ use std::fmt::Debug; mod subresource; #[cfg(feature = "ws")] #[cfg_attr(docsrs, doc(cfg(feature = "ws")))] -pub use subresource::{Attach, AttachParams, Execute, Portforward}; +pub use subresource::{Attach, AttachParams, Ephemeral, Execute, Portforward}; pub use subresource::{Evict, EvictParams, Log, LogParams, ScaleSpec, ScaleStatus}; -// Ephemeral containers were stabilized in Kubernetes 1.25. -k8s_openapi::k8s_if_ge_1_25! { - pub use subresource::Ephemeral; -} - mod util; pub mod entry; diff --git a/kube-client/src/client/auth/mod.rs b/kube-client/src/client/auth/mod.rs index f3621c0ba..406ca26ca 100644 --- a/kube-client/src/client/auth/mod.rs +++ b/kube-client/src/client/auth/mod.rs @@ -144,7 +144,7 @@ impl TokenFile { /// Get the cached token. Returns `None` if it's expiring. fn cached_token(&self) -> Option<&str> { - (!self.is_expiring()).then(|| self.token.expose_secret().as_ref()) + (!self.is_expiring()).then(|| self.token.expose_secret()) } /// Get a token. Reloads from file if the cached token is expiring. diff --git a/kube-client/src/client/auth/oauth.rs b/kube-client/src/client/auth/oauth.rs index 4363f6775..0def2f39d 100644 --- a/kube-client/src/client/auth/oauth.rs +++ b/kube-client/src/client/auth/oauth.rs @@ -118,13 +118,19 @@ impl Gcp { // Current TLS feature precedence when more than one are set: // 1. rustls-tls // 2. openssl-tls - #[cfg(feature = "rustls-tls")] + #[cfg(all(feature = "rustls-tls", not(feature = "webpki-roots")))] let https = hyper_rustls::HttpsConnectorBuilder::new() .with_native_roots() .map_err(Error::NoValidNativeRootCA)? .https_only() .enable_http1() .build(); + #[cfg(all(feature = "rustls-tls", feature = "webpki-roots"))] + let https = hyper_rustls::HttpsConnectorBuilder::new() + .with_webpki_roots() + .https_only() + .enable_http1() + .build(); #[cfg(all(not(feature = "rustls-tls"), feature = "openssl-tls"))] let https = hyper_openssl::HttpsConnector::new().map_err(Error::CreateOpensslHttpsConnector)?; diff --git a/kube-client/src/client/auth/oidc.rs b/kube-client/src/client/auth/oidc.rs index 49bde1225..820dbf701 100644 --- a/kube-client/src/client/auth/oidc.rs +++ b/kube-client/src/client/auth/oidc.rs @@ -187,7 +187,7 @@ impl Oidc { /// Retrieve the ID token. If the stored ID token is or will soon be expired, try refreshing it first. pub async fn id_token(&mut self) -> Result { if self.token_valid()? { - return Ok(self.id_token.expose_secret().clone()); + return Ok(self.id_token.expose_secret().to_string()); } let id_token = self.refresher.as_mut().map_err(|e| e.clone())?.id_token().await?; @@ -313,13 +313,19 @@ impl Refresher { .install_default() .unwrap(); - #[cfg(feature = "rustls-tls")] + #[cfg(all(feature = "rustls-tls", not(feature = "webpki-roots")))] let https = hyper_rustls::HttpsConnectorBuilder::new() .with_native_roots() .map_err(|_| errors::RefreshInitError::NoValidNativeRootCA)? .https_only() .enable_http1() .build(); + #[cfg(all(feature = "rustls-tls", feature = "webpki-roots"))] + let https = hyper_rustls::HttpsConnectorBuilder::new() + .with_webpki_roots() + .https_only() + .enable_http1() + .build(); #[cfg(all(not(feature = "rustls-tls"), feature = "openssl-tls"))] let https = hyper_openssl::HttpsConnector::new()?; @@ -394,8 +400,8 @@ impl Refresher { } AuthStyle::Params => { params.extend([ - ("client_id", self.client_id.expose_secret().as_str()), - ("client_secret", self.client_secret.expose_secret().as_str()), + ("client_id", self.client_id.expose_secret()), + ("client_secret", self.client_secret.expose_secret()), ]); } }; diff --git a/kube-client/src/client/builder.rs b/kube-client/src/client/builder.rs index f7c631896..05edf80b4 100644 --- a/kube-client/src/client/builder.rs +++ b/kube-client/src/client/builder.rs @@ -213,6 +213,7 @@ where } }), ) + .map_err(BoxError::from) .service(client); Ok(ClientBuilder::new( diff --git a/kube-client/src/client/client_ext.rs b/kube-client/src/client/client_ext.rs index f0de2f5b2..ced2df626 100644 --- a/kube-client/src/client/client_ext.rs +++ b/kube-client/src/client/client_ext.rs @@ -140,7 +140,7 @@ where ObjectReference { api_version: K::api_version(&dt).to_string().into(), namespace: namespace.into(), - name: self.name.clone(), + name: Some(self.name.clone()), kind: K::kind(&dt).to_string().into(), ..Default::default() } @@ -243,8 +243,7 @@ pub enum NamespaceError { /// ## Example /// /// ```no_run -/// # use k8s_openapi::api::core::v1::Pod; -/// # use k8s_openapi::api::core::v1::Service; +/// # use k8s_openapi::api::core::v1::{Pod, Service}; /// # use kube::client::scope::{Namespace, Cluster}; /// # use kube::prelude::*; /// # use kube::api::ListParams; @@ -295,11 +294,10 @@ impl Client { /// /// ```no_run /// # use k8s_openapi::api::rbac::v1::ClusterRole; - /// # use k8s_openapi::api::core::v1::Service; - /// # use k8s_openapi::api::core::v1::Secret; - /// # use k8s_openapi::api::core::v1::ObjectReference; - /// # use k8s_openapi::api::core::v1::LocalObjectReference; - /// # use k8s_openapi::api::core::v1::{Node, Pod}; + /// # use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference; + /// # use k8s_openapi::api::core::v1::{ObjectReference, LocalObjectReference}; + /// # use k8s_openapi::api::core::v1::{Node, Pod, Service, Secret}; + /// # use kube::client::scope::NamespacedRef; /// # use kube::api::GetParams; /// # use kube::prelude::*; /// # use kube::api::DynamicObject; @@ -333,7 +331,7 @@ impl Client { /// .image_pull_secrets /// .unwrap_or_default() /// .get(0) - /// .unwrap_or(&LocalObjectReference{name: Some("pull_secret".into())}); + /// .unwrap_or(&LocalObjectReference{name: "pull_secret".into()}); /// let secret: Secret = client.fetch(&secret_ref.within(pod.namespace())).await?; /// # Ok(()) /// # } diff --git a/kube-client/src/client/config_ext.rs b/kube-client/src/client/config_ext.rs index 2861ad3c8..0874e0f00 100644 --- a/kube-client/src/client/config_ext.rs +++ b/kube-client/src/client/config_ext.rs @@ -167,14 +167,14 @@ impl ConfigExt for Config { fn auth_layer(&self) -> Result> { Ok(match Auth::try_from(&self.auth_info).map_err(Error::Auth)? { Auth::None => None, - Auth::Basic(user, pass) => Some(AuthLayer(Either::A( + Auth::Basic(user, pass) => Some(AuthLayer(Either::Left( AddAuthorizationLayer::basic(&user, pass.expose_secret()).as_sensitive(true), ))), - Auth::Bearer(token) => Some(AuthLayer(Either::A( + Auth::Bearer(token) => Some(AuthLayer(Either::Left( AddAuthorizationLayer::bearer(token.expose_secret()).as_sensitive(true), ))), Auth::RefreshableToken(refreshable) => { - Some(AuthLayer(Either::B(AsyncFilterLayer::new(refreshable)))) + Some(AuthLayer(Either::Right(AsyncFilterLayer::new(refreshable)))) } Auth::Certificate(_client_certificate_data, _client_key_data) => None, }) diff --git a/kube-client/src/client/mod.rs b/kube-client/src/client/mod.rs index 768a06100..cd6c9ac9e 100644 --- a/kube-client/src/client/mod.rs +++ b/kube-client/src/client/mod.rs @@ -8,7 +8,7 @@ //! The [`Client`] can also be used with [`Discovery`](crate::Discovery) to dynamically //! retrieve the resources served by the kubernetes API. use either::{Either, Left, Right}; -use futures::{AsyncBufRead, StreamExt, TryStream, TryStreamExt}; +use futures::{future::BoxFuture, AsyncBufRead, StreamExt, TryStream, TryStreamExt}; use http::{self, Request, Response}; use http_body_util::BodyExt; #[cfg(feature = "ws")] use hyper_util::rt::TokioIo; @@ -75,8 +75,8 @@ pub use builder::{ClientBuilder, DynBody}; #[derive(Clone)] pub struct Client { // - `Buffer` for cheap clone - // - `BoxService` for dynamic response future type - inner: Buffer, Response, BoxError>, Request>, + // - `BoxFuture` for dynamic response future type + inner: Buffer, BoxFuture<'static, Result, BoxError>>>, default_ns: String, } @@ -102,13 +102,14 @@ impl Client { /// ```rust /// # async fn doc() -> Result<(), Box> { /// use kube::{client::ConfigExt, Client, Config}; - /// use tower::ServiceBuilder; + /// use tower::{BoxError, ServiceBuilder}; /// use hyper_util::rt::TokioExecutor; /// /// let config = Config::infer().await?; /// let service = ServiceBuilder::new() /// .layer(config.base_uri_layer()) /// .option_layer(config.auth_layer()?) + /// .map_err(BoxError::from) /// .service(hyper_util::client::legacy::Client::builder(TokioExecutor::new()).build_http()); /// let client = Client::new(service, config.default_namespace); /// # Ok(()) diff --git a/kube-client/src/client/tls.rs b/kube-client/src/client/tls.rs index 136a9bfa0..25bdb737e 100644 --- a/kube-client/src/client/tls.rs +++ b/kube-client/src/client/tls.rs @@ -55,9 +55,18 @@ pub mod rustls_tls { let config_builder = if let Some(certs) = root_certs { ClientConfig::builder().with_root_certificates(root_store(certs)?) } else { - ClientConfig::builder() - .with_native_roots() - .map_err(Error::NoValidNativeRootCA)? + #[cfg(feature = "webpki-roots")] + { + // Use WebPKI roots. + ClientConfig::builder().with_webpki_roots() + } + #[cfg(not(feature = "webpki-roots"))] + { + // Use native roots. This will panic on Android and iOS. + ClientConfig::builder() + .with_native_roots() + .map_err(Error::NoValidNativeRootCA)? + } }; let mut client_config = if let Some((chain, pkey)) = identity_pem.map(client_auth).transpose()? { diff --git a/kube-client/src/config/file_config.rs b/kube-client/src/config/file_config.rs index e6bf934c1..6ef99cafc 100644 --- a/kube-client/src/config/file_config.rs +++ b/kube-client/src/config/file_config.rs @@ -149,7 +149,7 @@ where D: Deserializer<'de>, { match Option::::deserialize(deserializer) { - Ok(Some(secret)) => Ok(Some(SecretString::new(secret))), + Ok(Some(secret)) => Ok(Some(SecretString::new(secret.into()))), Ok(None) => Ok(None), Err(e) => Err(e), } @@ -533,10 +533,7 @@ impl AuthInfo { // TODO Shouldn't error when `self.client_key_data.is_none() && self.client_key.is_none()` load_from_base64_or_file( - &self - .client_key_data - .as_ref() - .map(|secret| secret.expose_secret().as_str()), + &self.client_key_data.as_ref().map(|secret| secret.expose_secret()), &self.client_key, ) .map_err(KubeconfigError::LoadClientKey) @@ -664,7 +661,6 @@ mod tests { use super::*; use serde_json::{json, Value}; - use std::str::FromStr; #[test] fn kubeconfig_merge() { @@ -673,7 +669,7 @@ mod tests { auth_infos: vec![NamedAuthInfo { name: "red-user".into(), auth_info: Some(AuthInfo { - token: Some(SecretString::from_str("first-token").unwrap()), + token: Some(SecretString::new("first-token".into())), ..Default::default() }), }], @@ -685,7 +681,7 @@ mod tests { NamedAuthInfo { name: "red-user".into(), auth_info: Some(AuthInfo { - token: Some(SecretString::from_str("second-token").unwrap()), + token: Some(SecretString::new("second-token".into())), username: Some("red-user".into()), ..Default::default() }), @@ -693,7 +689,7 @@ mod tests { NamedAuthInfo { name: "green-user".into(), auth_info: Some(AuthInfo { - token: Some(SecretString::from_str("new-token").unwrap()), + token: Some(SecretString::new("new-token".into())), ..Default::default() }), }, @@ -713,8 +709,8 @@ mod tests { .unwrap() .token .as_ref() - .map(|t| t.expose_secret().to_string()), - Some("first-token".to_string()) + .map(|t| t.expose_secret()), + Some("first-token") ); // Even if it's not conflicting assert_eq!(merged.auth_infos[0].auth_info.as_ref().unwrap().username, None); @@ -910,7 +906,7 @@ password: kube_rs let authinfo_debug_output = format!("{authinfo:?}"); let expected_output = "AuthInfo { \ username: Some(\"user\"), \ - password: Some(Secret([REDACTED alloc::string::String])), \ + password: Some(SecretBox([REDACTED])), \ token: None, token_file: None, client_certificate: None, \ client_certificate_data: None, client_key: None, \ client_key_data: None, impersonate: None, \ diff --git a/kube-client/src/config/file_loader.rs b/kube-client/src/config/file_loader.rs index 152793b03..5bd02c8af 100644 --- a/kube-client/src/config/file_loader.rs +++ b/kube-client/src/config/file_loader.rs @@ -83,12 +83,21 @@ impl ConfigLoader { .ok_or_else(|| KubeconfigError::LoadClusterOfContext(cluster_name.clone()))?; let user_name = user.unwrap_or(¤t_context.user); + + // client-go doesn't fail on empty/missing user, so we don't either + // see https://github.com/kube-rs/kube/issues/1594 let mut user = config .auth_infos .iter() .find(|named_user| &named_user.name == user_name) .and_then(|named_user| named_user.auth_info.clone()) - .ok_or_else(|| KubeconfigError::FindUser(user_name.clone()))?; + .unwrap_or_else(|| { + // assuming that empty user is ok but if it's not empty user we should warn + if !user_name.is_empty() { + tracing::warn!("User {user_name} wasn't found in kubeconfig, using null auth"); + } + AuthInfo::default() + }); if let Some(exec_config) = &mut user.exec { if exec_config.provide_cluster_info { @@ -117,8 +126,6 @@ impl ConfigLoader { let nonempty = |o: Option| o.filter(|s| !s.is_empty()); if let Some(proxy) = nonempty(self.cluster.proxy_url.clone()) - .or_else(|| nonempty(std::env::var("HTTP_PROXY").ok())) - .or_else(|| nonempty(std::env::var("http_proxy").ok())) .or_else(|| nonempty(std::env::var("HTTPS_PROXY").ok())) .or_else(|| nonempty(std::env::var("https_proxy").ok())) { diff --git a/kube-client/src/config/mod.rs b/kube-client/src/config/mod.rs index a55c3e03f..20f25d21a 100644 --- a/kube-client/src/config/mod.rs +++ b/kube-client/src/config/mod.rs @@ -53,10 +53,6 @@ pub enum KubeconfigError { #[error("failed to load the cluster of context: {0}")] LoadClusterOfContext(String), - /// Failed to find named user - #[error("failed to find named user: {0}")] - FindUser(String), - /// Failed to find the path of kubeconfig #[error("failed to find the path of kubeconfig")] FindPath, diff --git a/kube-derive/README.md b/kube-derive/README.md index 38dfcf5ca..034748c7c 100644 --- a/kube-derive/README.md +++ b/kube-derive/README.md @@ -6,7 +6,7 @@ Add the `derive` feature to `kube`: ```toml [dependencies] -kube = { version = "0.94.2", feature = ["derive"] } +kube = { version = "0.95.0", feature = ["derive"] } ``` ## Usage diff --git a/kube-runtime/Cargo.toml b/kube-runtime/Cargo.toml index ea9332845..44c37cf9e 100644 --- a/kube-runtime/Cargo.toml +++ b/kube-runtime/Cargo.toml @@ -30,8 +30,8 @@ rust.unsafe_code = "forbid" [dependencies] futures = { workspace = true, features = ["async-await"] } -kube-client = { path = "../kube-client", version = "=0.94.2", default-features = false, features = ["jsonpatch", "client"] } -derivative.workspace = true +kube-client = { path = "../kube-client", version = "=0.95.0", default-features = false, features = ["jsonpatch", "client"] } +educe = { workspace = true, features = ["Clone", "Debug", "Hash", "PartialEq"] } serde.workspace = true ahash.workspace = true parking_lot.workspace = true diff --git a/kube-runtime/src/controller/mod.rs b/kube-runtime/src/controller/mod.rs index 3880d83fe..4a32dac36 100644 --- a/kube-runtime/src/controller/mod.rs +++ b/kube-runtime/src/controller/mod.rs @@ -12,7 +12,7 @@ use crate::{ watcher::{self, metadata_watcher, watcher, DefaultBackoff}, }; use backoff::backoff::Backoff; -use derivative::Derivative; +use educe::Educe; use futures::{ channel, future::{self, BoxFuture}, @@ -267,20 +267,21 @@ where /// NOTE: The reason is ignored for comparison purposes. This means that, for example, /// an object can only occupy one scheduler slot, even if it has been scheduled for multiple reasons. /// In this case, only *the first* reason is stored. -#[derive(Derivative)] -#[derivative( - Debug(bound = "K::DynamicType: Debug"), - Clone(bound = "K::DynamicType: Clone"), - PartialEq(bound = "K::DynamicType: PartialEq"), - Eq(bound = "K::DynamicType: Eq"), - Hash(bound = "K::DynamicType: Hash") +#[derive(Educe)] +#[educe( + Debug(bound("K::DynamicType: Debug")), + Clone(bound("K::DynamicType: Clone")), + PartialEq(bound("K::DynamicType: PartialEq")), + Hash(bound("K::DynamicType: Hash")) )] pub struct ReconcileRequest { pub obj_ref: ObjectRef, - #[derivative(PartialEq = "ignore", Hash = "ignore")] + #[educe(PartialEq(ignore), Hash(ignore))] pub reason: ReconcileReason, } +impl Eq for ReconcileRequest where K::DynamicType: Eq {} + impl From> for ReconcileRequest { fn from(obj_ref: ObjectRef) -> Self { ReconcileRequest { diff --git a/kube-runtime/src/lib.rs b/kube-runtime/src/lib.rs index aaae626fc..7d0d8512c 100644 --- a/kube-runtime/src/lib.rs +++ b/kube-runtime/src/lib.rs @@ -10,9 +10,11 @@ #![deny(clippy::all)] #![deny(clippy::pedantic)] -// Triggered by many derive macros (kube-derive, derivative) +// Triggered by many derive macros (kube-derive, educe) #![allow(clippy::default_trait_access)] #![allow(clippy::type_repetition_in_bounds)] +// Triggered by educe derives on enums +#![allow(clippy::used_underscore_binding)] // Triggered by Tokio macros #![allow(clippy::semicolon_if_nothing_returned)] // Triggered by nightly clippy on idiomatic code diff --git a/kube-runtime/src/reflector/dispatcher.rs b/kube-runtime/src/reflector/dispatcher.rs index 2db706a71..1060dab2b 100644 --- a/kube-runtime/src/reflector/dispatcher.rs +++ b/kube-runtime/src/reflector/dispatcher.rs @@ -4,7 +4,7 @@ use core::{ }; use std::{fmt::Debug, sync::Arc}; -use derivative::Derivative; +use educe::Educe; use futures::Stream; use pin_project::pin_project; use std::task::ready; @@ -14,8 +14,8 @@ use async_broadcast::{InactiveReceiver, Receiver, Sender}; use super::Lookup; -#[derive(Derivative)] -#[derivative(Debug(bound = "K: Debug, K::DynamicType: Debug"), Clone)] +#[derive(Educe)] +#[educe(Debug(bound("K: Debug, K::DynamicType: Debug")), Clone)] // A helper type that holds a broadcast transmitter and a broadcast receiver, // used to fan-out events from a root stream to multiple listeners. pub(crate) struct Dispatcher diff --git a/kube-runtime/src/reflector/object_ref.rs b/kube-runtime/src/reflector/object_ref.rs index 47e8b2d2f..a4db8473d 100644 --- a/kube-runtime/src/reflector/object_ref.rs +++ b/kube-runtime/src/reflector/object_ref.rs @@ -1,4 +1,4 @@ -use derivative::Derivative; +use educe::Educe; use k8s_openapi::{api::core::v1::ObjectReference, apimachinery::pkg::apis::meta::v1::OwnerReference}; #[cfg(doc)] use kube_client::core::ObjectMeta; use kube_client::{ @@ -98,13 +98,12 @@ impl Lookup for K { } } -#[derive(Derivative)] -#[derivative( - Debug(bound = "K::DynamicType: Debug"), - PartialEq(bound = "K::DynamicType: PartialEq"), - Eq(bound = "K::DynamicType: Eq"), - Hash(bound = "K::DynamicType: Hash"), - Clone(bound = "K::DynamicType: Clone") +#[derive(Educe)] +#[educe( + Debug(bound("K::DynamicType: Debug")), + PartialEq(bound("K::DynamicType: PartialEq")), + Hash(bound("K::DynamicType: Hash")), + Clone(bound("K::DynamicType: Clone")) )] /// A typed and namedspaced (if relevant) reference to a Kubernetes object /// @@ -141,10 +140,12 @@ pub struct ObjectRef { /// /// This is *not* considered when comparing objects, but may be used when converting to and from other representations, /// such as [`OwnerReference`] or [`ObjectReference`]. - #[derivative(Hash = "ignore", PartialEq = "ignore")] + #[educe(Hash(ignore), PartialEq(ignore))] pub extra: Extra, } +impl Eq for ObjectRef where K::DynamicType: Eq {} + /// Non-vital information about an object being referred to /// /// See [`ObjectRef::extra`]. diff --git a/kube-runtime/src/reflector/store.rs b/kube-runtime/src/reflector/store.rs index 343fd9602..f96ae6ec6 100644 --- a/kube-runtime/src/reflector/store.rs +++ b/kube-runtime/src/reflector/store.rs @@ -4,7 +4,7 @@ use crate::{ watcher, }; use ahash::AHashMap; -use derivative::Derivative; +use educe::Educe; use parking_lot::RwLock; use std::{fmt::Debug, hash::Hash, sync::Arc}; use thiserror::Error; @@ -179,8 +179,8 @@ where /// /// Cannot be constructed directly since one writer handle is required, /// use `Writer::as_reader()` instead. -#[derive(Derivative)] -#[derivative(Debug(bound = "K: Debug, K::DynamicType: Debug"), Clone)] +#[derive(Educe)] +#[educe(Debug(bound("K: Debug, K::DynamicType: Debug")), Clone)] pub struct Store where K::DynamicType: Hash + Eq, diff --git a/kube-runtime/src/scheduler.rs b/kube-runtime/src/scheduler.rs index 195b7a4ec..967a5e126 100644 --- a/kube-runtime/src/scheduler.rs +++ b/kube-runtime/src/scheduler.rs @@ -295,7 +295,7 @@ mod tests { use crate::utils::KubeRuntimeStreamExt; use super::{debounced_scheduler, scheduler, ScheduleRequest}; - use derivative::Derivative; + use educe::Educe; use futures::{channel::mpsc, future, poll, stream, FutureExt, SinkExt, StreamExt}; use std::{pin::pin, task::Poll}; use tokio::time::{advance, pause, sleep, Duration, Instant}; @@ -309,9 +309,9 @@ mod tests { } /// Message type that is always considered equal to itself - #[derive(Derivative, Eq, Clone, Debug)] - #[derivative(PartialEq, Hash)] - struct SingletonMessage(#[derivative(PartialEq = "ignore", Hash = "ignore")] u8); + #[derive(Educe, Eq, Clone, Debug)] + #[educe(PartialEq, Hash)] + struct SingletonMessage(#[educe(PartialEq(ignore), Hash(ignore))] u8); #[tokio::test] async fn scheduler_should_hold_and_release_items() { diff --git a/kube-runtime/src/utils/delayed_init.rs b/kube-runtime/src/utils/delayed_init.rs index 493947a23..7e273ae55 100644 --- a/kube-runtime/src/utils/delayed_init.rs +++ b/kube-runtime/src/utils/delayed_init.rs @@ -1,6 +1,5 @@ use std::{fmt::Debug, sync::Mutex, task::Poll}; -use derivative::Derivative; use futures::{channel, Future, FutureExt}; use thiserror::Error; use tracing::trace; @@ -26,8 +25,7 @@ impl Debug for Initializer { /// /// Can be considered equivalent to a [`channel::oneshot`] channel, except for that /// the value produced is retained for subsequent calls to [`Self::get`]. -#[derive(Derivative)] -#[derivative(Debug)] +#[derive(Debug)] pub struct DelayedInit { state: Mutex>, } diff --git a/kube-runtime/src/utils/event_flatten.rs b/kube-runtime/src/utils/event_decode.rs similarity index 90% rename from kube-runtime/src/utils/event_flatten.rs rename to kube-runtime/src/utils/event_decode.rs index 489b2103b..2a0085120 100644 --- a/kube-runtime/src/utils/event_flatten.rs +++ b/kube-runtime/src/utils/event_decode.rs @@ -9,17 +9,17 @@ use pin_project::pin_project; #[pin_project] /// Stream returned by the [`applied_objects`](super::WatchStreamExt::applied_objects) and [`touched_objects`](super::WatchStreamExt::touched_objects) method. #[must_use = "streams do nothing unless polled"] -pub struct EventFlatten { +pub struct EventDecode { #[pin] stream: St, emit_deleted: bool, } -impl>, K> EventFlatten { +impl>, K> EventDecode { pub(super) fn new(stream: St, emit_deleted: bool) -> Self { Self { stream, emit_deleted } } } -impl Stream for EventFlatten +impl Stream for EventDecode where St: Stream, Error>>, { @@ -50,11 +50,11 @@ where pub(crate) mod tests { use std::{pin::pin, task::Poll}; - use super::{Error, Event, EventFlatten}; + use super::{Error, Event, EventDecode}; use futures::{poll, stream, StreamExt}; #[tokio::test] - async fn watches_applies_uses_correct_eventflattened_stream() { + async fn watches_applies_uses_correct_stream() { let data = stream::iter([ Ok(Event::Apply(0)), Ok(Event::Apply(1)), @@ -65,7 +65,7 @@ pub(crate) mod tests { Err(Error::NoResourceVersion), Ok(Event::Apply(2)), ]); - let mut rx = pin!(EventFlatten::new(data, false)); + let mut rx = pin!(EventDecode::new(data, false)); assert!(matches!(poll!(rx.next()), Poll::Ready(Some(Ok(0))))); assert!(matches!(poll!(rx.next()), Poll::Ready(Some(Ok(1))))); // NB: no Deleted events here diff --git a/kube-runtime/src/utils/mod.rs b/kube-runtime/src/utils/mod.rs index 1be2bb540..74cc7cf2f 100644 --- a/kube-runtime/src/utils/mod.rs +++ b/kube-runtime/src/utils/mod.rs @@ -2,7 +2,7 @@ mod backoff_reset_timer; pub(crate) mod delayed_init; -mod event_flatten; +mod event_decode; mod event_modify; mod predicate; mod reflect; @@ -10,12 +10,18 @@ mod stream_backoff; mod watch_ext; pub use backoff_reset_timer::ResetTimerBackoff; -pub use event_flatten::EventFlatten; +pub use event_decode::EventDecode; pub use event_modify::EventModify; pub use predicate::{predicates, Predicate, PredicateFilter}; pub use reflect::Reflect; pub use stream_backoff::StreamBackoff; pub use watch_ext::WatchStreamExt; +/// Deprecated type alias for `EventDecode` +#[deprecated( + since = "0.96.0", + note = "renamed to by `EventDecode`. This alias will be removed in 0.100.0." +)] +pub use EventDecode as EventFlatten; use futures::{ stream::{self, Peekable}, diff --git a/kube-runtime/src/utils/watch_ext.rs b/kube-runtime/src/utils/watch_ext.rs index e25c02c19..67ebf0d0a 100644 --- a/kube-runtime/src/utils/watch_ext.rs +++ b/kube-runtime/src/utils/watch_ext.rs @@ -1,6 +1,6 @@ use crate::{ utils::{ - event_flatten::EventFlatten, + event_decode::EventDecode, event_modify::EventModify, predicate::{Predicate, PredicateFilter}, stream_backoff::StreamBackoff, @@ -36,24 +36,24 @@ pub trait WatchStreamExt: Stream { StreamBackoff::new(self, b) } - /// Flatten a [`watcher()`] stream into a stream of applied objects + /// Decode a [`watcher()`] stream into a stream of applied objects /// /// All Added/Modified events are passed through, and critical errors bubble up. - fn applied_objects(self) -> EventFlatten + fn applied_objects(self) -> EventDecode where Self: Stream, watcher::Error>> + Sized, { - EventFlatten::new(self, false) + EventDecode::new(self, false) } - /// Flatten a [`watcher()`] stream into a stream of touched objects + /// Decode a [`watcher()`] stream into a stream of touched objects /// /// All Added/Modified/Deleted events are passed through, and critical errors bubble up. - fn touched_objects(self) -> EventFlatten + fn touched_objects(self) -> EventDecode where Self: Stream, watcher::Error>> + Sized, { - EventFlatten::new(self, true) + EventDecode::new(self, true) } /// Modify elements of a [`watcher()`] stream. @@ -91,7 +91,7 @@ pub trait WatchStreamExt: Stream { EventModify::new(self, f) } - /// Filter out a flattened stream on [`predicates`](crate::predicates). + /// Filter a stream based on on [`predicates`](crate::predicates). /// /// This will filter out repeat calls where the predicate returns the same result. /// Common use case for this is to avoid repeat events for status updates diff --git a/kube-runtime/src/watcher.rs b/kube-runtime/src/watcher.rs index 63b1503ac..b7ef1b569 100644 --- a/kube-runtime/src/watcher.rs +++ b/kube-runtime/src/watcher.rs @@ -5,7 +5,7 @@ use crate::utils::ResetTimerBackoff; use async_trait::async_trait; use backoff::{backoff::Backoff, ExponentialBackoff}; -use derivative::Derivative; +use educe::Educe; use futures::{stream::BoxStream, Stream, StreamExt}; use kube_client::{ api::{ListParams, Resource, ResourceExt, VersionMatch, WatchEvent, WatchParams}, @@ -71,7 +71,10 @@ impl Event { /// /// `Deleted` objects are ignored, all objects mentioned by `Restarted` events are /// emitted individually. - #[deprecated(since = "0.92.0", note = "unnecessary to flatten a single object")] + #[deprecated( + since = "0.92.0", + note = "unnecessary to flatten a single object. This fn will be removed in 0.96.0." + )] pub fn into_iter_applied(self) -> impl Iterator { match self { Self::Apply(obj) | Self::InitApply(obj) => Some(obj), @@ -85,7 +88,10 @@ impl Event { /// Note that `Deleted` events may be missed when restarting the stream. Use finalizers /// or owner references instead if you care about cleaning up external resources after /// deleted objects. - #[deprecated(since = "0.92.0", note = "unnecessary to flatten a single object")] + #[deprecated( + since = "0.92.0", + note = "unnecessary to flatten a single object. This fn will be removed in 0.96.0." + )] pub fn into_iter_touched(self) -> impl Iterator { match self { Self::Apply(obj) | Self::Delete(obj) | Self::InitApply(obj) => Some(obj), @@ -121,8 +127,8 @@ impl Event { } } -#[derive(Derivative, Default)] -#[derivative(Debug)] +#[derive(Educe, Default)] +#[educe(Debug)] /// The internal finite state machine driving the [`watcher`] enum State { /// The Watcher is empty, and the next [`poll`](Stream::poll_next) will start the initial LIST to get all existing objects @@ -137,7 +143,7 @@ enum State { /// Kubernetes 1.27 Streaming Lists /// The initial watch is in progress InitialWatch { - #[derivative(Debug = "ignore")] + #[educe(Debug(ignore))] stream: BoxStream<'static, kube_client::Result>>, }, /// The initial LIST was successful, so we should move on to starting the actual watch. @@ -150,7 +156,7 @@ enum State { /// with `Empty`. Watching { resource_version: String, - #[derivative(Debug = "ignore")] + #[educe(Debug(ignore))] stream: BoxStream<'static, kube_client::Result>>, }, } @@ -710,8 +716,8 @@ where /// [`try_for_each`](futures::TryStreamExt::try_for_each) and [`try_concat`](futures::TryStreamExt::try_concat)) /// will terminate eagerly as soon as they receive an [`Err`]. /// -/// This is intended to provide a safe and atomic input interface for a state store like a [`reflector`]. -/// Direct users may want to flatten composite events via [`WatchStreamExt`]: +/// The events are intended to provide a safe input interface for a state store like a [`reflector`]. +/// Direct users may want to use [`WatchStreamExt`] for higher-level constructs. /// /// ```no_run /// use kube::{ @@ -773,8 +779,8 @@ pub fn watcher( /// [`try_for_each`](futures::TryStreamExt::try_for_each) and [`try_concat`](futures::TryStreamExt::try_concat)) /// will terminate eagerly as soon as they receive an [`Err`]. /// -/// This is intended to provide a safe and atomic input interface for a state store like a [`reflector`]. -/// Direct users may want to flatten composite events via [`WatchStreamExt`]: +/// The events are intended to provide a safe input interface for a state store like a [`reflector`]. +/// Direct users may want to use [`WatchStreamExt`] for higher-level constructs. /// /// ```no_run /// use kube::{ diff --git a/kube/Cargo.toml b/kube/Cargo.toml index ab5ee07eb..d56d470eb 100644 --- a/kube/Cargo.toml +++ b/kube/Cargo.toml @@ -37,6 +37,7 @@ unstable-runtime = ["kube-runtime/unstable-runtime", "runtime"] unstable-client = ["kube-client/unstable-client", "client"] socks5 = ["kube-client/socks5", "client"] http-proxy = ["kube-client/http-proxy", "client"] +webpki-roots = ["kube-client/webpki-roots", "client"] [package.metadata.docs.rs] features = ["client", "rustls-tls", "openssl-tls", "derive", "ws", "oauth", "jsonpatch", "admission", "runtime", "k8s-openapi/latest", "unstable-runtime", "socks5", "http-proxy"] @@ -47,10 +48,10 @@ rustdoc-args = ["--cfg", "docsrs"] workspace = true [dependencies] -kube-derive = { path = "../kube-derive", version = "=0.94.2", optional = true } -kube-core = { path = "../kube-core", version = "=0.94.2" } -kube-client = { path = "../kube-client", version = "=0.94.2", default-features = false, optional = true } -kube-runtime = { path = "../kube-runtime", version = "=0.94.2", optional = true} +kube-derive = { path = "../kube-derive", version = "=0.95.0", optional = true } +kube-core = { path = "../kube-core", version = "=0.95.0" } +kube-client = { path = "../kube-client", version = "=0.95.0", default-features = false, optional = true } +kube-runtime = { path = "../kube-runtime", version = "=0.95.0", optional = true} # Not used directly, but required by resolver 2.0 to ensure that the k8s-openapi dependency # is considered part of the "deps" graph rather than just the "dev-deps" graph k8s-openapi.workspace = true