diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 8ae0e5fe1..8701d864a 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -30,7 +30,7 @@ assert-json-diff = "2.0.1" garde = { version = "0.16.1", default-features = false, features = ["derive"] } anyhow = "1.0.44" futures = "0.3.17" -jsonpath_lib = "0.3.0" +jsonpath-rust = "0.3.4" kube = { path = "../kube", version = "^0.87.1", default-features = false, features = ["admission"] } kube-derive = { path = "../kube-derive", version = "^0.87.1", default-features = false } # only needed to opt out of schema k8s-openapi = { version = "0.20.0", default-features = false } diff --git a/examples/dynamic_jsonpath.rs b/examples/dynamic_jsonpath.rs index 347b1cbbb..09994f555 100644 --- a/examples/dynamic_jsonpath.rs +++ b/examples/dynamic_jsonpath.rs @@ -1,3 +1,5 @@ +use anyhow::{Context, Error}; +use jsonpath_rust::JsonPathInst; use k8s_openapi::api::core::v1::Pod; use kube::{ api::{Api, ListParams}, @@ -13,11 +15,18 @@ async fn main() -> anyhow::Result<()> { // Equivalent to `kubectl get pods --all-namespace \ // -o jsonpath='{.items[*].spec.containers[*].image}'` let field_selector = std::env::var("FIELD_SELECTOR").unwrap_or_default(); - let jsonpath = format!( - "{}{}", - "$", - std::env::var("JSONPATH").unwrap_or_else(|_| ".items[*].spec.containers[*].image".into()) - ); + let jsonpath = { + let path = std::env::var("JSONPATH").unwrap_or_else(|_| ".items[*].spec.containers[*].image".into()); + format!("${path}") + .parse::() + .map_err(Error::msg) + .with_context(|| { + format!( + "Failed to parse 'JSONPATH' value as a JsonPath expression.\n + Got: {path}" + ) + })? + }; let pods: Api = Api::::all(client); let list_params = ListParams::default().fields(&field_selector); @@ -25,7 +34,8 @@ async fn main() -> anyhow::Result<()> { // Use the given JSONPATH to filter the ObjectList let list_json = serde_json::to_value(&list)?; - let res = jsonpath_lib::select(&list_json, &jsonpath).unwrap(); - info!("\t\t {:?}", res); + for res in jsonpath.find_slice(&list_json) { + info!("\t\t {}", *res); + } Ok(()) } diff --git a/kube-client/Cargo.toml b/kube-client/Cargo.toml index c52b8fd80..9ed10cbf7 100644 --- a/kube-client/Cargo.toml +++ b/kube-client/Cargo.toml @@ -23,7 +23,7 @@ ws = ["client", "tokio-tungstenite", "rand", "kube-core/ws", "tokio/macros"] oauth = ["client", "tame-oauth"] oidc = ["client", "form_urlencoded"] gzip = ["client", "tower-http/decompression-gzip"] -client = ["config", "__non_core", "hyper", "http-body", "tower", "tower-http", "hyper-timeout", "pin-project", "chrono", "jsonpath_lib", "bytes", "futures", "tokio", "tokio-util", "either"] +client = ["config", "__non_core", "hyper", "http-body", "tower", "tower-http", "hyper-timeout", "pin-project", "chrono", "jsonpath-rust", "bytes", "futures", "tokio", "tokio-util", "either"] jsonpatch = ["kube-core/jsonpatch"] admission = ["kube-core/admission"] config = ["__non_core", "pem", "home"] @@ -56,7 +56,7 @@ rustls-pemfile = { version = "1.0.0", optional = true } bytes = { version = "1.1.0", optional = true } tokio = { version = "1.14.0", features = ["time", "signal", "sync"], optional = true } kube-core = { path = "../kube-core", version = "=0.87.1" } -jsonpath_lib = { version = "0.3.0", optional = true } +jsonpath-rust = { version = "0.3.4", optional = true } tokio-util = { version = "0.7.0", optional = true, features = ["io", "codec"] } hyper = { version = "0.14.13", optional = true, features = ["client", "http1", "stream", "tcp"] } hyper-rustls = { version = "0.24.0", optional = true } diff --git a/kube-client/src/client/auth/mod.rs b/kube-client/src/client/auth/mod.rs index 3c5a42933..74515a483 100644 --- a/kube-client/src/client/auth/mod.rs +++ b/kube-client/src/client/auth/mod.rs @@ -10,7 +10,7 @@ use http::{ header::{InvalidHeaderValue, AUTHORIZATION}, HeaderValue, Request, }; -use jsonpath_lib::select as jsonpath_select; +use jsonpath_rust::JsonPathInst; use secrecy::{ExposeSecret, SecretString}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -440,9 +440,9 @@ fn token_from_gcp_provider(provider: &AuthProviderConfig) -> Result>() .map_err(Error::MalformedTokenExpirationDate)?; @@ -474,22 +474,32 @@ fn token_from_gcp_provider(provider: &AuthProviderConfig) -> Result Result { - let pure_path = path.trim_matches(|c| c == '"' || c == '{' || c == '}'); - match jsonpath_select(json, &format!("${pure_path}")) { - Ok(v) if !v.is_empty() => { - if let serde_json::Value::String(res) = v[0] { - Ok(res.clone()) - } else { - Err(Error::AuthExec(format!( - "Target value at {pure_path:} is not a string" - ))) - } - } - - Err(e) => Err(Error::AuthExec(format!("Could not extract JSON value: {e:}"))), +fn extract_value(json: &serde_json::Value, context: &str, path: &str) -> Result { + let parsed_path = path + .trim_matches(|c| c == '"' || c == '{' || c == '}') + .parse::() + .map_err(|err| { + Error::AuthExec(format!( + "Failed to parse {context:?} as a JsonPath: {path}\n + Error: {err}" + )) + })?; + + let res = parsed_path.find_slice(json); + + let Some(res) = res.into_iter().next() else { + return Err(Error::AuthExec(format!( + "Target {context:?} value {path:?} not found" + ))); + }; - _ => Err(Error::AuthExec(format!("Target value {pure_path:} not found"))), + if let Some(val) = res.as_str() { + Ok(val.to_owned()) + } else { + Err(Error::AuthExec(format!( + "Target {:?} value {:?} is not a string: {:?}", + context, path, *res + ))) } }