From 251728e49d4ebf0416c0d9d84361176169990dc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kangwook=20Lee=20=28=EC=9D=B4=EA=B0=95=EC=9A=B1=29?= Date: Fri, 19 Aug 2022 16:33:09 +0900 Subject: [PATCH] Add `create_subresource` method to `Api` and `create_token_request` method to `Api` (#989) * Add create_subresource method to Request Signed-off-by: pbzweihander * Add create_subresource method to Api Signed-off-by: pbzweihander * Add create_token_request method to Api Signed-off-by: pbzweihander * Add test for Request::create_subresource Signed-off-by: pbzweihander * Add intergration test for Api::::create_token_request Signed-off-by: pbzweihander * Create and delete ServiceAccount for integration test Signed-off-by: pbzweihander Signed-off-by: pbzweihander --- kube-client/src/api/subresource.rs | 19 ++++++ kube-client/src/api/util/mod.rs | 98 +++++++++++++++++++++++++++++- kube-core/src/request.rs | 29 +++++++++ 3 files changed, 143 insertions(+), 3 deletions(-) diff --git a/kube-client/src/api/subresource.rs b/kube-client/src/api/subresource.rs index a7dbe5e3c..f1876737b 100644 --- a/kube-client/src/api/subresource.rs +++ b/kube-client/src/api/subresource.rs @@ -76,6 +76,25 @@ where self.client.request::(req).await } + /// Create an instance of the subresource + pub async fn create_subresource( + &self, + subresource_name: &str, + name: &str, + pp: &PostParams, + data: Vec, + ) -> Result + where + T: DeserializeOwned, + { + let mut req = self + .request + .create_subresource(subresource_name, name, pp, data) + .map_err(Error::BuildRequest)?; + req.extensions_mut().insert("create_subresource"); + self.client.request::(req).await + } + /// Patch an instance of the subresource pub async fn patch_subresource( &self, diff --git a/kube-client/src/api/util/mod.rs b/kube-client/src/api/util/mod.rs index c8b5cc6dd..b1983df69 100644 --- a/kube-client/src/api/util/mod.rs +++ b/kube-client/src/api/util/mod.rs @@ -2,8 +2,11 @@ use crate::{ api::{Api, Resource}, Error, Result, }; -use k8s_openapi::api::core::v1::Node; -use kube_core::util::Restart; +use k8s_openapi::api::{ + authentication::v1::TokenRequest, + core::v1::{Node, ServiceAccount}, +}; +use kube_core::{params::PostParams, util::Restart}; use serde::de::DeserializeOwned; k8s_openapi::k8s_if_ge_1_19! { @@ -38,6 +41,24 @@ impl Api { } } +impl Api { + /// Create a TokenRequest of a ServiceAccount + pub async fn create_token_request( + &self, + name: &str, + pp: &PostParams, + token_request: &TokenRequest, + ) -> Result { + let bytes = serde_json::to_vec(token_request).map_err(Error::SerdeError)?; + let mut req = self + .request + .create_subresource("token", name, pp, bytes) + .map_err(Error::BuildRequest)?; + req.extensions_mut().insert("create_token_request"); + self.client.request::(req).await + } +} + // Tests that require a cluster and the complete feature set // Can be run with `cargo test -p kube-client --lib -- --ignored` #[cfg(test)] @@ -47,7 +68,10 @@ mod test { api::{Api, DeleteParams, ListParams, PostParams}, Client, }; - use k8s_openapi::api::core::v1::Node; + use k8s_openapi::api::{ + authentication::v1::{TokenRequest, TokenRequestSpec, TokenReview, TokenReviewSpec}, + core::v1::{Node, ServiceAccount}, + }; use serde_json::json; #[tokio::test] @@ -81,4 +105,72 @@ mod test { nodes.delete(node_name, &DeleteParams::default()).await?; Ok(()) } + + #[tokio::test] + #[ignore] // requires a cluster + async fn create_token_request() -> Result<(), Box> { + let client = Client::try_default().await?; + + let serviceaccount_name = "fakesa"; + let serviceaccount_namespace = "default"; + let audiences = vec!["api".to_string()]; + + let serviceaccounts: Api = Api::namespaced(client.clone(), serviceaccount_namespace); + let tokenreviews: Api = Api::all(client); + + // Create ServiceAccount + let fake_sa = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "ServiceAccount", + "metadata": { + "name": serviceaccount_name, + }, + }))?; + serviceaccounts.create(&PostParams::default(), &fake_sa).await?; + + // Create TokenRequest + let tokenrequest = serviceaccounts + .create_token_request(serviceaccount_name, &PostParams::default(), &TokenRequest { + metadata: Default::default(), + spec: TokenRequestSpec { + audiences: audiences.clone(), + bound_object_ref: None, + expiration_seconds: None, + }, + status: None, + }) + .await?; + let token = tokenrequest.status.unwrap().token; + assert!(!token.is_empty()); + + // Check created token is valid with TokenReview + let tokenreview = tokenreviews + .create(&PostParams::default(), &TokenReview { + metadata: Default::default(), + spec: TokenReviewSpec { + audiences: Some(audiences.clone()), + token: Some(token), + }, + status: None, + }) + .await?; + let tokenreviewstatus = tokenreview.status.unwrap(); + assert_eq!(tokenreviewstatus.audiences, Some(audiences)); + assert_eq!(tokenreviewstatus.authenticated, Some(true)); + assert_eq!(tokenreviewstatus.error, None); + assert_eq!( + tokenreviewstatus.user.unwrap().username, + Some(format!( + "system:serviceaccount:{}:{}", + serviceaccount_namespace, serviceaccount_name + )) + ); + + // Cleanup ServiceAccount + serviceaccounts + .delete(serviceaccount_name, &DeleteParams::default()) + .await?; + + Ok(()) + } } diff --git a/kube-core/src/request.rs b/kube-core/src/request.rs index 892b15cca..d295d75c7 100644 --- a/kube-core/src/request.rs +++ b/kube-core/src/request.rs @@ -210,6 +210,24 @@ impl Request { req.body(vec![]).map_err(Error::BuildRequest) } + /// Create an instance of the subresource + pub fn create_subresource( + &self, + subresource_name: &str, + name: &str, + pp: &PostParams, + data: Vec, + ) -> Result>, Error> { + let target = format!("{}/{}/{}?", self.url_path, name, subresource_name); + let mut qp = form_urlencoded::Serializer::new(target); + if pp.dry_run { + qp.append_pair("dryRun", "All"); + } + let urlstr = qp.finish(); + let req = http::Request::post(urlstr).header(http::header::CONTENT_TYPE, JSON_MIME); + req.body(data).map_err(Error::BuildRequest) + } + /// Patch an instance of the subresource pub fn patch_subresource( &self, @@ -529,6 +547,17 @@ mod test { assert_eq!(req.method(), "PUT"); } + #[test] + fn create_subresource_path() { + let url = corev1::ServiceAccount::url_path(&(), Some("ns")); + let pp = PostParams::default(); + let data = vec![]; + let req = Request::new(url) + .create_subresource("token", "sa", &pp, data) + .unwrap(); + assert_eq!(req.uri(), "/api/v1/namespaces/ns/serviceaccounts/sa/token"); + } + // TODO: reinstate if we get scoping in trait //#[test] //#[should_panic]