Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add create_subresource method to Api and create_token_request method to Api<ServiceAccount> #989

Merged
merged 6 commits into from
Aug 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions kube-client/src/api/subresource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,25 @@ where
self.client.request::<K>(req).await
}

/// Create an instance of the subresource
pub async fn create_subresource<T>(
&self,
subresource_name: &str,
name: &str,
pp: &PostParams,
data: Vec<u8>,
) -> Result<T>
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::<T>(req).await
}

/// Patch an instance of the subresource
pub async fn patch_subresource<P: serde::Serialize + Debug>(
&self,
Expand Down
98 changes: 95 additions & 3 deletions kube-client/src/api/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand Down Expand Up @@ -38,6 +41,24 @@ impl Api<Node> {
}
}

impl Api<ServiceAccount> {
/// Create a TokenRequest of a ServiceAccount
pub async fn create_token_request(
clux marked this conversation as resolved.
Show resolved Hide resolved
&self,
name: &str,
pp: &PostParams,
token_request: &TokenRequest,
) -> Result<TokenRequest> {
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::<TokenRequest>(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)]
Expand All @@ -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]
Expand Down Expand Up @@ -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<dyn std::error::Error>> {
let client = Client::try_default().await?;

let serviceaccount_name = "fakesa";
let serviceaccount_namespace = "default";
let audiences = vec!["api".to_string()];

let serviceaccounts: Api<ServiceAccount> = Api::namespaced(client.clone(), serviceaccount_namespace);
let tokenreviews: Api<TokenReview> = 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(())
}
}
29 changes: 29 additions & 0 deletions kube-core/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>,
) -> Result<http::Request<Vec<u8>>, 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)
}

clux marked this conversation as resolved.
Show resolved Hide resolved
/// Patch an instance of the subresource
pub fn patch_subresource<P: serde::Serialize>(
&self,
Expand Down Expand Up @@ -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]
Expand Down