diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index bfb6fe5ea..a87404135 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/rust:1.85.0-bullseye +FROM docker.io/rust:1.88.0-bullseye ENV DEBIAN_FRONTEND=noninteractive RUN apt update && apt upgrade -y @@ -41,7 +41,7 @@ RUN export K8S_VERSION="$(scurl https://dl.k8s.io/release/stable.txt)" \ RUN scurl https://raw.githubusercontent.com/rancher/k3d/main/install.sh \ | USE_SUDO=false K3D_INSTALL_DIR=$HOME/bin bash -RUN rustup component add clippy rls rust-src rustfmt +RUN rustup component add clippy rust-analysis rust-src rustfmt # Install cargo-deny ARG CARGO_DENY_VERSION=0.16.1 diff --git a/Cargo.toml b/Cargo.toml index fb8758264..ed8fd301e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ repository = "https://github.com/kube-rs/kube" readme = "README.md" license = "Apache-2.0" edition = "2024" -rust-version = "1.85.0" +rust-version = "1.88.0" [workspace.lints.rust] unsafe_code = "forbid" diff --git a/README.md b/README.md index 98d0d5c88..16ec0636f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # kube-rs [![Crates.io](https://img.shields.io/crates/v/kube.svg)](https://crates.io/crates/kube) -[![Rust 1.85](https://img.shields.io/badge/MSRV-1.85-dea584.svg)](https://github.com/rust-lang/rust/releases/tag/1.85.0) +[![Rust 1.88](https://img.shields.io/badge/MSRV-1.88-dea584.svg)](https://github.com/rust-lang/rust/releases/tag/1.88.0) [![Tested against Kubernetes v1.30 and above](https://img.shields.io/badge/MK8SV-v1.30-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) diff --git a/kube-client/src/client/auth/mod.rs b/kube-client/src/client/auth/mod.rs index 9709af368..1e776c284 100644 --- a/kube-client/src/client/auth/mod.rs +++ b/kube-client/src/client/auth/mod.rs @@ -356,7 +356,6 @@ impl TryFrom<&AuthInfo> for Auth { .transpose() .map_err(Error::MalformedTokenExpirationDate)?; - if let (Some(client_certificate_data), Some(client_key_data)) = (status.client_certificate_data, status.client_key_data) { @@ -431,14 +430,14 @@ fn token_from_gcp_provider(provider: &AuthProviderConfig) -> Result>() - .map_err(Error::MalformedTokenExpirationDate)?; - if Utc::now() + SIXTY_SEC < expiry_date { - return Ok(ProviderToken::GcpCommand(access_token.clone(), Some(expiry_date))); - } + if let Some(access_token) = provider.config.get("access-token") + && let Some(expiry) = provider.config.get("expiry") + { + let expiry_date = expiry + .parse::>() + .map_err(Error::MalformedTokenExpirationDate)?; + if Utc::now() + SIXTY_SEC < expiry_date { + return Ok(ProviderToken::GcpCommand(access_token.clone(), Some(expiry_date))); } } diff --git a/kube-client/src/config/file_config.rs b/kube-client/src/config/file_config.rs index 097a795b8..8a7374d4b 100644 --- a/kube-client/src/config/file_config.rs +++ b/kube-client/src/config/file_config.rs @@ -359,30 +359,29 @@ impl Kubeconfig { for mut config in kubeconfig_from_yaml(&data)? { if let Some(dir) = path.as_ref().parent() { for named in config.clusters.iter_mut() { - if let Some(cluster) = &mut named.cluster { - if let Some(path) = &cluster.certificate_authority { - if let Some(abs_path) = to_absolute(dir, path) { - cluster.certificate_authority = Some(abs_path); - } - } + if let Some(cluster) = &mut named.cluster + && let Some(path) = &cluster.certificate_authority + && let Some(abs_path) = to_absolute(dir, path) + { + cluster.certificate_authority = Some(abs_path); } } for named in config.auth_infos.iter_mut() { if let Some(auth_info) = &mut named.auth_info { - if let Some(path) = &auth_info.client_certificate { - if let Some(abs_path) = to_absolute(dir, path) { - auth_info.client_certificate = Some(abs_path); - } + if let Some(path) = &auth_info.client_certificate + && let Some(abs_path) = to_absolute(dir, path) + { + auth_info.client_certificate = Some(abs_path); } - if let Some(path) = &auth_info.client_key { - if let Some(abs_path) = to_absolute(dir, path) { - auth_info.client_key = Some(abs_path); - } + if let Some(path) = &auth_info.client_key + && let Some(abs_path) = to_absolute(dir, path) + { + auth_info.client_key = Some(abs_path); } - if let Some(path) = &auth_info.token_file { - if let Some(abs_path) = to_absolute(dir, path) { - auth_info.token_file = Some(abs_path); - } + if let Some(path) = &auth_info.token_file + && let Some(abs_path) = to_absolute(dir, path) + { + auth_info.token_file = Some(abs_path); } } } diff --git a/kube-client/src/config/file_loader.rs b/kube-client/src/config/file_loader.rs index fae1add49..617c267fe 100644 --- a/kube-client/src/config/file_loader.rs +++ b/kube-client/src/config/file_loader.rs @@ -97,10 +97,10 @@ impl ConfigLoader { AuthInfo::default() }; - if let Some(exec_config) = &mut auth_info.exec { - if exec_config.provide_cluster_info { - exec_config.cluster = Some((&cluster).try_into()?); - } + if let Some(exec_config) = &mut auth_info.exec + && exec_config.provide_cluster_info + { + exec_config.cluster = Some((&cluster).try_into()?); } Ok(ConfigLoader { diff --git a/kube-core/src/discovery.rs b/kube-core/src/discovery.rs index 4b87ca329..fe847c6d8 100644 --- a/kube-core/src/discovery.rs +++ b/kube-core/src/discovery.rs @@ -130,15 +130,14 @@ fn to_plural(word: &str) -> String { // Words ending in y that are preceded by a consonant will be pluralized by // replacing y with -ies (eg. puppies). - if word.ends_with('y') { - if let Some(c) = word.chars().nth(word.len() - 2) { - if !matches!(c, 'a' | 'e' | 'i' | 'o' | 'u') { - // Remove 'y' and add `ies` - let mut chars = word.chars(); - chars.next_back(); - return format!("{}ies", chars.as_str()); - } - } + if word.ends_with('y') + && let Some(c) = word.chars().nth(word.len() - 2) + && !matches!(c, 'a' | 'e' | 'i' | 'o' | 'u') + { + // Remove 'y' and add `ies` + let mut chars = word.chars(); + chars.next_back(); + return format!("{}ies", chars.as_str()); } // All other words will have "s" added to the end (eg. days). diff --git a/kube-core/src/params.rs b/kube-core/src/params.rs index 7c5dabda2..4724cc2e5 100644 --- a/kube-core/src/params.rs +++ b/kube-core/src/params.rs @@ -103,21 +103,19 @@ impl ListParams { } if let Some(continue_token) = &self.continue_token { qp.append_pair("continue", continue_token); - } else { - // When there's a continue token, we don't want to set resourceVersion - if let Some(rv) = &self.resource_version { - if rv != "0" || self.limit.is_none() { - qp.append_pair("resourceVersion", rv.as_str()); - - match &self.version_match { - None => {} - Some(VersionMatch::NotOlderThan) => { - qp.append_pair("resourceVersionMatch", "NotOlderThan"); - } - Some(VersionMatch::Exact) => { - qp.append_pair("resourceVersionMatch", "Exact"); - } - } + } else if let Some(rv) = &self.resource_version + && (rv != "0" || self.limit.is_none()) + { + // NB: When there's a continue token, we don't want to set resourceVersion + qp.append_pair("resourceVersion", rv.as_str()); + + match &self.version_match { + None => {} + Some(VersionMatch::NotOlderThan) => { + qp.append_pair("resourceVersionMatch", "NotOlderThan"); + } + Some(VersionMatch::Exact) => { + qp.append_pair("resourceVersionMatch", "Exact"); } } } diff --git a/kube-core/src/schema.rs b/kube-core/src/schema.rs index de25a812a..cefbe7684 100644 --- a/kube-core/src/schema.rs +++ b/kube-core/src/schema.rs @@ -316,15 +316,14 @@ impl Transform for StructuralSchemaRewriter { // check for maps without with properties (i.e. flattened maps) // and allow these to persist dynamically - if let Some(object) = &mut schema.object { - if !object.properties.is_empty() - && object.additional_properties.as_deref() == Some(&Schema::Bool(true)) - { - object.additional_properties = None; - schema - .extensions - .insert("x-kubernetes-preserve-unknown-fields".into(), true.into()); - } + if let Some(object) = &mut schema.object + && !object.properties.is_empty() + && object.additional_properties.as_deref() == Some(&Schema::Bool(true)) + { + object.additional_properties = None; + schema + .extensions + .insert("x-kubernetes-preserve-unknown-fields".into(), true.into()); } // As of version 1.30 Kubernetes does not support setting `uniqueItems` to `true`, @@ -336,10 +335,10 @@ impl Transform for StructuralSchemaRewriter { array.unique_items = None; } - if let Ok(schema) = serde_json::to_value(schema) { - if let Ok(transformed) = serde_json::from_value(schema) { - *transform_schema = transformed; - } + if let Ok(schema) = serde_json::to_value(schema) + && let Ok(transformed) = serde_json::from_value(schema) + { + *transform_schema = transformed; } } } @@ -432,18 +431,15 @@ fn hoist_subschema_properties( { let common_obj = common_obj.get_or_insert_with(Box::::default); - if let Some(variant_metadata) = variant_metadata { - // Move enum variant description from oneOf clause to its corresponding property - if let Some(description) = std::mem::take(&mut variant_metadata.description) { - if let Some(Schema::Object(variant_object)) = - only_item(variant_obj.properties.values_mut()) - { - let metadata = variant_object - .metadata - .get_or_insert_with(Box::::default); - metadata.description = Some(description); - } - } + // Move enum variant description from oneOf clause to its corresponding property + if let Some(variant_metadata) = variant_metadata + && let Some(description) = std::mem::take(&mut variant_metadata.description) + && let Some(Schema::Object(variant_object)) = only_item(variant_obj.properties.values_mut()) + { + let metadata = variant_object + .metadata + .get_or_insert_with(Box::::default); + metadata.description = Some(description); } // Move all properties diff --git a/kube-derive/src/custom_resource.rs b/kube-derive/src/custom_resource.rs index 5b156cb6c..0d66d6b30 100644 --- a/kube-derive/src/custom_resource.rs +++ b/kube-derive/src/custom_resource.rs @@ -77,14 +77,12 @@ struct KVTuple(String, String); impl FromMeta for KVTuple { fn from_list(items: &[darling::ast::NestedMeta]) -> darling::Result { - if items.len() == 2 { - if let ( - darling::ast::NestedMeta::Lit(syn::Lit::Str(key)), - darling::ast::NestedMeta::Lit(syn::Lit::Str(value)), - ) = (&items[0], &items[1]) - { - return Ok(KVTuple(key.value(), value.value())); - } + if let [ + darling::ast::NestedMeta::Lit(syn::Lit::Str(key)), + darling::ast::NestedMeta::Lit(syn::Lit::Str(value)), + ] = items + { + return Ok(KVTuple(key.value(), value.value())); } Err(darling::Error::unsupported_format( @@ -345,17 +343,17 @@ impl FromMeta for KubeRootMeta { const NOT_ALLOWED_ATTRIBUTES: [&str; 3] = ["derive", "serde", "schemars"]; let meta = syn::parse_str::(value)?; - if let Some(ident) = meta.path().get_ident() { - if NOT_ALLOWED_ATTRIBUTES.iter().any(|el| ident == el) { - if ident == "derive" { - return Err(darling::Error::custom( - r#"#[derive(CustomResource)] `kube(attr = "...")` does not support to set derives, you likely want to use `kube(derive = "...")`."#, - )); - } - return Err(darling::Error::custom(format!( - r#"#[derive(CustomResource)] `kube(attr = "...")` does not support to set the attributes {NOT_ALLOWED_ATTRIBUTES:?} as they might lead to unexpected behaviour.`"#, - ))); + if let Some(ident) = meta.path().get_ident() + && NOT_ALLOWED_ATTRIBUTES.iter().any(|el| ident == el) + { + if ident == "derive" { + return Err(darling::Error::custom( + r#"#[derive(CustomResource)] `kube(attr = "...")` does not support to set derives, you likely want to use `kube(derive = "...")`."#, + )); } + return Err(darling::Error::custom(format!( + r#"#[derive(CustomResource)] `kube(attr = "...")` does not support to set the attributes {NOT_ALLOWED_ATTRIBUTES:?} as they might lead to unexpected behaviour.`"#, + ))); } Ok(Self(meta)) @@ -909,15 +907,14 @@ fn to_plural(word: &str) -> String { // Words ending in y that are preceded by a consonant will be pluralized by // replacing y with -ies (eg. puppies). - if word.ends_with('y') { - if let Some(c) = word.chars().nth(word.len() - 2) { - if !matches!(c, 'a' | 'e' | 'i' | 'o' | 'u') { - // Remove 'y' and add `ies` - let mut chars = word.chars(); - chars.next_back(); - return format!("{}ies", chars.as_str()); - } - } + if word.ends_with('y') + && let Some(c) = word.chars().nth(word.len() - 2) + && !matches!(c, 'a' | 'e' | 'i' | 'o' | 'u') + { + // Remove 'y' and add `ies` + let mut chars = word.chars(); + chars.next_back(); + return format!("{}ies", chars.as_str()); } // All other words will have "s" added to the end (eg. days). @@ -974,7 +971,6 @@ mod tests { } } - #[test] fn test_derive_crd() { let path = env::current_dir().unwrap().join("tests").join("crd_enum_test.rs"); diff --git a/kube-runtime/src/utils/backoff_reset_timer.rs b/kube-runtime/src/utils/backoff_reset_timer.rs index e18817c24..513322666 100644 --- a/kube-runtime/src/utils/backoff_reset_timer.rs +++ b/kube-runtime/src/utils/backoff_reset_timer.rs @@ -33,15 +33,15 @@ impl Iterator for ResetTimerBackoff { type Item = Duration; fn next(&mut self) -> Option { - if let Some(last_backoff) = self.last_backoff { - if tokio::time::Instant::now().into_std() > last_backoff + self.reset_duration { - tracing::debug!( - ?last_backoff, - reset_duration = ?self.reset_duration, - "Resetting backoff, since reset duration has expired" - ); - self.backoff.reset(); - } + if let Some(last_backoff) = self.last_backoff + && tokio::time::Instant::now().into_std() > last_backoff + self.reset_duration + { + tracing::debug!( + ?last_backoff, + reset_duration = ?self.reset_duration, + "Resetting backoff, since reset duration has expired" + ); + self.backoff.reset(); } self.last_backoff = Some(tokio::time::Instant::now().into_std()); self.backoff.next() diff --git a/kube-runtime/src/wait.rs b/kube-runtime/src/wait.rs index dd8eb9e17..5e8eb625a 100644 --- a/kube-runtime/src/wait.rs +++ b/kube-runtime/src/wait.rs @@ -79,14 +79,12 @@ where /// use k8s_openapi::api::core::v1::Pod; /// fn my_custom_condition(my_cond: &str) -> impl Condition + '_ { /// move |obj: Option<&Pod>| { -/// if let Some(pod) = &obj { -/// if let Some(status) = &pod.status { -/// if let Some(conds) = &status.conditions { -/// if let Some(pcond) = conds.iter().find(|c| c.type_ == my_cond) { -/// return pcond.status == "True"; -/// } -/// } -/// } +/// if let Some(pod) = &obj +/// && let Some(status) = &pod.status +/// && let Some(conds) = &status.conditions +/// && let Some(pcond) = conds.iter().find(|c| c.type_ == my_cond) +/// { +/// return pcond.status == "True"; /// } /// false /// } @@ -196,14 +194,12 @@ pub mod conditions { #[must_use] pub fn is_crd_established() -> impl Condition { |obj: Option<&CustomResourceDefinition>| { - if let Some(o) = obj { - if let Some(s) = &o.status { - if let Some(conds) = &s.conditions { - if let Some(pcond) = conds.iter().find(|c| c.type_ == "Established") { - return pcond.status == "True"; - } - } - } + if let Some(o) = obj + && let Some(s) = &o.status + && let Some(conds) = &s.conditions + && let Some(pcond) = conds.iter().find(|c| c.type_ == "Established") + { + return pcond.status == "True"; } false } @@ -213,12 +209,11 @@ pub mod conditions { #[must_use] pub fn is_pod_running() -> impl Condition { |obj: Option<&Pod>| { - if let Some(pod) = &obj { - if let Some(status) = &pod.status { - if let Some(phase) = &status.phase { - return phase == "Running"; - } - } + if let Some(pod) = &obj + && let Some(status) = &pod.status + && let Some(phase) = &status.phase + { + return phase == "Running"; } false } @@ -228,14 +223,12 @@ pub mod conditions { #[must_use] pub fn is_job_completed() -> impl Condition { |obj: Option<&Job>| { - if let Some(job) = &obj { - if let Some(s) = &job.status { - if let Some(conds) = &s.conditions { - if let Some(pcond) = conds.iter().find(|c| c.type_ == "Complete") { - return pcond.status == "True"; - } - } - } + if let Some(job) = &obj + && let Some(s) = &job.status + && let Some(conds) = &s.conditions + && let Some(pcond) = conds.iter().find(|c| c.type_ == "Complete") + { + return pcond.status == "True"; } false } @@ -248,16 +241,14 @@ pub mod conditions { #[must_use] pub fn is_deployment_completed() -> impl Condition { |obj: Option<&Deployment>| { - if let Some(depl) = &obj { - if let Some(s) = &depl.status { - if let Some(conds) = &s.conditions { - if let Some(dcond) = conds.iter().find(|c| { - c.type_ == "Progressing" && c.reason == Some("NewReplicaSetAvailable".to_string()) - }) { - return dcond.status == "True"; - } - } - } + if let Some(depl) = &obj + && let Some(s) = &depl.status + && let Some(conds) = &s.conditions + && let Some(dcond) = conds.iter().find(|c| { + c.type_ == "Progressing" && c.reason == Some("NewReplicaSetAvailable".to_string()) + }) + { + return dcond.status == "True"; } false } @@ -267,20 +258,19 @@ pub mod conditions { #[must_use] pub fn is_service_loadbalancer_provisioned() -> impl Condition { |obj: Option<&Service>| { - if let Some(svc) = &obj { + if let Some(svc) = &obj + && let Some(spec) = &svc.spec + { // ignore services that are not type LoadBalancer (return true immediately) - if let Some(spec) = &svc.spec { - if spec.type_ != Some("LoadBalancer".to_string()) { - return true; - } - // carry on if this is a LoadBalancer service - if let Some(s) = &svc.status { - if let Some(lbs) = &s.load_balancer { - if let Some(ings) = &lbs.ingress { - return ings.iter().all(|ip| ip.ip.is_some() || ip.hostname.is_some()); - } - } - } + if spec.type_ != Some("LoadBalancer".to_string()) { + return true; + } + // carry on if this is a LoadBalancer service + if let Some(s) = &svc.status + && let Some(lbs) = &s.load_balancer + && let Some(ings) = &lbs.ingress + { + return ings.iter().all(|ip| ip.ip.is_some() || ip.hostname.is_some()); } } false @@ -291,14 +281,12 @@ pub mod conditions { #[must_use] pub fn is_ingress_provisioned() -> impl Condition { |obj: Option<&Ingress>| { - if let Some(ing) = &obj { - if let Some(s) = &ing.status { - if let Some(lbs) = &s.load_balancer { - if let Some(ings) = &lbs.ingress { - return ings.iter().all(|ip| ip.ip.is_some() || ip.hostname.is_some()); - } - } - } + if let Some(ing) = &obj + && let Some(s) = &ing.status + && let Some(lbs) = &s.load_balancer + && let Some(ings) = &lbs.ingress + { + return ings.iter().all(|ip| ip.ip.is_some() || ip.hostname.is_some()); } false } diff --git a/kube-runtime/src/watcher.rs b/kube-runtime/src/watcher.rs index 0343cb4e1..9cd2c3240 100644 --- a/kube-runtime/src/watcher.rs +++ b/kube-runtime/src/watcher.rs @@ -491,11 +491,11 @@ where }); } // check if we need to perform more pages - if continue_token.is_none() { - if let Some(resource_version) = last_bookmark { - // we have drained the last page - move on to next stage - return (Some(Ok(Event::InitDone)), State::InitListed { resource_version }); - } + if continue_token.is_none() + && let Some(resource_version) = last_bookmark + { + // we have drained the last page - move on to next stage + return (Some(Ok(Event::InitDone)), State::InitListed { resource_version }); } let mut lp = wc.to_list_params(); lp.continue_token = continue_token; diff --git a/kube/src/lib.rs b/kube/src/lib.rs index 17485b7c7..2c23ba46c 100644 --- a/kube/src/lib.rs +++ b/kube/src/lib.rs @@ -526,14 +526,12 @@ mod test { // TODO: remove these once we can write these functions generically fn is_each_container_ready() -> impl Condition { |obj: Option<&Pod>| { - if let Some(o) = obj { - if let Some(s) = &o.status { - if let Some(conds) = &s.conditions { - if let Some(pcond) = conds.iter().find(|c| c.type_ == "ContainersReady") { - return pcond.status == "True"; - } - } - } + if let Some(o) = obj + && let Some(s) = &o.status + && let Some(conds) = &s.conditions + && let Some(pcond) = conds.iter().find(|c| c.type_ == "ContainersReady") + { + return pcond.status == "True"; } false }