Skip to content

Commit

Permalink
Implement Ephemeral Containers subresource
Browse files Browse the repository at this point in the history
This commit implements replace, patch and get operations for the
ephemeral containers subresource.

Signed-off-by: Jessie Chatham Spencer <[email protected]>
  • Loading branch information
jmintb committed Jul 22, 2023
1 parent a9f1a4f commit 18cec4c
Show file tree
Hide file tree
Showing 3 changed files with 336 additions and 30 deletions.
5 changes: 5 additions & 0 deletions kube-client/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ mod subresource;
pub use subresource::{Attach, AttachParams, Execute, Portforward};
pub use subresource::{Evict, EvictParams, Log, LogParams, ScaleSpec, ScaleStatus};

// Ephemeral containers were stabilized in Kubernetes 1.25.
k8s_openapi::k8s_if_ge_1_25! {
pub use subresource::Ephemeral;
}

mod util;

pub mod entry;
Expand Down
208 changes: 180 additions & 28 deletions kube-client/src/api/subresource.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use futures::AsyncBufRead;
use serde::de::DeserializeOwned;
use serde::{de::DeserializeOwned, Serialize};
use std::fmt::Debug;

use crate::{
Expand Down Expand Up @@ -127,6 +127,160 @@ where
}
}

// ----------------------------------------------------------------------------
// Ephemeral containers
// ----------------------------------------------------------------------------

/// Marker trait for objects that support the ephemeral containers sub resource.
pub trait Ephemeral {}

impl Ephemeral for k8s_openapi::api::core::v1::Pod {}

impl<K> Api<K>
where
K: Clone + DeserializeOwned + Ephemeral,
{
/// Replace the ephemeral containers sub resource entirely.
///
/// This functions in the same way as [`Api::replace`] except only `.spec.ephemeralcontainers` is replaced, everything else is ignored.
///
/// Note that ephemeral containers may **not** be changed or removed once attached to a pod.
///
///
/// You way want to patch the underlying resource to gain access to the main container process,
/// see the [documentation](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/) for `sharedProcessNamespace`.
///
/// See the Kubernetes [documentation](https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/#what-is-an-ephemeral-container) for more details.
///
/// [`Api::patch_ephemeral_containers`] may be more ergonomic, as you can will avoid having to first fetch the
/// existing subresources with an approriate merge strategy, see the examples for more details.
///
/// Example of using `replace_ephemeral_containers`:
///
/// ```no_run
/// use k8s_openapi::api::core::v1::Pod;
/// use kube::{Api, api::PostParams};
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
/// # let client = kube::Client::try_default().await?;
/// let pods: Api<Pod> = Api::namespaced(client, "apps");
/// let pp = PostParams::default();
///
/// // Get pod object with ephemeral containers.
/// let mut mypod = pods.get_ephemeral_containers("mypod").await?;
///
/// // If there were existing ephemeral containers, we would have to append
/// // new containers to the list before calling replace_ephemeral_containers.
/// assert_eq!(mypod.spec.as_mut().unwrap().ephemeral_containers, None);
///
/// // Add an ephemeral container to the pod object.
/// mypod.spec.as_mut().unwrap().ephemeral_containers = Some(serde_json::from_value(serde_json::json!([
/// {
/// "name": "myephemeralcontainer",
/// "image": "busybox:1.34.1",
/// "command": ["sh", "-c", "sleep 20"],
/// },
/// ]))?);
///
/// pods.replace_ephemeral_containers("mypod", &pp, &mypod).await?;
///
/// # Ok(())
/// # }
/// ```
pub async fn replace_ephemeral_containers(&self, name: &str, pp: &PostParams, data: &K) -> Result<K>
where
K: Serialize,
{
let mut req = self
.request
.replace_subresource(
"ephemeralcontainers",
name,
pp,
serde_json::to_vec(data).map_err(Error::SerdeError)?,
)
.map_err(Error::BuildRequest)?;
req.extensions_mut().insert("replace_ephemeralcontainers");
self.client.request::<K>(req).await
}

/// Patch the ephemeral containers sub resource
///
/// Any partial object containing the ephemeral containers
/// sub resource is valid as long as the complete structure
/// for the object is present, as shown below.
///
/// You way want to patch the underlying resource to gain access to the main container process,
/// see the [docs](https://kubernetes.io/docs/tasks/configure-pod-container/share-process-namespace/) for `sharedProcessNamespace`.
///
/// Ephemeral containers may **not** be changed or removed once attached to a pod.
/// Therefore if the chosen merge strategy overwrites the existing ephemeral containers,
/// you will have to fetch the existing ephemeral containers first.
/// In order to append your new ephemeral containers to the existing list before patching. See some examples and
/// discussion related to merge strategies in Kubernetes
/// [here](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/update-api-object-kubectl-patch/#use-a-json-merge-patch-to-update-a-deployment). The example below uses a strategic merge patch which does not require
///
/// See the `Kubernetes` [documentation](https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/)
/// for more information about ephemeral containers.
///
///
/// Example of using `patch_ephemeral_containers`:
///
/// ```no_run
/// use kube::api::{Api, PatchParams, Patch};
/// use k8s_openapi::api::core::v1::Pod;
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
/// # let client = kube::Client::try_default().await?;
/// let pods: Api<Pod> = Api::namespaced(client, "apps");
/// let pp = PatchParams::default(); // stratetgic merge patch
///
/// // Note that the strategic merge patch will concatenate the
/// // lists of ephemeral containers so we avoid having to fetch the
/// // current list and append to it manually.
/// let patch = serde_json::json!({
/// "spec":{
/// "ephemeralContainers": [
/// {
/// "name": "myephemeralcontainer",
/// "image": "busybox:1.34.1",
/// "command": ["sh", "-c", "sleep 20"],
/// },
/// ]
/// }});
///
/// pods.patch_ephemeral_containers("mypod", &pp, &Patch::Strategic(patch)).await?;
///
/// # Ok(())
/// # }
/// ```
pub async fn patch_ephemeral_containers<P: serde::Serialize>(
&self,
name: &str,
pp: &PatchParams,
patch: &Patch<P>,
) -> Result<K> {
let mut req = self
.request
.patch_subresource("ephemeralcontainers", name, pp, patch)
.map_err(Error::BuildRequest)?;

req.extensions_mut().insert("patch_ephemeralcontainers");
self.client.request::<K>(req).await
}

/// Get the named resource with the ephemeral containers subresource.
///
/// This returns the whole K, with metadata and spec.
pub async fn get_ephemeral_containers(&self, name: &str) -> Result<K> {
let mut req = self
.request
.get_subresource("ephemeralcontainers", name)
.map_err(Error::BuildRequest)?;

req.extensions_mut().insert("get_ephemeralcontainers");
self.client.request::<K>(req).await
}
}

// ----------------------------------------------------------------------------

// TODO: Replace examples with owned custom resources. Bad practice to write to owned objects
Expand All @@ -153,23 +307,22 @@ where
/// NB: Requires that the resource has a status subresource.
///
/// ```no_run
/// use kube::{api::{Api, PatchParams, Patch}, Client};
/// use kube::api::{Api, PatchParams, Patch};
/// use k8s_openapi::api::batch::v1::Job;
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let client = Client::try_default().await?;
/// let jobs: Api<Job> = Api::namespaced(client, "apps");
/// let mut j = jobs.get("baz").await?;
/// let pp = PatchParams::default(); // json merge patch
/// let data = serde_json::json!({
/// "status": {
/// "succeeded": 2
/// }
/// });
/// let o = jobs.patch_status("baz", &pp, &Patch::Merge(data)).await?;
/// assert_eq!(o.status.unwrap().succeeded, Some(2));
/// Ok(())
/// }
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
/// # let client = kube::Client::try_default().await?;
/// let jobs: Api<Job> = Api::namespaced(client, "apps");
/// let mut j = jobs.get("baz").await?;
/// let pp = PatchParams::default(); // json merge patch
/// let data = serde_json::json!({
/// "status": {
/// "succeeded": 2
/// }
/// });
/// let o = jobs.patch_status("baz", &pp, &Patch::Merge(data)).await?;
/// assert_eq!(o.status.unwrap().succeeded, Some(2));
/// # Ok(())
/// # }
/// ```
pub async fn patch_status<P: serde::Serialize + Debug>(
&self,
Expand All @@ -191,18 +344,17 @@ where
/// You can leave out the `.spec` entirely from the serialized output.
///
/// ```no_run
/// use kube::{api::{Api, PostParams}, Client};
/// use kube::api::{Api, PostParams};
/// use k8s_openapi::api::batch::v1::{Job, JobStatus};
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let client = Client::try_default().await?;
/// let jobs: Api<Job> = Api::namespaced(client, "apps");
/// let mut o = jobs.get_status("baz").await?; // retrieve partial object
/// o.status = Some(JobStatus::default()); // update the job part
/// let pp = PostParams::default();
/// let o = jobs.replace_status("baz", &pp, serde_json::to_vec(&o)?).await?;
/// Ok(())
/// }
/// # async fn wrapper() -> Result<(), Box<dyn std::error::Error>> {
/// # let client = kube::Client::try_default().await?;
/// let jobs: Api<Job> = Api::namespaced(client, "apps");
/// let mut o = jobs.get_status("baz").await?; // retrieve partial object
/// o.status = Some(JobStatus::default()); // update the job part
/// let pp = PostParams::default();
/// let o = jobs.replace_status("baz", &pp, serde_json::to_vec(&o)?).await?;
/// # Ok(())
/// # }
/// ```
pub async fn replace_status(&self, name: &str, pp: &PostParams, data: Vec<u8>) -> Result<K> {
let mut req = self
Expand Down
Loading

0 comments on commit 18cec4c

Please sign in to comment.