diff --git a/Cargo.toml b/Cargo.toml index 00e695c..6f3c596 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,6 +103,7 @@ tokio-util = { version = "0.7.10", features = ["compat"] } zookeeper-client = { version = "0.8.0" } kube = { version = "0.90.0", default-features = false, features = [ "client", + "runtime", "rustls-tls", ] } k8s-openapi = { version = "0.21.1", features = ["v1_29"] } diff --git a/src/k3s/mod.rs b/src/k3s/mod.rs index 56a1392..587e141 100644 --- a/src/k3s/mod.rs +++ b/src/k3s/mod.rs @@ -171,12 +171,10 @@ mod tests { use std::env::temp_dir; use k8s_openapi::api::core::v1::Pod; - use kube::{ - api::ListParams, - config::{KubeConfigOptions, Kubeconfig}, - Api, Config, ResourceExt, - }; + use kube::config::{KubeConfigOptions, Kubeconfig}; + use kube::{api::{Api, DeleteParams, ListParams, Patch, PatchParams, PostParams, ResourceExt}, runtime::wait::{await_condition, conditions::is_pod_running}, Config}; use rustls::crypto::CryptoProvider; + use serde_json::json; use testcontainers::{runners::AsyncRunner, ContainerAsync, ImageExt}; use super::*; @@ -224,6 +222,84 @@ mod tests { Ok(()) } + // Based on: https://github.com/kube-rs/kube/blob/main/examples/pod_api.rs + #[tokio::test] + async fn pod_api() -> Result<(), Box> { + let conf_dir = temp_dir(); + let k3s = K3s::default() + .with_conf_mount(&conf_dir) + .with_privileged(true) + .with_userns_mode("host"); + + let k3s_container = k3s.start().await?; + + let client = get_kube_client(&k3s_container).await?; + + // Manage pods + let pods: Api = Api::default_namespaced(client); + + // Create Pod blog + let p: Pod = serde_json::from_value(json!({ + "apiVersion": "v1", + "kind": "Pod", + "metadata": { "name": "blog" }, + "spec": { + "containers": [{ + "name": "blog", + "image": "clux/blog:0.1.0" + }], + } + }))?; + + let pp = PostParams::default(); + match pods.create(&pp, &p).await { + Ok(o) => { + let name = o.name_any(); + assert_eq!(p.name_any(), name); + } + Err(kube::Error::Api(ae)) => assert_eq!(ae.code, 409), // if you skipped delete, for instance + Err(e) => return Err(e.into()), // any other case is probably bad + } + + // Watch it phase for a few seconds + let establish = await_condition(pods.clone(), "blog", is_pod_running()); + let _ = tokio::time::timeout(std::time::Duration::from_secs(15), establish).await?; + + // Verify we can get it + let p1cpy = pods.get("blog").await?; + if let Some(spec) = &p1cpy.spec { + assert_eq!(spec.containers[0].name, "blog"); + } + + // Replace its spec + let patch = json!({ + "metadata": { + "resourceVersion": p1cpy.resource_version(), + }, + "spec": { + "activeDeadlineSeconds": 5 + } + }); + let patchparams = PatchParams::default(); + let p_patched = pods + .patch("blog", &patchparams, &Patch::Merge(&patch)) + .await?; + assert_eq!(p_patched.spec.unwrap().active_deadline_seconds, Some(5)); + + let lp = ListParams::default().fields(&format!("metadata.name={}", "blog")); // only want results for our pod + for p in pods.list(&lp).await? { + println!("Found Pod: {}", p.name_any()); + } + + // Delete it + let dp = DeleteParams::default(); + pods.delete("blog", &dp).await?.map_left(|pdel| { + assert_eq!(pdel.name_any(), "blog"); + }); + + Ok(()) + } + pub async fn get_kube_client( container: &ContainerAsync, ) -> Result> {