Skip to content

Commit

Permalink
Implement Ephemeral Containers subresource
Browse files Browse the repository at this point in the history
Extra:

An example of an invalid patch that will not cause an error has been
added to Patch docs.

Signed-off-by: Jessie Chatham Spencer <jessie@teainspace.com>
  • Loading branch information
jmintb committed Mar 18, 2023
1 parent 2e5e4de commit 772463e
Show file tree
Hide file tree
Showing 4 changed files with 366 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 @@ -15,6 +15,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,6 +1,6 @@
use bytes::Bytes;
use futures::Stream;
use serde::de::DeserializeOwned;
use serde::{de::DeserializeOwned, Serialize};
use std::fmt::Debug;

use crate::{
Expand Down Expand Up @@ -128,6 +128,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 @@ -154,23 +308,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 @@ -192,18 +345,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 772463e

Please sign in to comment.